mirror of
https://github.com/logto-io/logto.git
synced 2025-01-13 21:30:30 -05:00
parent
9a89c1a200
commit
ddb0e47950
25 changed files with 447 additions and 189 deletions
|
@ -7,7 +7,13 @@ import { appendPath } from '@/utils/url';
|
||||||
|
|
||||||
// Need To Align With UI
|
// Need To Align With UI
|
||||||
export const sessionNotFoundPath = '/unknown-session';
|
export const sessionNotFoundPath = '/unknown-session';
|
||||||
export const guardedPath = ['/sign-in', '/register', '/social-register'];
|
export const guardedPath = [
|
||||||
|
'/sign-in',
|
||||||
|
'/register',
|
||||||
|
'/social/register',
|
||||||
|
'/reset-password',
|
||||||
|
'/forgot-password',
|
||||||
|
];
|
||||||
|
|
||||||
export default function koaSpaSessionGuard<
|
export default function koaSpaSessionGuard<
|
||||||
StateT,
|
StateT,
|
||||||
|
|
|
@ -26,6 +26,7 @@ const translation = {
|
||||||
got_it: 'Got it',
|
got_it: 'Got it',
|
||||||
sign_in_with: 'Sign in with {{name}}',
|
sign_in_with: 'Sign in with {{name}}',
|
||||||
forgot_password: 'Forgot Password?',
|
forgot_password: 'Forgot Password?',
|
||||||
|
switch_to: 'Switch to {{method}}',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
|
|
@ -28,6 +28,7 @@ const translation = {
|
||||||
got_it: 'Compris',
|
got_it: 'Compris',
|
||||||
sign_in_with: 'Connexion avec {{name}}',
|
sign_in_with: 'Connexion avec {{name}}',
|
||||||
forgot_password: 'Mot de passe oublié ?',
|
forgot_password: 'Mot de passe oublié ?',
|
||||||
|
switch_to: 'Passer au {{method}}',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
|
|
@ -28,6 +28,7 @@ const translation = {
|
||||||
got_it: '알겠습니다',
|
got_it: '알겠습니다',
|
||||||
sign_in_with: '{{name}} 로그인',
|
sign_in_with: '{{name}} 로그인',
|
||||||
forgot_password: '비밀번호를 잊어버리셨나요?',
|
forgot_password: '비밀번호를 잊어버리셨나요?',
|
||||||
|
switch_to: 'Switch to {{method}}', // TODO: untranslated
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: '이메일',
|
email: '이메일',
|
||||||
|
|
|
@ -28,6 +28,7 @@ const translation = {
|
||||||
got_it: 'Entendi',
|
got_it: 'Entendi',
|
||||||
sign_in_with: 'Entrar com {{name}}',
|
sign_in_with: 'Entrar com {{name}}',
|
||||||
forgot_password: 'Esqueceu a password?',
|
forgot_password: 'Esqueceu a password?',
|
||||||
|
switch_to: 'Mudar para {{method}}',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
|
|
@ -28,6 +28,7 @@ const translation = {
|
||||||
got_it: 'Anladım',
|
got_it: 'Anladım',
|
||||||
sign_in_with: '{{name}} ile giriş yap',
|
sign_in_with: '{{name}} ile giriş yap',
|
||||||
forgot_password: 'Şifremi Unuttum?',
|
forgot_password: 'Şifremi Unuttum?',
|
||||||
|
switch_to: 'Switch to {{method}}', // TODO: not translated
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'e-posta adresi',
|
email: 'e-posta adresi',
|
||||||
|
|
|
@ -28,6 +28,7 @@ const translation = {
|
||||||
got_it: '知道了',
|
got_it: '知道了',
|
||||||
sign_in_with: '通过 {{name}} 登录',
|
sign_in_with: '通过 {{name}} 登录',
|
||||||
forgot_password: '忘记密码?',
|
forgot_password: '忘记密码?',
|
||||||
|
switch_to: '切换到{{method}}',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: '邮箱',
|
email: '邮箱',
|
||||||
|
|
|
@ -11,7 +11,17 @@
|
||||||
margin-bottom: _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(8);
|
||||||
|
}
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
margin: _.unit(8) 0 _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(body.desktop) {
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,41 +85,44 @@ const CreateAccount = ({ className, autoFocus }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<Input
|
<div className={styles.formFields}>
|
||||||
autoFocus={autoFocus}
|
<Input
|
||||||
className={styles.inputField}
|
autoFocus={autoFocus}
|
||||||
name="new-username"
|
className={styles.inputField}
|
||||||
placeholder={t('input.username')}
|
name="new-username"
|
||||||
{...fieldRegister('username', usernameValidation)}
|
placeholder={t('input.username')}
|
||||||
onClear={() => {
|
{...fieldRegister('username', usernameValidation)}
|
||||||
setFieldValue((state) => ({ ...state, username: '' }));
|
onClear={() => {
|
||||||
}}
|
setFieldValue((state) => ({ ...state, username: '' }));
|
||||||
/>
|
}}
|
||||||
<Input
|
/>
|
||||||
className={styles.inputField}
|
<Input
|
||||||
name="new-password"
|
className={styles.inputField}
|
||||||
type="password"
|
name="new-password"
|
||||||
autoComplete="new-password"
|
type="password"
|
||||||
placeholder={t('input.password')}
|
autoComplete="new-password"
|
||||||
{...fieldRegister('password', passwordValidation)}
|
placeholder={t('input.password')}
|
||||||
onClear={() => {
|
{...fieldRegister('password', passwordValidation)}
|
||||||
setFieldValue((state) => ({ ...state, password: '' }));
|
onClear={() => {
|
||||||
}}
|
setFieldValue((state) => ({ ...state, password: '' }));
|
||||||
/>
|
}}
|
||||||
<Input
|
/>
|
||||||
className={styles.inputField}
|
<Input
|
||||||
name="confirm-new-password"
|
className={styles.inputField}
|
||||||
type="password"
|
name="confirm-new-password"
|
||||||
autoComplete="new-password"
|
type="password"
|
||||||
placeholder={t('input.confirm_password')}
|
autoComplete="new-password"
|
||||||
{...fieldRegister('confirmPassword', (confirmPassword) =>
|
placeholder={t('input.confirm_password')}
|
||||||
confirmPasswordValidation(fieldValue.password, confirmPassword)
|
{...fieldRegister('confirmPassword', (confirmPassword) =>
|
||||||
)}
|
confirmPasswordValidation(fieldValue.password, confirmPassword)
|
||||||
errorStyling={false}
|
)}
|
||||||
onClear={() => {
|
errorStyling={false}
|
||||||
setFieldValue((state) => ({ ...state, confirmPassword: '' }));
|
onClear={() => {
|
||||||
}}
|
setFieldValue((state) => ({ ...state, confirmPassword: '' }));
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TermsOfUse className={styles.terms} />
|
<TermsOfUse className={styles.terms} />
|
||||||
|
|
||||||
<Button title="action.create" onClick={async () => onSubmitHandler()} />
|
<Button title="action.create" onClick={async () => onSubmitHandler()} />
|
||||||
|
|
|
@ -5,7 +5,6 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { sendRegisterEmailPasscode } from '@/apis/register';
|
import { sendRegisterEmailPasscode } from '@/apis/register';
|
||||||
import { sendSignInEmailPasscode } from '@/apis/sign-in';
|
import { sendSignInEmailPasscode } from '@/apis/sign-in';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
|
||||||
|
|
||||||
import EmailPasswordless from './EmailPasswordless';
|
import EmailPasswordless from './EmailPasswordless';
|
||||||
|
|
||||||
|
@ -31,15 +30,24 @@ describe('<EmailPasswordless/>', () => {
|
||||||
const { queryByText } = renderWithPageContext(
|
const { queryByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<EmailPasswordless type="sign-in">
|
<EmailPasswordless type="sign-in" />
|
||||||
<TermsOfUse />
|
|
||||||
</EmailPasswordless>
|
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(queryByText('description.terms_of_use')).not.toBeNull();
|
expect(queryByText('description.terms_of_use')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ender with terms settings but hasTerms param set to false', () => {
|
||||||
|
const { queryByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter>
|
||||||
|
<SettingsProvider>
|
||||||
|
<EmailPasswordless type="sign-in" hasTerms={false} />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(queryByText('description.terms_of_use')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('required email with error message', () => {
|
test('required email with error message', () => {
|
||||||
const { queryByText, container, getByText } = renderWithPageContext(
|
const { queryByText, container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
|
@ -63,14 +71,15 @@ describe('<EmailPasswordless/>', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should block in extra validation failed', async () => {
|
test('should blocked by terms validation with terms settings enabled', async () => {
|
||||||
const { container, getByText } = renderWithPageContext(
|
const { container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<EmailPasswordless type="sign-in" onSubmitValidation={async () => false} />
|
<EmailPasswordless type="sign-in" />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailInput = container.querySelector('input[name="email"]');
|
const emailInput = container.querySelector('input[name="email"]');
|
||||||
|
|
||||||
if (emailInput) {
|
if (emailInput) {
|
||||||
|
@ -88,14 +97,15 @@ describe('<EmailPasswordless/>', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call sign-in method properly', async () => {
|
test('should call sign-in method properly with terms settings enabled but hasTerms param set to false', async () => {
|
||||||
const { container, getByText } = renderWithPageContext(
|
const { container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<EmailPasswordless type="sign-in" />
|
<EmailPasswordless type="sign-in" hasTerms={false} />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailInput = container.querySelector('input[name="email"]');
|
const emailInput = container.querySelector('input[name="email"]');
|
||||||
|
|
||||||
if (emailInput) {
|
if (emailInput) {
|
||||||
|
@ -113,7 +123,35 @@ describe('<EmailPasswordless/>', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call register method properly', async () => {
|
test('should call sign-in method properly with terms settings enabled and checked', async () => {
|
||||||
|
const { container, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter>
|
||||||
|
<SettingsProvider>
|
||||||
|
<EmailPasswordless type="sign-in" />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
const emailInput = container.querySelector('input[name="email"]');
|
||||||
|
|
||||||
|
if (emailInput) {
|
||||||
|
fireEvent.change(emailInput, { target: { value: 'foo@logto.io' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const termsButton = getByText('description.agree_with_terms');
|
||||||
|
fireEvent.click(termsButton);
|
||||||
|
|
||||||
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(sendSignInEmailPasscode).toBeCalledWith('foo@logto.io');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call register method properly if type is register', async () => {
|
||||||
const { container, getByText } = renderWithPageContext(
|
const { container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
|
@ -126,6 +164,10 @@ describe('<EmailPasswordless/>', () => {
|
||||||
if (emailInput) {
|
if (emailInput) {
|
||||||
fireEvent.change(emailInput, { target: { value: 'foo@logto.io' } });
|
fireEvent.change(emailInput, { target: { value: 'foo@logto.io' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const termsButton = getByText('description.agree_with_terms');
|
||||||
|
fireEvent.click(termsButton);
|
||||||
|
|
||||||
const submitButton = getByText('action.continue');
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
|
@ -6,14 +6,17 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import { getSendPasscodeApi } from '@/apis/utils';
|
import { getSendPasscodeApi } from '@/apis/utils';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Input from '@/components/Input';
|
import Input from '@/components/Input';
|
||||||
|
import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import useApi, { ErrorHandlers } from '@/hooks/use-api';
|
import useApi, { ErrorHandlers } from '@/hooks/use-api';
|
||||||
import useForm from '@/hooks/use-form';
|
import useForm from '@/hooks/use-form';
|
||||||
import { PageContext } from '@/hooks/use-page-context';
|
import { PageContext } from '@/hooks/use-page-context';
|
||||||
|
import useTerms from '@/hooks/use-terms';
|
||||||
import { UserFlow, SearchParameters } from '@/types';
|
import { UserFlow, SearchParameters } from '@/types';
|
||||||
import { getSearchParameters } from '@/utils';
|
import { getSearchParameters } from '@/utils';
|
||||||
import { emailValidation } from '@/utils/field-validations';
|
import { emailValidation } from '@/utils/field-validations';
|
||||||
|
|
||||||
import PasswordlessConfirmModal from './PasswordlessConfirmModal';
|
import PasswordlessConfirmModal from './PasswordlessConfirmModal';
|
||||||
|
import PasswordlessSwitch from './PasswordlessSwitch';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -21,8 +24,8 @@ type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
// eslint-disable-next-line react/boolean-prop-naming
|
// eslint-disable-next-line react/boolean-prop-naming
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
onSubmitValidation?: () => Promise<boolean>;
|
hasTerms?: boolean;
|
||||||
children?: React.ReactNode;
|
hasSwitch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldState = {
|
type FieldState = {
|
||||||
|
@ -31,10 +34,18 @@ type FieldState = {
|
||||||
|
|
||||||
const defaultState: FieldState = { email: '' };
|
const defaultState: FieldState = { email: '' };
|
||||||
|
|
||||||
const EmailPasswordless = ({ type, autoFocus, onSubmitValidation, children, className }: Props) => {
|
const EmailPasswordless = ({
|
||||||
|
type,
|
||||||
|
autoFocus,
|
||||||
|
hasTerms = true,
|
||||||
|
hasSwitch = false,
|
||||||
|
className,
|
||||||
|
}: Props) => {
|
||||||
const { setToast } = useContext(PageContext);
|
const { setToast } = useContext(PageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { termsValidation } = useTerms();
|
||||||
const { fieldValue, setFieldValue, setFieldErrors, register, validateForm } =
|
const { fieldValue, setFieldValue, setFieldErrors, register, validateForm } =
|
||||||
useForm(defaultState);
|
useForm(defaultState);
|
||||||
|
|
||||||
|
@ -75,13 +86,13 @@ const EmailPasswordless = ({ type, autoFocus, onSubmitValidation, children, clas
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onSubmitValidation && !(await onSubmitValidation())) {
|
if (hasTerms && !(await termsValidation())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncSendPasscode(fieldValue.email);
|
void asyncSendPasscode(fieldValue.email);
|
||||||
},
|
},
|
||||||
[validateForm, onSubmitValidation, asyncSendPasscode, fieldValue.email]
|
[validateForm, hasTerms, termsValidation, asyncSendPasscode, fieldValue.email]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onModalCloseHandler = useCallback(() => {
|
const onModalCloseHandler = useCallback(() => {
|
||||||
|
@ -103,22 +114,24 @@ const EmailPasswordless = ({ type, autoFocus, onSubmitValidation, children, clas
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<Input
|
<div className={styles.formFields}>
|
||||||
type="email"
|
<Input
|
||||||
name="email"
|
type="email"
|
||||||
autoComplete="email"
|
name="email"
|
||||||
inputMode="email"
|
autoComplete="email"
|
||||||
placeholder={t('input.email')}
|
inputMode="email"
|
||||||
autoFocus={autoFocus}
|
placeholder={t('input.email')}
|
||||||
className={styles.inputField}
|
autoFocus={autoFocus}
|
||||||
{...register('email', emailValidation)}
|
className={styles.inputField}
|
||||||
onClear={() => {
|
{...register('email', emailValidation)}
|
||||||
setFieldValue((state) => ({ ...state, email: '' }));
|
onClear={() => {
|
||||||
}}
|
setFieldValue((state) => ({ ...state, email: '' }));
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
{children && <div className={styles.childWrapper}>{children}</div>}
|
{hasSwitch && <PasswordlessSwitch target="sms" className={styles.switch} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasTerms && <TermsOfUse className={styles.terms} />}
|
||||||
<Button title="action.continue" onClick={async () => onSubmitHandler()} />
|
<Button title="action.continue" onClick={async () => onSubmitHandler()} />
|
||||||
|
|
||||||
<input hidden type="submit" />
|
<input hidden type="submit" />
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { fireEvent } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
|
import { mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||||
|
|
||||||
|
import PasswordlessSwitch from './PasswordlessSwitch';
|
||||||
|
|
||||||
|
const mockedNavigate = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useNavigate: () => mockedNavigate,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('<PasswordlessSwitch />', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mockedNavigate.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render sms passwordless switch', () => {
|
||||||
|
const { queryByText, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter initialEntries={['/forgot-password/sms']}>
|
||||||
|
<SettingsProvider>
|
||||||
|
<PasswordlessSwitch target="email" />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryByText('action.switch_to')).not.toBeNull();
|
||||||
|
|
||||||
|
const link = getByText('action.switch_to');
|
||||||
|
fireEvent.click(link);
|
||||||
|
|
||||||
|
expect(mockedNavigate).toBeCalledWith({ pathname: '/forgot-password/email' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render email passwordless switch', () => {
|
||||||
|
const { queryByText, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter initialEntries={['/forgot-password/email']}>
|
||||||
|
<SettingsProvider>
|
||||||
|
<PasswordlessSwitch target="sms" />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryByText('action.switch_to')).not.toBeNull();
|
||||||
|
|
||||||
|
const link = getByText('action.switch_to');
|
||||||
|
fireEvent.click(link);
|
||||||
|
|
||||||
|
expect(mockedNavigate).toBeCalledWith({ pathname: '/forgot-password/sms' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not render the switch if SIE setting does not has the supported sign in method', () => {
|
||||||
|
const { queryByText, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter initialEntries={['/forgot-password/email']}>
|
||||||
|
<SettingsProvider
|
||||||
|
settings={{
|
||||||
|
...mockSignInExperienceSettings,
|
||||||
|
primarySignInMethod: 'username',
|
||||||
|
secondarySignInMethods: ['email', 'social'],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PasswordlessSwitch target="sms" />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryByText('action.switch_to')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import TextLink from '@/components/TextLink';
|
||||||
|
import { PageContext } from '@/hooks/use-page-context';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
target: 'sms' | 'email';
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PasswordlessSwitch = ({ target, className }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { experienceSettings } = useContext(PageContext);
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (!experienceSettings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
experienceSettings.primarySignInMethod !== target &&
|
||||||
|
!experienceSettings.secondarySignInMethods.includes(target)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPathname = pathname.replace(target === 'email' ? 'sms' : 'email', target);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextLink
|
||||||
|
className={className}
|
||||||
|
onClick={() => {
|
||||||
|
navigate({
|
||||||
|
pathname: targetPathname,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('action.switch_to', {
|
||||||
|
method: t(`description.${target === 'email' ? 'email' : 'phone_number'}`),
|
||||||
|
})}
|
||||||
|
</TextLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PasswordlessSwitch;
|
|
@ -5,7 +5,6 @@ import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { sendRegisterSmsPasscode } from '@/apis/register';
|
import { sendRegisterSmsPasscode } from '@/apis/register';
|
||||||
import { sendSignInSmsPasscode } from '@/apis/sign-in';
|
import { sendSignInSmsPasscode } from '@/apis/sign-in';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
|
||||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||||
|
|
||||||
import PhonePasswordless from './PhonePasswordless';
|
import PhonePasswordless from './PhonePasswordless';
|
||||||
|
@ -38,15 +37,24 @@ describe('<PhonePasswordless/>', () => {
|
||||||
const { queryByText } = renderWithPageContext(
|
const { queryByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<PhonePasswordless type="sign-in">
|
<PhonePasswordless type="sign-in" />
|
||||||
<TermsOfUse />
|
|
||||||
</PhonePasswordless>
|
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(queryByText('description.terms_of_use')).not.toBeNull();
|
expect(queryByText('description.terms_of_use')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('render with terms settings but hasTerms param set to false', () => {
|
||||||
|
const { queryByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter>
|
||||||
|
<SettingsProvider>
|
||||||
|
<PhonePasswordless type="sign-in" hasTerms={false} />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(queryByText('description.terms_of_use')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('required phone with error message', () => {
|
test('required phone with error message', () => {
|
||||||
const { queryByText, container, getByText } = renderWithPageContext(
|
const { queryByText, container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
|
@ -70,31 +78,7 @@ describe('<PhonePasswordless/>', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should block if extra validation failed', async () => {
|
test('should blocked by terms validation with terms settings enabled', async () => {
|
||||||
const { container, getByText } = renderWithPageContext(
|
|
||||||
<MemoryRouter>
|
|
||||||
<SettingsProvider>
|
|
||||||
<PhonePasswordless type="sign-in" onSubmitValidation={async () => false} />
|
|
||||||
</SettingsProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
const phoneInput = container.querySelector('input[name="phone"]');
|
|
||||||
|
|
||||||
if (phoneInput) {
|
|
||||||
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
|
||||||
}
|
|
||||||
const submitButton = getByText('action.continue');
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(sendSignInSmsPasscode).not.toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should call sign-in method properly', async () => {
|
|
||||||
const { container, getByText } = renderWithPageContext(
|
const { container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
|
@ -107,6 +91,32 @@ describe('<PhonePasswordless/>', () => {
|
||||||
if (phoneInput) {
|
if (phoneInput) {
|
||||||
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(sendSignInSmsPasscode).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call sign-in method properly with terms settings enabled but hasTerms param set to false', async () => {
|
||||||
|
const { container, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter>
|
||||||
|
<SettingsProvider>
|
||||||
|
<PhonePasswordless type="sign-in" hasTerms={false} />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
const phoneInput = container.querySelector('input[name="phone"]');
|
||||||
|
|
||||||
|
if (phoneInput) {
|
||||||
|
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
||||||
|
}
|
||||||
|
|
||||||
const submitButton = getByText('action.continue');
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -118,7 +128,35 @@ describe('<PhonePasswordless/>', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should call register method properly', async () => {
|
test('should call sign-in method properly with terms settings enabled and checked', async () => {
|
||||||
|
const { container, getByText } = renderWithPageContext(
|
||||||
|
<MemoryRouter>
|
||||||
|
<SettingsProvider>
|
||||||
|
<PhonePasswordless type="sign-in" />
|
||||||
|
</SettingsProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
const phoneInput = container.querySelector('input[name="phone"]');
|
||||||
|
|
||||||
|
if (phoneInput) {
|
||||||
|
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const termsButton = getByText('description.agree_with_terms');
|
||||||
|
fireEvent.click(termsButton);
|
||||||
|
|
||||||
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(sendSignInSmsPasscode).toBeCalledWith(`${defaultCountryCallingCode}${phoneNumber}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call register method properly if type is register', async () => {
|
||||||
const { container, getByText } = renderWithPageContext(
|
const { container, getByText } = renderWithPageContext(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
|
@ -132,6 +170,9 @@ describe('<PhonePasswordless/>', () => {
|
||||||
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
fireEvent.change(phoneInput, { target: { value: phoneNumber } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const termsButton = getByText('description.agree_with_terms');
|
||||||
|
fireEvent.click(termsButton);
|
||||||
|
|
||||||
const submitButton = getByText('action.continue');
|
const submitButton = getByText('action.continue');
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
|
@ -6,14 +6,17 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import { getSendPasscodeApi } from '@/apis/utils';
|
import { getSendPasscodeApi } from '@/apis/utils';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import { PhoneInput } from '@/components/Input';
|
import { PhoneInput } from '@/components/Input';
|
||||||
|
import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import useApi, { ErrorHandlers } from '@/hooks/use-api';
|
import useApi, { ErrorHandlers } from '@/hooks/use-api';
|
||||||
import useForm from '@/hooks/use-form';
|
import useForm from '@/hooks/use-form';
|
||||||
import { PageContext } from '@/hooks/use-page-context';
|
import { PageContext } from '@/hooks/use-page-context';
|
||||||
import usePhoneNumber from '@/hooks/use-phone-number';
|
import usePhoneNumber from '@/hooks/use-phone-number';
|
||||||
|
import useTerms from '@/hooks/use-terms';
|
||||||
import { UserFlow, SearchParameters } from '@/types';
|
import { UserFlow, SearchParameters } from '@/types';
|
||||||
import { getSearchParameters } from '@/utils';
|
import { getSearchParameters } from '@/utils';
|
||||||
|
|
||||||
import PasswordlessConfirmModal from './PasswordlessConfirmModal';
|
import PasswordlessConfirmModal from './PasswordlessConfirmModal';
|
||||||
|
import PasswordlessSwitch from './PasswordlessSwitch';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -21,8 +24,8 @@ type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
// eslint-disable-next-line react/boolean-prop-naming
|
// eslint-disable-next-line react/boolean-prop-naming
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
onSubmitValidation?: () => Promise<boolean>;
|
hasTerms?: boolean;
|
||||||
children?: React.ReactNode;
|
hasSwitch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldState = {
|
type FieldState = {
|
||||||
|
@ -31,9 +34,17 @@ type FieldState = {
|
||||||
|
|
||||||
const defaultState: FieldState = { phone: '' };
|
const defaultState: FieldState = { phone: '' };
|
||||||
|
|
||||||
const PhonePasswordless = ({ type, autoFocus, onSubmitValidation, children, className }: Props) => {
|
const PhonePasswordless = ({
|
||||||
|
type,
|
||||||
|
autoFocus,
|
||||||
|
hasTerms = true,
|
||||||
|
hasSwitch = false,
|
||||||
|
className,
|
||||||
|
}: Props) => {
|
||||||
const { setToast } = useContext(PageContext);
|
const { setToast } = useContext(PageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { termsValidation } = useTerms();
|
||||||
const { countryList, phoneNumber, setPhoneNumber, isValidPhoneNumber } = usePhoneNumber();
|
const { countryList, phoneNumber, setPhoneNumber, isValidPhoneNumber } = usePhoneNumber();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { fieldValue, setFieldValue, setFieldErrors, validateForm, register } =
|
const { fieldValue, setFieldValue, setFieldErrors, validateForm, register } =
|
||||||
|
@ -85,13 +96,13 @@ const PhonePasswordless = ({ type, autoFocus, onSubmitValidation, children, clas
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onSubmitValidation && !(await onSubmitValidation())) {
|
if (hasTerms && !(await termsValidation())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncSendPasscode(fieldValue.phone);
|
void asyncSendPasscode(fieldValue.phone);
|
||||||
},
|
},
|
||||||
[validateForm, onSubmitValidation, asyncSendPasscode, fieldValue.phone]
|
[validateForm, hasTerms, termsValidation, asyncSendPasscode, fieldValue.phone]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onModalCloseHandler = useCallback(() => {
|
const onModalCloseHandler = useCallback(() => {
|
||||||
|
@ -118,20 +129,24 @@ const PhonePasswordless = ({ type, autoFocus, onSubmitValidation, children, clas
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<PhoneInput
|
<div className={styles.formFields}>
|
||||||
name="phone"
|
<PhoneInput
|
||||||
placeholder={t('input.phone_number')}
|
name="phone"
|
||||||
className={styles.inputField}
|
placeholder={t('input.phone_number')}
|
||||||
countryCallingCode={phoneNumber.countryCallingCode}
|
className={styles.inputField}
|
||||||
nationalNumber={phoneNumber.nationalNumber}
|
countryCallingCode={phoneNumber.countryCallingCode}
|
||||||
autoFocus={autoFocus}
|
nationalNumber={phoneNumber.nationalNumber}
|
||||||
countryList={countryList}
|
autoFocus={autoFocus}
|
||||||
{...register('phone', phoneNumberValidation)}
|
countryList={countryList}
|
||||||
onChange={(data) => {
|
{...register('phone', phoneNumberValidation)}
|
||||||
setPhoneNumber((previous) => ({ ...previous, ...data }));
|
onChange={(data) => {
|
||||||
}}
|
setPhoneNumber((previous) => ({ ...previous, ...data }));
|
||||||
/>
|
}}
|
||||||
{children && <div className={styles.childWrapper}>{children}</div>}
|
/>
|
||||||
|
{hasSwitch && <PasswordlessSwitch target="email" className={styles.switch} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasTerms && <TermsOfUse className={styles.terms} />}
|
||||||
|
|
||||||
<Button title="action.continue" onClick={async () => onSubmitHandler()} />
|
<Button title="action.continue" onClick={async () => onSubmitHandler()} />
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,24 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputField {
|
.inputField,
|
||||||
margin-bottom: _.unit(12);
|
.terms,
|
||||||
}
|
.switch {
|
||||||
|
|
||||||
.childWrapper {
|
|
||||||
margin-bottom: _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
margin-top: _.unit(-1);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(body.desktop) {
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.terms {
|
||||||
|
margin-bottom: _.unit(4);
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import useNativeMessageListener from '@/hooks/use-native-message-listener';
|
import useNativeMessageListener from '@/hooks/use-native-message-listener';
|
||||||
import useSocial from '@/hooks/use-social';
|
import useSocial from '@/hooks/use-social';
|
||||||
|
|
||||||
import SocialSignInList from '../SocialSignInList';
|
import SocialSignInList from '../SocialSignInList';
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
export const defaultSize = 3;
|
export const defaultSize = 3;
|
||||||
|
|
||||||
|
@ -13,7 +15,12 @@ const PrimarySocialSignIn = ({ className }: Props) => {
|
||||||
const { socialConnectors } = useSocial();
|
const { socialConnectors } = useSocial();
|
||||||
useNativeMessageListener();
|
useNativeMessageListener();
|
||||||
|
|
||||||
return <SocialSignInList className={className} socialConnectors={socialConnectors} />;
|
return (
|
||||||
|
<>
|
||||||
|
<TermsOfUse className={styles.terms} />
|
||||||
|
<SocialSignInList className={className} socialConnectors={socialConnectors} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrimarySocialSignIn;
|
export default PrimarySocialSignIn;
|
||||||
|
|
|
@ -11,16 +11,22 @@
|
||||||
margin-bottom: _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(8);
|
||||||
|
}
|
||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
margin: _.unit(8) 0 _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.formErrors {
|
.formErrors {
|
||||||
margin-top: _.unit(-2);
|
margin-top: _.unit(-2);
|
||||||
margin-bottom: _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
|
}
|
||||||
+ .terms {
|
}
|
||||||
margin-top: _.unit(4);
|
|
||||||
}
|
:global(body.desktop) {
|
||||||
|
.formFields {
|
||||||
|
margin-bottom: _.unit(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,27 +91,30 @@ const UsernameSignIn = ({ className, autoFocus }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<Input
|
<div className={styles.formFields}>
|
||||||
autoFocus={autoFocus}
|
<Input
|
||||||
className={styles.inputField}
|
autoFocus={autoFocus}
|
||||||
name="username"
|
className={styles.inputField}
|
||||||
autoComplete="username"
|
name="username"
|
||||||
placeholder={t('input.username')}
|
autoComplete="username"
|
||||||
{...register('username', (value) => requiredValidation('username', value))}
|
placeholder={t('input.username')}
|
||||||
onClear={() => {
|
{...register('username', (value) => requiredValidation('username', value))}
|
||||||
setFieldValue((state) => ({ ...state, username: '' }));
|
onClear={() => {
|
||||||
}}
|
setFieldValue((state) => ({ ...state, username: '' }));
|
||||||
/>
|
}}
|
||||||
<PasswordInput
|
/>
|
||||||
className={styles.inputField}
|
<PasswordInput
|
||||||
name="password"
|
className={styles.inputField}
|
||||||
autoComplete="current-password"
|
name="password"
|
||||||
placeholder={t('input.password')}
|
autoComplete="current-password"
|
||||||
{...register('password', (value) => requiredValidation('password', value))}
|
placeholder={t('input.password')}
|
||||||
/>
|
{...register('password', (value) => requiredValidation('password', value))}
|
||||||
{formErrorMessage && (
|
/>
|
||||||
<ErrorMessage className={styles.formErrors}>{formErrorMessage}</ErrorMessage>
|
{formErrorMessage && (
|
||||||
)}
|
<ErrorMessage className={styles.formErrors}>{formErrorMessage}</ErrorMessage>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<TermsOfUse className={styles.terms} />
|
<TermsOfUse className={styles.terms} />
|
||||||
|
|
||||||
<Button title="action.sign_in" onClick={async () => onSubmitHandler()} />
|
<Button title="action.sign_in" onClick={async () => onSubmitHandler()} />
|
||||||
|
|
|
@ -14,16 +14,15 @@ type Props = {
|
||||||
|
|
||||||
const ForgotPassword = () => {
|
const ForgotPassword = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { method = '' } = useParams<Props>();
|
const { method = '' } = useParams<Props>();
|
||||||
|
|
||||||
const forgotPasswordForm = useMemo(() => {
|
const forgotPasswordForm = useMemo(() => {
|
||||||
if (method === 'sms') {
|
if (method === 'sms') {
|
||||||
return <PhonePasswordless autoFocus type="reset-password" />;
|
return <PhonePasswordless autoFocus hasSwitch type="reset-password" hasTerms={false} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method === 'email') {
|
if (method === 'email') {
|
||||||
return <EmailPasswordless autoFocus type="reset-password" />;
|
return <EmailPasswordless autoFocus hasSwitch type="reset-password" hasTerms={false} />;
|
||||||
}
|
}
|
||||||
}, [method]);
|
}, [method]);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { useParams } from 'react-router-dom';
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
import CreateAccount from '@/containers/CreateAccount';
|
import CreateAccount from '@/containers/CreateAccount';
|
||||||
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
|
||||||
import useTerms from '@/hooks/use-terms';
|
|
||||||
import ErrorPage from '@/pages/ErrorPage';
|
import ErrorPage from '@/pages/ErrorPage';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -19,27 +17,17 @@ const Register = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { method = 'username' } = useParams<Parameters>();
|
const { method = 'username' } = useParams<Parameters>();
|
||||||
|
|
||||||
const { termsValidation } = useTerms();
|
|
||||||
|
|
||||||
const registerForm = useMemo(() => {
|
const registerForm = useMemo(() => {
|
||||||
if (method === 'sms') {
|
if (method === 'sms') {
|
||||||
return (
|
return <PhonePasswordless autoFocus type="register" />;
|
||||||
<PhonePasswordless autoFocus type="register" onSubmitValidation={termsValidation}>
|
|
||||||
<TermsOfUse />
|
|
||||||
</PhonePasswordless>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method === 'email') {
|
if (method === 'email') {
|
||||||
return (
|
return <EmailPasswordless autoFocus type="register" />;
|
||||||
<EmailPasswordless autoFocus type="register" onSubmitValidation={termsValidation}>
|
|
||||||
<TermsOfUse />
|
|
||||||
</EmailPasswordless>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CreateAccount autoFocus />;
|
return <CreateAccount autoFocus />;
|
||||||
}, [method, termsValidation]);
|
}, [method]);
|
||||||
|
|
||||||
if (!['email', 'sms', 'username'].includes(method)) {
|
if (!['email', 'sms', 'username'].includes(method)) {
|
||||||
return <ErrorPage />;
|
return <ErrorPage />;
|
||||||
|
|
|
@ -4,9 +4,7 @@ import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
|
||||||
import UsernameSignIn from '@/containers/UsernameSignIn';
|
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||||
import useTerms from '@/hooks/use-terms';
|
|
||||||
import ErrorPage from '@/pages/ErrorPage';
|
import ErrorPage from '@/pages/ErrorPage';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -19,27 +17,17 @@ const SecondarySignIn = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { method = 'username' } = useParams<Props>();
|
const { method = 'username' } = useParams<Props>();
|
||||||
|
|
||||||
const { termsValidation } = useTerms();
|
|
||||||
|
|
||||||
const signInForm = useMemo(() => {
|
const signInForm = useMemo(() => {
|
||||||
if (method === 'sms') {
|
if (method === 'sms') {
|
||||||
return (
|
return <PhonePasswordless autoFocus type="sign-in" />;
|
||||||
<PhonePasswordless autoFocus type="sign-in" onSubmitValidation={termsValidation}>
|
|
||||||
<TermsOfUse />
|
|
||||||
</PhonePasswordless>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method === 'email') {
|
if (method === 'email') {
|
||||||
return (
|
return <EmailPasswordless autoFocus type="sign-in" />;
|
||||||
<EmailPasswordless autoFocus type="sign-in" onSubmitValidation={termsValidation}>
|
|
||||||
<TermsOfUse />
|
|
||||||
</EmailPasswordless>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <UsernameSignIn autoFocus />;
|
return <UsernameSignIn autoFocus />;
|
||||||
}, [method, termsValidation]);
|
}, [method]);
|
||||||
|
|
||||||
if (!['email', 'sms', 'username'].includes(method)) {
|
if (!['email', 'sms', 'username'].includes(method)) {
|
||||||
return <ErrorPage />;
|
return <ErrorPage />;
|
||||||
|
|
|
@ -5,10 +5,6 @@
|
||||||
@include _.flex-column(normal, normal);
|
@include _.flex-column(normal, normal);
|
||||||
@include _.full-width;
|
@include _.full-width;
|
||||||
|
|
||||||
.terms {
|
|
||||||
margin-bottom: _.unit(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.primarySignIn {
|
.primarySignIn {
|
||||||
margin-bottom: _.unit(5);
|
margin-bottom: _.unit(5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import CreateAccount from '@/containers/CreateAccount';
|
||||||
import { EmailPasswordless, PhonePasswordless } from '@/containers/Passwordless';
|
import { EmailPasswordless, PhonePasswordless } from '@/containers/Passwordless';
|
||||||
import SignInMethodsLink from '@/containers/SignInMethodsLink';
|
import SignInMethodsLink from '@/containers/SignInMethodsLink';
|
||||||
import { PrimarySocialSignIn, SecondarySocialSignIn } from '@/containers/SocialSignIn';
|
import { PrimarySocialSignIn, SecondarySocialSignIn } from '@/containers/SocialSignIn';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
|
||||||
import UsernameSignIn from '@/containers/UsernameSignIn';
|
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||||
import { SignInMethod, LocalSignInMethod } from '@/types';
|
import { SignInMethod, LocalSignInMethod } from '@/types';
|
||||||
|
|
||||||
|
@ -44,10 +43,7 @@ export const PrimarySection = ({
|
||||||
);
|
);
|
||||||
case 'social':
|
case 'social':
|
||||||
return socialConnectors.length > 0 ? (
|
return socialConnectors.length > 0 ? (
|
||||||
<>
|
<PrimarySocialSignIn className={styles.primarySocial} />
|
||||||
<TermsOfUse className={styles.terms} />
|
|
||||||
<PrimarySocialSignIn className={styles.primarySocial} />
|
|
||||||
</>
|
|
||||||
) : null;
|
) : null;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Add table
Reference in a new issue