feat(core): make password sigin default if user has one (#5577)

This commit is contained in:
liuyi
2024-01-17 11:13:58 +00:00
parent bf88b6edaa
commit b9f20877d0
12 changed files with 170 additions and 71 deletions

View File

@@ -53,9 +53,12 @@ export const AfterSignInSendEmail = ({
subTitle={t['com.affine.auth.sign.in.sent.email.subtitle']()}
/>
<AuthContent style={{ height: 100 }}>
{t['com.affine.auth.sign.sent.email.message.start']()}
<a href={`mailto:${email}`}>{email}</a>
{t['com.affine.auth.sign.sent.email.message.end']()}
<Trans
i18nKey="com.affine.auth.sign.sent.email.message.sent-tips"
values={{ email }}
components={{ a: <a href={`mailto:${email}`} /> }}
/>
{t['com.affine.auth.sign.sent.email.message.sent-tips.sign-in']()}
</AuthContent>
<div className={style.resendWrapper}>
@@ -73,15 +76,15 @@ export const AfterSignInSendEmail = ({
</Button>
</>
) : (
<>
<span className="resend-code-hint">
{t['com.affine.auth.sign.auth.code.on.resend.hint']()}
</span>
<div className={style.sentRow}>
<div className={style.sentMessage}>
{t['com.affine.auth.sent']()}
</div>
<CountDownRender
className={style.resendCountdown}
timeLeft={resendCountDown}
/>
</>
</div>
)}
</div>
@@ -90,16 +93,18 @@ export const AfterSignInSendEmail = ({
{subscriptionData ? null : ( // If with payment, just support email sign in to avoid duplicate redirect to the same stripe url.
<React.Fragment>
&nbsp;
{/*prettier-ignore*/}
<Trans i18nKey="com.affine.auth.sign.auth.code.message.password">
Or <span
className="link"
data-testid='sign-in-with-password'
onClick={onSignInWithPasswordClick}
>
sign in with password
</span> instead.
</Trans>
<Trans
i18nKey="com.affine.auth.sign.auth.code.message.password"
components={{
1: (
<span
className="link"
data-testid="sign-in-with-password"
onClick={onSignInWithPasswordClick}
/>
),
}}
/>
</React.Fragment>
)}
</div>

View File

@@ -6,6 +6,7 @@ import {
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { type FC, useCallback } from 'react';
@@ -43,9 +44,12 @@ export const AfterSignUpSendEmail: FC<AuthPanelProps> = ({
subTitle={t['com.affine.auth.sign.up.sent.email.subtitle']()}
/>
<AuthContent style={{ height: 100 }}>
{t['com.affine.auth.sign.sent.email.message.start']()}
<a href={`mailto:${email}`}>{email}</a>
{t['com.affine.auth.sign.sent.email.message.end']()}
<Trans
i18nKey="com.affine.auth.sign.sent.email.message.sent-tips"
values={{ email }}
components={{ a: <a href={`mailto:${email}`} /> }}
/>
{t['com.affine.auth.sign.sent.email.message.sent-tips.sign-up']()}
</AuthContent>
<div className={style.resendWrapper}>
@@ -63,15 +67,15 @@ export const AfterSignUpSendEmail: FC<AuthPanelProps> = ({
</Button>
</>
) : (
<>
<span className="resend-code-hint">
{t['com.affine.auth.sign.auth.code.on.resend.hint']()}
</span>
<div className={style.sentRow}>
<div className={style.sentMessage}>
{t['com.affine.auth.sent']()}
</div>
<CountDownRender
className={style.resendCountdown}
timeLeft={resendCountDown}
/>
</>
</div>
)}
</div>

View File

@@ -39,7 +39,7 @@ const useContent = (emailType: AuthPanelProps['emailType'], email: string) => {
case 'setPassword':
return t['com.affine.auth.set.password.message']();
case 'changePassword':
return t['com.affine.auth.set.password.message']();
return t['com.affine.auth.reset.password.message']();
case 'changeEmail':
return t['com.affine.auth.change.email.message']({
email,

View File

@@ -14,10 +14,13 @@ import { useCallback, useState } from 'react';
import { signInCloud } from '../../../utils/cloud-utils';
import type { AuthPanelProps } from './index';
import { forgetPasswordButton } from './style.css';
import * as styles from './style.css';
import { INTERNAL_BETA_URL, useAuth } from './use-auth';
import { useCaptcha } from './use-captcha';
export const SignInWithPassword: FC<AuthPanelProps> = ({
setAuthState,
setEmailType,
email,
onSignedIn,
}) => {
@@ -26,6 +29,13 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState(false);
const {
signIn,
allowSendEmail,
resetCountDown,
isMutating: sendingEmail,
} = useAuth();
const [verifyToken, challenge] = useCaptcha();
const onSignIn = useAsyncCallback(async () => {
const res = await signInCloud('credentials', {
@@ -42,6 +52,31 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
onSignedIn?.();
}, [email, password, onSignedIn, update]);
const sendMagicLink = useAsyncCallback(async () => {
if (allowSendEmail && verifyToken && !sendingEmail) {
const res = await signIn(email, verifyToken, challenge);
if (res?.status === 403 && res?.url === INTERNAL_BETA_URL) {
resetCountDown();
return setAuthState('noAccess');
}
setAuthState('afterSignInSendEmail');
}
}, [
email,
signIn,
allowSendEmail,
sendingEmail,
setAuthState,
verifyToken,
challenge,
resetCountDown,
]);
const sendChangePasswordEmail = useCallback(() => {
setEmailType('changePassword');
setAuthState('sendEmail');
}, [setAuthState, setEmailType]);
return (
<>
<ModalHeader
@@ -51,7 +86,6 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
<Wrapper
marginTop={30}
marginBottom={50}
style={{
position: 'relative',
}}
@@ -73,29 +107,43 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
errorHint={t['com.affine.auth.password.error']()}
onEnter={onSignIn}
/>
<span></span>
<button
className={forgetPasswordButton}
// onClick={useCallback(() => {
// setAuthState('sendPasswordEmail');
// }, [setAuthState])}
<div
className={styles.forgetPasswordButtonRow}
style={{ display: 'none' }} // Not implemented yet.
>
{t['com.affine.auth.forget']()}
</button>
<a
className={styles.linkButton}
onClick={sendChangePasswordEmail}
style={{
color: 'var(--affine-text-secondary-color)',
fontSize: 'var(--affine-font-sm)',
}}
>
{t['com.affine.auth.forget']()}
</a>
</div>
<div className={styles.sendMagicLinkButtonRow}>
<a
data-testid="send-magic-link-button"
className={styles.linkButton}
onClick={sendMagicLink}
>
{t['com.affine.auth.sign.auth.code.send-email.sign-in']()}
</a>
</div>
<Button
data-testid="sign-in-button"
type="primary"
size="extraLarge"
style={{ width: '100%' }}
onClick={onSignIn}
>
{t['com.affine.auth.sign.in']()}
</Button>
</Wrapper>
<Button
data-testid="sign-in-button"
type="primary"
size="extraLarge"
style={{ width: '100%' }}
onClick={onSignIn}
>
{t['com.affine.auth.sign.in']()}
</Button>
<BackButton
onClick={useCallback(() => {
setAuthState('afterSignInSendEmail');
setAuthState('signIn');
}, [setAuthState])}
/>
</>

View File

@@ -61,16 +61,13 @@ export const SignIn: FC<AuthPanelProps> = ({
setIsValidEmail(true);
// 0 for no access for internal beta
let user: GetUserQuery['user'] | null | 0 = null;
await verifyUser({ email })
.then(({ user: u }) => {
user = u;
})
const user: GetUserQuery['user'] | null | 0 = await verifyUser({ email })
.then(({ user }) => user)
.catch(err => {
const e = err?.[0];
if (e instanceof GraphQLError && e.extensions?.code === 402) {
setAuthState('noAccess');
user = 0;
return 0;
} else {
throw err;
}
@@ -83,11 +80,16 @@ export const SignIn: FC<AuthPanelProps> = ({
if (verifyToken) {
if (user) {
const res = await signIn(email, verifyToken, challenge);
if (res?.status === 403 && res?.url === INTERNAL_BETA_URL) {
return setAuthState('noAccess');
// provider password sign-in if user has by default
if (user.hasPassword) {
setAuthState('signInWithPassword');
} else {
const res = await signIn(email, verifyToken, challenge);
if (res?.status === 403 && res?.url === INTERNAL_BETA_URL) {
return setAuthState('noAccess');
}
setAuthState('afterSignInSendEmail');
}
setAuthState('afterSignInSendEmail');
} else {
const res = await signUp(email, verifyToken, challenge);
if (res?.status === 403 && res?.url === INTERNAL_BETA_URL) {

View File

@@ -24,13 +24,30 @@ globalStyle(`${authMessage} .link`, {
color: 'var(--affine-link-color)',
});
export const forgetPasswordButtonRow = style({
position: 'absolute',
right: 0,
marginTop: '-26px', // Let this button be a tail of password input.
});
export const sendMagicLinkButtonRow = style({
marginBottom: '30px',
});
export const linkButton = style({
color: 'var(--affine-link-color)',
background: 'transparent',
borderColor: 'transparent',
fontSize: 'var(--affine-font-xs)',
lineHeight: '22px',
userSelect: 'none',
});
export const forgetPasswordButton = style({
fontSize: 'var(--affine-font-sm)',
color: 'var(--affine-text-secondary-color)',
position: 'absolute',
right: 0,
bottom: 0,
display: 'none',
});
export const resendWrapper = style({
@@ -42,7 +59,21 @@ export const resendWrapper = style({
marginTop: 30,
});
export const resendCountdown = style({ width: 45, textAlign: 'center' });
export const sentRow = style({
display: 'flex',
justifyContent: 'center',
gap: '8px',
lineHeight: '22px',
fontSize: 'var(--affine-font-sm)',
});
export const sentMessage = style({
color: 'var(--affine-text-primary-color)',
fontWeight: 600,
});
export const resendCountdown = style({
width: 45,
textAlign: 'center',
});
export const resendCountdownInButton = style({
width: 40,
textAlign: 'center',

View File

@@ -150,9 +150,18 @@ export const useAuth = () => {
signInCloud('google').catch(console.error);
}, []);
const resetCountDown = useCallback(() => {
setAuthStore({
isMutating: false,
allowSendEmail: false,
resendCountDown: 0,
});
}, [setAuthStore]);
return {
allowSendEmail: authStore.allowSendEmail,
resendCountDown: authStore.resendCountDown,
resetCountDown,
isMutating: authStore.isMutating,
signUp,
signIn,