Skip to content

Commit

Permalink
feat(core): add subscribe link (#6610)
Browse files Browse the repository at this point in the history
  • Loading branch information
EYHN committed Apr 18, 2024
1 parent 5437c65 commit 09832dc
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 48 deletions.
20 changes: 15 additions & 5 deletions packages/frontend/core/src/components/affine/auth/oauth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const OAuthProviderMap: Record<
},
};

export function OAuth() {
export function OAuth({ redirectUri }: { redirectUri?: string | null }) {
const serverConfig = useService(ServerConfigService).serverConfig;
const oauth = useLiveData(serverConfig.features$.map(r => r?.oauth));
const oauthProviders = useLiveData(
Expand All @@ -42,27 +42,37 @@ export function OAuth() {
}

return oauthProviders?.map(provider => (
<OAuthProvider key={provider} provider={provider} />
<OAuthProvider
key={provider}
provider={provider}
redirectUri={redirectUri}
/>
));
}

function OAuthProvider({ provider }: { provider: OAuthProviderType }) {
function OAuthProvider({
provider,
redirectUri,
}: {
provider: OAuthProviderType;
redirectUri?: string | null;
}) {
const { icon } = OAuthProviderMap[provider];
const authService = useService(AuthService);
const [isConnecting, setIsConnecting] = useState(false);

const onClick = useAsyncCallback(async () => {
try {
setIsConnecting(true);
await authService.signInOauth(provider);
await authService.signInOauth(provider, redirectUri);
} catch (err) {
console.error(err);
notify.error({ message: 'Failed to sign in, please try again.' });
} finally {
setIsConnecting(false);
mixpanel.track('OAuth', { provider });
}
}, [authService, provider]);
}, [authService, provider, redirectUri]);

return (
<Button
Expand Down
29 changes: 24 additions & 5 deletions packages/frontend/core/src/components/affine/auth/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ArrowDownBigIcon } from '@blocksuite/icons';
import { useLiveData, useService } from '@toeverything/infra';
import type { FC } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

import { AuthService } from '../../../modules/cloud';
import { mixpanel } from '../../../utils';
Expand All @@ -29,7 +30,7 @@ export const SignIn: FC<AuthPanelProps> = ({
}) => {
const t = useAFFiNEI18N();
const authService = useService(AuthService);

const [searchParams] = useSearchParams();
const [isMutating, setIsMutating] = useState(false);
const [verifyToken, challenge] = useCaptcha();

Expand Down Expand Up @@ -74,11 +75,21 @@ export const SignIn: FC<AuthPanelProps> = ({
mixpanel.track_forms('SignIn', 'Email', {
email,
});
await authService.sendEmailMagicLink(email, verifyToken, challenge);
await authService.sendEmailMagicLink(
email,
verifyToken,
challenge,
searchParams.get('redirect_uri')
);
setAuthState('afterSignInSendEmail');
}
} else {
await authService.sendEmailMagicLink(email, verifyToken, challenge);
await authService.sendEmailMagicLink(
email,
verifyToken,
challenge,
searchParams.get('redirect_uri')
);
mixpanel.track_forms('SignUp', 'Email', {
email,
});
Expand All @@ -95,7 +106,15 @@ export const SignIn: FC<AuthPanelProps> = ({
}

setIsMutating(false);
}, [authService, challenge, email, setAuthEmail, setAuthState, verifyToken]);
}, [
authService,
challenge,
email,
searchParams,
setAuthEmail,
setAuthState,
verifyToken,
]);

return (
<>
Expand All @@ -104,7 +123,7 @@ export const SignIn: FC<AuthPanelProps> = ({
subTitle={t['com.affine.brand.affineCloud']()}
/>

<OAuth />
<OAuth redirectUri={searchParams.get('redirect_uri')} />

<div className={style.authModalContent}>
<AuthInput
Expand Down
15 changes: 11 additions & 4 deletions packages/frontend/core/src/hooks/use-navigate-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,20 @@ export function useNavigateHelper() {
);
const jumpToSignIn = useCallback(
(
redirectUri?: string,
logic: RouteLogic = RouteLogic.PUSH,
otherOptions?: Omit<NavigateOptions, 'replace'>
) => {
return navigate('/signIn', {
replace: logic === RouteLogic.REPLACE,
...otherOptions,
});
return navigate(
'/signIn' +
(redirectUri
? `?redirect_uri=${encodeURIComponent(redirectUri)}`
: ''),
{
replace: logic === RouteLogic.REPLACE,
...otherOptions,
}
);
},
[navigate]
);
Expand Down
7 changes: 5 additions & 2 deletions packages/frontend/core/src/modules/cloud/entities/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ export class AuthSession extends Entity {
}
}

async waitForRevalidation() {
async waitForRevalidation(signal?: AbortSignal) {
this.revalidate();
await this.isRevalidating$.waitFor(isRevalidating => !isRevalidating);
await this.isRevalidating$.waitFor(
isRevalidating => !isRevalidating,
signal
);
}

async removeAvatar() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,12 @@ export class Subscription extends Entity {
await this.waitForRevalidation();
}

async waitForRevalidation() {
async waitForRevalidation(signal?: AbortSignal) {
this.revalidate();
await this.isRevalidating$.waitFor(isRevalidating => !isRevalidating);
await this.isRevalidating$.waitFor(
isRevalidating => !isRevalidating,
signal
);
}

revalidate = effect(
Expand Down
16 changes: 8 additions & 8 deletions packages/frontend/core/src/modules/cloud/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,18 @@ export class AuthService extends Service {
async sendEmailMagicLink(
email: string,
verifyToken: string,
challenge?: string
challenge?: string,
redirectUri?: string | null
) {
const searchParams = new URLSearchParams();
if (challenge) {
searchParams.set('challenge', challenge);
}
searchParams.set('token', verifyToken);
const redirectUri = new URL(location.href);
if (environment.isDesktop) {
redirectUri.pathname = this.buildRedirectUri('/open-app/signin-redirect');
}
searchParams.set('redirect_uri', redirectUri.toString());
const redirect = environment.isDesktop
? this.buildRedirectUri('/open-app/signin-redirect')
: redirectUri ?? location.href;
searchParams.set('redirect_uri', redirect.toString());

const res = await this.fetchService.fetch(
'/api/auth/sign-in?' + searchParams.toString(),
Expand All @@ -104,7 +104,7 @@ export class AuthService extends Service {
}
}

async signInOauth(provider: OAuthProviderType) {
async signInOauth(provider: OAuthProviderType, redirectUri?: string | null) {
if (environment.isDesktop) {
await apis?.ui.openExternal(
`${
Expand All @@ -117,7 +117,7 @@ export class AuthService extends Service {
location.href = `${
runtimeConfig.serverUrlPrefix
}/oauth/login?provider=${provider}&redirect_uri=${encodeURIComponent(
location.pathname
redirectUri ?? location.pathname
)}`;
}

Expand Down
9 changes: 4 additions & 5 deletions packages/frontend/core/src/pages/invite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 5 additions & 17 deletions packages/frontend/core/src/pages/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,29 @@ 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();
const isLoggedIn = status === 'authenticated' && !isRevalidating;

useEffect(() => {
if (isLoggedIn) {
if (location.state?.callbackURL) {
navigate(location.state.callbackURL, {
const redirectUri = searchParams.get('redirect_uri');
if (redirectUri) {
navigate(redirectUri, {
replace: true,
});
} else {
Expand All @@ -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']) => {
Expand Down
13 changes: 13 additions & 0 deletions packages/frontend/core/src/pages/subscribe.css.ts
Original file line number Diff line number Diff line change
@@ -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'),
});
Loading

0 comments on commit 09832dc

Please sign in to comment.