mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00: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',
|
||||
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 { checkoutMutation } from '@affine/graphql';
|
||||
import { useMutation } from '@affine/workspace/affine/gql';
|
||||
import { checkoutMutation, subscriptionQuery } from '@affine/graphql';
|
||||
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 { 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 { 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 idempotencyKey = useMemo(() => nanoid(), []);
|
||||
const { data } = useQuery({
|
||||
query: subscriptionQuery,
|
||||
});
|
||||
const { trigger: checkoutSubscription } = useMutation({
|
||||
mutation: checkoutMutation,
|
||||
});
|
||||
@@ -20,12 +66,18 @@ export const SubscriptionRedirect: FC = () => {
|
||||
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.
|
||||
const timeoutId = setTimeout(() => {
|
||||
const recurring = subscriptionData.recurring as SubscriptionRecurring;
|
||||
checkoutSubscription({ recurring, idempotencyKey }).then(data => {
|
||||
window.open(data.checkout, '_self', 'norefferer');
|
||||
});
|
||||
checkoutSubscription({ recurring, idempotencyKey }).then(
|
||||
({ checkout }) => {
|
||||
window.open(checkout, '_self', 'norefferer');
|
||||
}
|
||||
);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
@@ -36,9 +88,17 @@ export const SubscriptionRedirect: FC = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (data.currentUser?.subscription) {
|
||||
return <SubscriptionExisting />;
|
||||
}
|
||||
|
||||
return <CenterLoading />;
|
||||
};
|
||||
|
||||
export const SubscriptionRedirect = () => {
|
||||
return (
|
||||
<div className={styles.loadingContainer}>
|
||||
<Loading size={40} />
|
||||
</div>
|
||||
<Suspense fallback={<CenterLoading />}>
|
||||
<SubscriptionRedirectInner />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { SignInPageContainer } from '@affine/component/auth-components';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
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 { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
|
||||
@@ -15,15 +17,31 @@ interface LocationState {
|
||||
};
|
||||
}
|
||||
export const Component = () => {
|
||||
const paymentRedirectRef = useRef<'redirect' | 'ignore' | null>(null);
|
||||
const [{ state, email = '', emailType = 'changePassword' }, setAuthAtom] =
|
||||
useAtom(authAtom);
|
||||
const loginStatus = useCurrentLoginStatus();
|
||||
const location = useLocation() as LocationState;
|
||||
const navigate = useNavigate();
|
||||
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(() => {
|
||||
if (loginStatus === 'authenticated') {
|
||||
if (paymentRedirectRef.current === 'redirect') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
if (location.state?.callbackURL) {
|
||||
navigate(location.state.callbackURL, {
|
||||
replace: true,
|
||||
@@ -35,35 +53,46 @@ export const Component = () => {
|
||||
}, [
|
||||
jumpToIndex,
|
||||
location.state?.callbackURL,
|
||||
loginStatus,
|
||||
navigate,
|
||||
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 (
|
||||
<SignInPageContainer>
|
||||
<AuthPanel
|
||||
state={state}
|
||||
email={email}
|
||||
emailType={emailType}
|
||||
setEmailType={useCallback(
|
||||
emailType => {
|
||||
setAuthAtom(prev => ({ ...prev, emailType }));
|
||||
},
|
||||
[setAuthAtom]
|
||||
)}
|
||||
setAuthState={useCallback(
|
||||
state => {
|
||||
setAuthAtom(prev => ({ ...prev, state }));
|
||||
},
|
||||
[setAuthAtom]
|
||||
)}
|
||||
setAuthEmail={useCallback(
|
||||
email => {
|
||||
setAuthAtom(prev => ({ ...prev, email }));
|
||||
},
|
||||
[setAuthAtom]
|
||||
)}
|
||||
setEmailType={onSetEmailType}
|
||||
setAuthState={onSetAuthState}
|
||||
setAuthEmail={onSetAuthEmail}
|
||||
/>
|
||||
</SignInPageContainer>
|
||||
);
|
||||
|
||||
@@ -766,6 +766,7 @@
|
||||
"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.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.affine-community": "AFFiNE Community",
|
||||
"com.affine.other-page.nav.blog": "Blog",
|
||||
|
||||
Reference in New Issue
Block a user