mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 21:06:22 +08:00
fix(core): visit /signin pay when already logged and subscribed (#4882)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user