diff --git a/packages/frontend/core/src/components/affine/auth/subscription-redirect.css.ts b/packages/frontend/core/src/components/affine/auth/subscription-redirect.css.ts
index d0ba8345c5..c72392b20f 100644
--- a/packages/frontend/core/src/components/affine/auth/subscription-redirect.css.ts
+++ b/packages/frontend/core/src/components/affine/auth/subscription-redirect.css.ts
@@ -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',
+});
diff --git a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx
index 85f96c1e3a..e142148eea 100644
--- a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx
+++ b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx
@@ -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 (
+
+
+
+ );
+};
+
+const SubscriptionExisting = () => {
+ const t = useAFFiNEI18N();
+ const { jumpToIndex } = useNavigateHelper();
+
+ const onButtonClick = useCallback(() => {
+ jumpToIndex(RouteLogic.REPLACE);
+ }, [jumpToIndex]);
+
+ return (
+
+
+
+
+ {t['com.affine.payment.subscription.exist']()}
+
+
+
+
+ );
+};
+
+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 ;
+ }
+
+ return ;
+};
+
+export const SubscriptionRedirect = () => {
return (
-
-
-
+ }>
+
+
);
};
diff --git a/packages/frontend/core/src/pages/sign-in.tsx b/packages/frontend/core/src/pages/sign-in.tsx
index b759891822..68f735c6a4 100644
--- a/packages/frontend/core/src/pages/sign-in.tsx
+++ b/packages/frontend/core/src/pages/sign-in.tsx
@@ -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 ;
+ }
+
return (
{
- 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}
/>
);
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 03cb2153a9..07cca76d48 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -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 support1>.",
+ "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",