feat(core): add subscribe link (#6610)

This commit is contained in:
EYHN
2024-04-18 13:28:32 +00:00
parent 5437c6567b
commit 09832dc940
11 changed files with 216 additions and 48 deletions

View File

@@ -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,

View File

@@ -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']) => {

View 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'),
});

View 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>
);
};