fix(core): visit /signin pay when already logged and subscribed (#4882)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
Joooye_34
2023-11-09 15:38:52 +08:00
committed by GitHub
parent 227b8b0061
commit 12a2ccf1a5
4 changed files with 143 additions and 32 deletions
@@ -7,3 +7,24 @@ export const loadingContainer = style({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}); });
export const subscriptionLayout = style({
margin: '10% auto',
maxWidth: '536px',
});
export const subscriptionBox = style({
padding: '48px 52px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
});
export const subscriptionTips = style({
margin: '20px 0',
color: 'var(--affine-text-secondary-color)',
fontSize: '12px',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '20px',
});
@@ -1,16 +1,62 @@
import { AffineShapeIcon } from '@affine/component/page-list';
import type { SubscriptionRecurring } from '@affine/graphql'; import type { SubscriptionRecurring } from '@affine/graphql';
import { checkoutMutation } from '@affine/graphql'; import { checkoutMutation, subscriptionQuery } from '@affine/graphql';
import { useMutation } from '@affine/workspace/affine/gql'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { Button } from '@toeverything/components/button';
import { Loading } from '@toeverything/components/loading'; import { Loading } from '@toeverything/components/loading';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { type FC, useEffect, useMemo } from 'react'; import { type FC, Suspense, useCallback, useEffect, useMemo } from 'react';
import {
RouteLogic,
useNavigateHelper,
} from '../../../hooks/use-navigate-helper';
import * as styles from './subscription-redirect.css'; import * as styles from './subscription-redirect.css';
import { useSubscriptionSearch } from './use-subscription'; import { useSubscriptionSearch } from './use-subscription';
export const SubscriptionRedirect: FC = () => { const CenterLoading = () => {
return (
<div className={styles.loadingContainer}>
<Loading size={40} />
</div>
);
};
const SubscriptionExisting = () => {
const t = useAFFiNEI18N();
const { jumpToIndex } = useNavigateHelper();
const onButtonClick = useCallback(() => {
jumpToIndex(RouteLogic.REPLACE);
}, [jumpToIndex]);
return (
<div className={styles.subscriptionLayout}>
<div className={styles.subscriptionBox}>
<AffineShapeIcon width={180} height={180} />
<p className={styles.subscriptionTips}>
{t['com.affine.payment.subscription.exist']()}
</p>
<Button
data-testid="upgrade-workspace-button"
onClick={onButtonClick}
size="extraLarge"
type="primary"
>
{t['com.affine.auth.open.affine']()}
</Button>
</div>
</div>
);
};
const SubscriptionRedirectInner: FC = () => {
const subscriptionData = useSubscriptionSearch(); const subscriptionData = useSubscriptionSearch();
const idempotencyKey = useMemo(() => nanoid(), []); const idempotencyKey = useMemo(() => nanoid(), []);
const { data } = useQuery({
query: subscriptionQuery,
});
const { trigger: checkoutSubscription } = useMutation({ const { trigger: checkoutSubscription } = useMutation({
mutation: checkoutMutation, mutation: checkoutMutation,
}); });
@@ -20,12 +66,18 @@ export const SubscriptionRedirect: FC = () => {
throw new Error('No subscription data found'); throw new Error('No subscription data found');
} }
if (data.currentUser?.subscription) {
return;
}
// This component will be render multiple times, use timeout to avoid multiple effect. // This component will be render multiple times, use timeout to avoid multiple effect.
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
const recurring = subscriptionData.recurring as SubscriptionRecurring; const recurring = subscriptionData.recurring as SubscriptionRecurring;
checkoutSubscription({ recurring, idempotencyKey }).then(data => { checkoutSubscription({ recurring, idempotencyKey }).then(
window.open(data.checkout, '_self', 'norefferer'); ({ checkout }) => {
}); window.open(checkout, '_self', 'norefferer');
}
);
}, 100); }, 100);
return () => { return () => {
@@ -36,9 +88,17 @@ export const SubscriptionRedirect: FC = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
if (data.currentUser?.subscription) {
return <SubscriptionExisting />;
}
return <CenterLoading />;
};
export const SubscriptionRedirect = () => {
return ( return (
<div className={styles.loadingContainer}> <Suspense fallback={<CenterLoading />}>
<Loading size={40} /> <SubscriptionRedirectInner />
</div> </Suspense>
); );
}; };
+51 -22
View File
@@ -1,11 +1,13 @@
import { SignInPageContainer } from '@affine/component/auth-components'; import { SignInPageContainer } from '@affine/component/auth-components';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect, useRef } from 'react';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { authAtom } from '../atoms'; import { authAtom } from '../atoms';
import { AuthPanel } from '../components/affine/auth'; import { AuthPanel, type AuthProps } from '../components/affine/auth';
import { SubscriptionRedirect } from '../components/affine/auth/subscription-redirect';
import { useSubscriptionSearch } from '../components/affine/auth/use-subscription';
import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status'; import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status';
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper'; import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
@@ -15,15 +17,31 @@ interface LocationState {
}; };
} }
export const Component = () => { export const Component = () => {
const paymentRedirectRef = useRef<'redirect' | 'ignore' | null>(null);
const [{ state, email = '', emailType = 'changePassword' }, setAuthAtom] = const [{ state, email = '', emailType = 'changePassword' }, setAuthAtom] =
useAtom(authAtom); useAtom(authAtom);
const loginStatus = useCurrentLoginStatus(); const loginStatus = useCurrentLoginStatus();
const location = useLocation() as LocationState; const location = useLocation() as LocationState;
const navigate = useNavigate(); const navigate = useNavigate();
const { jumpToIndex } = useNavigateHelper(); const { jumpToIndex } = useNavigateHelper();
const subscriptionData = useSubscriptionSearch();
const isLoggedIn = loginStatus === 'authenticated';
// Check payment redirect once after session loaded, to avoid unnecessary page rendering.
if (loginStatus !== 'loading' && !paymentRedirectRef.current) {
// If user is logged in and visit sign in page with subscription query, redirect to stripe payment page immediately.
// Otherwise, user will login through email, and then redirect to payment page.
paymentRedirectRef.current =
subscriptionData && isLoggedIn ? 'redirect' : 'ignore';
}
useEffect(() => { useEffect(() => {
if (loginStatus === 'authenticated') { if (paymentRedirectRef.current === 'redirect') {
return;
}
if (isLoggedIn) {
if (location.state?.callbackURL) { if (location.state?.callbackURL) {
navigate(location.state.callbackURL, { navigate(location.state.callbackURL, {
replace: true, replace: true,
@@ -35,35 +53,46 @@ export const Component = () => {
}, [ }, [
jumpToIndex, jumpToIndex,
location.state?.callbackURL, location.state?.callbackURL,
loginStatus,
navigate, navigate,
setAuthAtom, setAuthAtom,
subscriptionData,
isLoggedIn,
]); ]);
const onSetEmailType = useCallback(
(emailType: AuthProps['emailType']) => {
setAuthAtom(prev => ({ ...prev, emailType }));
},
[setAuthAtom]
);
const onSetAuthState = useCallback(
(state: AuthProps['state']) => {
setAuthAtom(prev => ({ ...prev, state }));
},
[setAuthAtom]
);
const onSetAuthEmail = useCallback(
(email: AuthProps['email']) => {
setAuthAtom(prev => ({ ...prev, email }));
},
[setAuthAtom]
);
if (paymentRedirectRef.current === 'redirect') {
return <SubscriptionRedirect />;
}
return ( return (
<SignInPageContainer> <SignInPageContainer>
<AuthPanel <AuthPanel
state={state} state={state}
email={email} email={email}
emailType={emailType} emailType={emailType}
setEmailType={useCallback( setEmailType={onSetEmailType}
emailType => { setAuthState={onSetAuthState}
setAuthAtom(prev => ({ ...prev, emailType })); setAuthEmail={onSetAuthEmail}
},
[setAuthAtom]
)}
setAuthState={useCallback(
state => {
setAuthAtom(prev => ({ ...prev, state }));
},
[setAuthAtom]
)}
setAuthEmail={useCallback(
email => {
setAuthAtom(prev => ({ ...prev, email }));
},
[setAuthAtom]
)}
/> />
</SignInPageContainer> </SignInPageContainer>
); );
@@ -766,6 +766,7 @@
"com.affine.payment.upgrade-success-page.title": "Upgrade Successful!", "com.affine.payment.upgrade-success-page.title": "Upgrade Successful!",
"com.affine.payment.upgrade-success-page.text": "Congratulations! Your AFFiNE account has been successfully upgraded to a Pro account.", "com.affine.payment.upgrade-success-page.text": "Congratulations! Your AFFiNE account has been successfully upgraded to a Pro account.",
"com.affine.payment.upgrade-success-page.support": "If you have any questions, please contact our <1> customer support</1>.", "com.affine.payment.upgrade-success-page.support": "If you have any questions, please contact our <1> customer support</1>.",
"com.affine.payment.subscription.exist": "You already have a subscription.",
"com.affine.other-page.nav.official-website": "Official Website", "com.affine.other-page.nav.official-website": "Official Website",
"com.affine.other-page.nav.affine-community": "AFFiNE Community", "com.affine.other-page.nav.affine-community": "AFFiNE Community",
"com.affine.other-page.nav.blog": "Blog", "com.affine.other-page.nav.blog": "Blog",