mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-21 16:26:58 +08:00
feat(core): add subscribe link (#6610)
This commit is contained in:
@@ -79,11 +79,10 @@ export const Component = () => {
|
||||
if (loginStatus === 'unauthenticated' && !isRevalidating) {
|
||||
// We can not pass function to navigate state, so we need to save it in atom
|
||||
setOnceSignedInEvent(openWorkspace);
|
||||
jumpToSignIn(RouteLogic.REPLACE, {
|
||||
state: {
|
||||
callbackURL: `/workspace/${inviteInfo.workspace.id}/all`,
|
||||
},
|
||||
});
|
||||
jumpToSignIn(
|
||||
`/workspace/${inviteInfo.workspace.id}/all`,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
}
|
||||
}, [
|
||||
inviteInfo.workspace.id,
|
||||
|
||||
@@ -5,25 +5,19 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { authAtom } from '../atoms';
|
||||
import type { AuthProps } from '../components/affine/auth';
|
||||
import { AuthPanel } from '../components/affine/auth';
|
||||
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
|
||||
interface LocationState {
|
||||
state?: {
|
||||
callbackURL?: string;
|
||||
};
|
||||
}
|
||||
export const SignIn = () => {
|
||||
const [{ state, email = '', emailType = 'changePassword' }, setAuthAtom] =
|
||||
useAtom(authAtom);
|
||||
const session = useService(AuthService).session;
|
||||
const status = useLiveData(session.status$);
|
||||
const isRevalidating = useLiveData(session.isRevalidating$);
|
||||
const location = useLocation() as LocationState;
|
||||
const navigate = useNavigate();
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -31,8 +25,9 @@ export const SignIn = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) {
|
||||
if (location.state?.callbackURL) {
|
||||
navigate(location.state.callbackURL, {
|
||||
const redirectUri = searchParams.get('redirect_uri');
|
||||
if (redirectUri) {
|
||||
navigate(redirectUri, {
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
@@ -41,14 +36,7 @@ export const SignIn = () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
jumpToIndex,
|
||||
location.state,
|
||||
navigate,
|
||||
setAuthAtom,
|
||||
isLoggedIn,
|
||||
searchParams,
|
||||
]);
|
||||
}, [jumpToIndex, navigate, setAuthAtom, isLoggedIn, searchParams]);
|
||||
|
||||
const onSetEmailType = useCallback(
|
||||
(emailType: AuthProps['emailType']) => {
|
||||
|
||||
13
packages/frontend/core/src/pages/subscribe.css.ts
Normal file
13
packages/frontend/core/src/pages/subscribe.css.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const container = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
width: '100%',
|
||||
lineHeight: 4,
|
||||
color: cssVar('--affine-text-secondary-color'),
|
||||
});
|
||||
122
packages/frontend/core/src/pages/subscribe.tsx
Normal file
122
packages/frontend/core/src/pages/subscribe.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Button, Loading } from '@affine/component';
|
||||
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
|
||||
import { effect, fromPromise, useServices } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { EMPTY, mergeMap, switchMap } from 'rxjs';
|
||||
|
||||
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { AuthService, SubscriptionService } from '../modules/cloud';
|
||||
import { container } from './subscribe.css';
|
||||
|
||||
export const Component = () => {
|
||||
const { authService, subscriptionService } = useServices({
|
||||
AuthService,
|
||||
SubscriptionService,
|
||||
});
|
||||
const [searchParams] = useSearchParams();
|
||||
const [message, setMessage] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [retryKey, setRetryKey] = useState(0);
|
||||
const { jumpToSignIn, jumpToIndex } = useNavigateHelper();
|
||||
const idempotencyKey = useMemo(() => nanoid(), []);
|
||||
|
||||
const plan = searchParams.get('plan') as string | null;
|
||||
const recurring = searchParams.get('recurring') as string | null;
|
||||
|
||||
useEffect(() => {
|
||||
const call = effect(
|
||||
switchMap(() => {
|
||||
return fromPromise(async signal => {
|
||||
retryKey;
|
||||
// TODO: i18n
|
||||
setMessage('Checking account status...');
|
||||
setError('');
|
||||
await authService.session.waitForRevalidation(signal);
|
||||
const loggedIn =
|
||||
authService.session.status$.value === 'authenticated';
|
||||
if (!loggedIn) {
|
||||
setMessage('Redirecting to sign in...');
|
||||
jumpToSignIn(
|
||||
location.pathname + location.search,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
return;
|
||||
}
|
||||
setMessage('Checking subscription status...');
|
||||
await subscriptionService.subscription.waitForRevalidation(signal);
|
||||
const subscribed = !!subscriptionService.subscription.ai$.value;
|
||||
if (!subscribed) {
|
||||
setMessage('Creating checkout...');
|
||||
try {
|
||||
const checkout = await subscriptionService.createCheckoutSession({
|
||||
idempotencyKey,
|
||||
plan:
|
||||
plan?.toLowerCase() === 'ai'
|
||||
? SubscriptionPlan.AI
|
||||
: SubscriptionPlan.Pro,
|
||||
coupon: null,
|
||||
recurring:
|
||||
recurring?.toLowerCase() === 'monthly'
|
||||
? SubscriptionRecurring.Monthly
|
||||
: SubscriptionRecurring.Yearly,
|
||||
successCallbackLink: null,
|
||||
});
|
||||
setMessage('Redirecting...');
|
||||
location.href = checkout;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError('Something went wrong. Please try again.');
|
||||
}
|
||||
} else {
|
||||
setMessage('Your account is already subscribed. Redirecting...');
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 5000);
|
||||
});
|
||||
jumpToIndex(RouteLogic.REPLACE);
|
||||
}
|
||||
}).pipe(mergeMap(() => EMPTY));
|
||||
})
|
||||
);
|
||||
|
||||
call();
|
||||
|
||||
return () => {
|
||||
call.unsubscribe();
|
||||
};
|
||||
}, [
|
||||
authService,
|
||||
subscriptionService,
|
||||
jumpToSignIn,
|
||||
idempotencyKey,
|
||||
plan,
|
||||
jumpToIndex,
|
||||
recurring,
|
||||
retryKey,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
authService.session.revalidate();
|
||||
}, [authService]);
|
||||
|
||||
return (
|
||||
<div className={container}>
|
||||
{!error ? (
|
||||
<>
|
||||
{message}
|
||||
<br />
|
||||
<Loading size={20} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{error}
|
||||
<br />
|
||||
<Button type="primary" onClick={() => setRetryKey(i => i + 1)}>
|
||||
Retry
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user