mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(ui): add SmsSignIn container (#2309)
This commit is contained in:
parent
96ade39498
commit
6242907271
28 changed files with 386 additions and 71 deletions
|
@ -75,7 +75,8 @@
|
|||
"eslintConfig": {
|
||||
"extends": "@silverhand/react",
|
||||
"rules": {
|
||||
"complexity": "off"
|
||||
"complexity": "off",
|
||||
"jsx-a11y/no-autofocus": "off"
|
||||
}
|
||||
},
|
||||
"stylelint": {
|
||||
|
|
|
@ -80,7 +80,6 @@ const PhoneInput = ({
|
|||
type="tel"
|
||||
inputMode="tel"
|
||||
autoComplete="tel-national"
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -87,7 +87,6 @@ const CreateAccount = ({ className, autoFocus }: Props) => {
|
|||
return (
|
||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||
<Input
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
className={styles.inputField}
|
||||
name="new-username"
|
||||
|
|
|
@ -74,7 +74,6 @@ const EmailForm = ({
|
|||
autoComplete="email"
|
||||
inputMode="email"
|
||||
placeholder={t('input.email')}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
className={styles.inputField}
|
||||
onChange={(event) => {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { default as EmailRegister } from './EmailRegister';
|
||||
export { default as EmailSignIn } from './EmailSignIn';
|
||||
|
|
|
@ -17,7 +17,7 @@ const useEmailRegister = () => {
|
|||
setErrorMessage('invalid_email');
|
||||
},
|
||||
}),
|
||||
[setErrorMessage]
|
||||
[]
|
||||
);
|
||||
|
||||
const clearErrorMessage = useCallback(() => {
|
||||
|
|
|
@ -21,7 +21,7 @@ const useEmailSignIn = ({ password, isPasswordPrimary, verificationCode }: Metho
|
|||
setErrorMessage('invalid_email');
|
||||
},
|
||||
}),
|
||||
[setErrorMessage]
|
||||
[]
|
||||
);
|
||||
|
||||
const clearErrorMessage = useCallback(() => {
|
||||
|
|
|
@ -84,7 +84,6 @@ const EmailPassword = ({ className, autoFocus }: Props) => {
|
|||
autoComplete="email"
|
||||
inputMode="email"
|
||||
placeholder={t('input.email')}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
className={styles.inputField}
|
||||
{...register('email', emailValidation)}
|
||||
|
|
|
@ -96,7 +96,6 @@ const EmailPasswordless = ({
|
|||
autoComplete="email"
|
||||
inputMode="email"
|
||||
placeholder={t('input.email')}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
className={styles.inputField}
|
||||
{...register('email', emailValidation)}
|
||||
|
|
|
@ -111,7 +111,6 @@ const PhonePasswordless = ({
|
|||
className={styles.inputField}
|
||||
countryCallingCode={phoneNumber.countryCallingCode}
|
||||
nationalNumber={phoneNumber.nationalNumber}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
countryList={countryList}
|
||||
{...register('phone', phoneNumberValidation)}
|
||||
|
|
|
@ -92,7 +92,6 @@ const PhoneForm = ({
|
|||
className={styles.inputField}
|
||||
countryCallingCode={phoneNumber.countryCallingCode}
|
||||
nationalNumber={phoneNumber.nationalNumber}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
countryList={countryList}
|
||||
{...register('phone', phoneNumberValidation)}
|
||||
|
|
57
packages/ui/src/containers/PhoneForm/SmsRegister.test.tsx
Normal file
57
packages/ui/src/containers/PhoneForm/SmsRegister.test.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendRegisterSmsPasscode } from '@/apis/register';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
import SmsRegister from './SmsRegister';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
// PhoneNum CountryCode detection
|
||||
jest.mock('i18next', () => ({
|
||||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/register', () => ({
|
||||
sendRegisterSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockedNavigate,
|
||||
}));
|
||||
|
||||
describe('SmsRegister', () => {
|
||||
const phone = '8573333333';
|
||||
const defaultCountryCallingCode = getDefaultCountryCallingCode();
|
||||
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
|
||||
|
||||
test('register form submit', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<SmsRegister />
|
||||
</MemoryRouter>
|
||||
);
|
||||
const phoneInput = container.querySelector('input[name="phone"]');
|
||||
|
||||
if (phoneInput) {
|
||||
fireEvent.change(phoneInput, { target: { value: phone } });
|
||||
}
|
||||
|
||||
const submitButton = getByText('action.create_account');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendRegisterSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/register/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
173
packages/ui/src/containers/PhoneForm/SmsSignIn.test.tsx
Normal file
173
packages/ui/src/containers/PhoneForm/SmsSignIn.test.tsx
Normal file
|
@ -0,0 +1,173 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||
import { sendSignInSmsPasscode } from '@/apis/sign-in';
|
||||
import { getDefaultCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
import SmsSignIn from './SmsSignIn';
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
// PhoneNum CountryCode detection
|
||||
jest.mock('i18next', () => ({
|
||||
language: 'en',
|
||||
}));
|
||||
|
||||
jest.mock('@/apis/sign-in', () => ({
|
||||
sendSignInSmsPasscode: jest.fn(() => ({ success: true })),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockedNavigate,
|
||||
}));
|
||||
|
||||
describe('SmsSignIn', () => {
|
||||
const phone = '8573333333';
|
||||
const defaultCountryCallingCode = getDefaultCountryCallingCode();
|
||||
const fullPhoneNumber = `${defaultCountryCallingCode}${phone}`;
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('SmsSignIn form with password as primary method', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<SmsSignIn
|
||||
signInMethod={{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: true,
|
||||
verificationCode: true,
|
||||
isPasswordPrimary: true,
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
const phoneInput = container.querySelector('input[name="phone"]');
|
||||
|
||||
if (phoneInput) {
|
||||
fireEvent.change(phoneInput, { target: { value: phone } });
|
||||
}
|
||||
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/password', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('SmsSignIn form with password true, primary true but verification code false', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<SmsSignIn
|
||||
signInMethod={{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: true,
|
||||
verificationCode: false,
|
||||
isPasswordPrimary: true,
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
const phoneInput = container.querySelector('input[name="phone"]');
|
||||
|
||||
if (phoneInput) {
|
||||
fireEvent.change(phoneInput, { target: { value: phone } });
|
||||
}
|
||||
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).not.toBeCalled();
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/password', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('SmsSignIn form with password true but is primary false and verification code true', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<SmsSignIn
|
||||
signInMethod={{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: true,
|
||||
verificationCode: true,
|
||||
isPasswordPrimary: false,
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const phoneInput = container.querySelector('input[name="phone"]');
|
||||
|
||||
if (phoneInput) {
|
||||
fireEvent.change(phoneInput, { target: { value: phone } });
|
||||
}
|
||||
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('SmsSignIn form with password false but primary verification code true', async () => {
|
||||
const { container, getByText } = renderWithPageContext(
|
||||
<MemoryRouter>
|
||||
<SmsSignIn
|
||||
signInMethod={{
|
||||
identifier: SignInIdentifier.Sms,
|
||||
password: false,
|
||||
verificationCode: true,
|
||||
isPasswordPrimary: true,
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const phoneInput = container.querySelector('input[name="phone"]');
|
||||
|
||||
if (phoneInput) {
|
||||
fireEvent.change(phoneInput, { target: { value: phone } });
|
||||
}
|
||||
|
||||
const submitButton = getByText('action.sign_in');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendSignInSmsPasscode).toBeCalledWith(fullPhoneNumber);
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
{ pathname: '/sign-in/sms/passcode-validation', search: '' },
|
||||
{ state: { phone: fullPhoneNumber } }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
26
packages/ui/src/containers/PhoneForm/SmsSignIn.tsx
Normal file
26
packages/ui/src/containers/PhoneForm/SmsSignIn.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import PhoneForm from './PhoneForm';
|
||||
import type { MethodProps } from './use-sms-sign-in';
|
||||
import useSmsSignIn from './use-sms-sign-in';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
// eslint-disable-next-line react/boolean-prop-naming
|
||||
autoFocus?: boolean;
|
||||
signInMethod: MethodProps;
|
||||
};
|
||||
|
||||
const SmsSignIn = ({ signInMethod, ...props }: Props) => {
|
||||
const { onSubmit, errorMessage, clearErrorMessage } = useSmsSignIn(signInMethod);
|
||||
|
||||
return (
|
||||
<PhoneForm
|
||||
onSubmit={onSubmit}
|
||||
{...props}
|
||||
submitButtonText="action.sign_in"
|
||||
errorMessage={errorMessage}
|
||||
clearErrorMessage={clearErrorMessage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsSignIn;
|
|
@ -1 +1,2 @@
|
|||
export { default as SmsRegister } from './SmsRegister';
|
||||
export { default as SmsSignIn } from './SmsSignIn';
|
||||
|
|
|
@ -17,7 +17,7 @@ const useSmsRegister = () => {
|
|||
setErrorMessage('invalid_phone');
|
||||
},
|
||||
}),
|
||||
[setErrorMessage]
|
||||
[]
|
||||
);
|
||||
|
||||
const clearErrorMessage = useCallback(() => {
|
||||
|
|
89
packages/ui/src/containers/PhoneForm/use-sms-sign-in.ts
Normal file
89
packages/ui/src/containers/PhoneForm/use-sms-sign-in.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import type { SignIn } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { sendSignInSmsPasscode } from '@/apis/sign-in';
|
||||
import type { ErrorHandlers } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import type { ArrayElement } from '@/types';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
export type MethodProps = ArrayElement<SignIn['methods']>;
|
||||
|
||||
const useEmailSignIn = ({ password, isPasswordPrimary, verificationCode }: MethodProps) => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const errorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
'guard.invalid_input': () => {
|
||||
setErrorMessage('invalid_phone');
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const clearErrorMessage = useCallback(() => {
|
||||
setErrorMessage('');
|
||||
}, []);
|
||||
|
||||
const { run: asyncSendSignInEmailPasscode } = useApi(sendSignInSmsPasscode, errorHandlers);
|
||||
|
||||
const navigateToPasswordPage = useCallback(
|
||||
(phone: string) => {
|
||||
navigate(
|
||||
{
|
||||
pathname: `/${UserFlow.signIn}/${SignInIdentifier.Sms}/password`,
|
||||
search: location.search,
|
||||
},
|
||||
{ state: { phone } }
|
||||
);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const sendPasscode = useCallback(
|
||||
async (phone: string) => {
|
||||
const result = await asyncSendSignInEmailPasscode(phone);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(
|
||||
{
|
||||
pathname: `/${UserFlow.signIn}/${SignInIdentifier.Sms}/passcode-validation`,
|
||||
search: location.search,
|
||||
},
|
||||
{ state: { phone } }
|
||||
);
|
||||
},
|
||||
[asyncSendSignInEmailPasscode, navigate]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (phone: string) => {
|
||||
// Sms Password SignIn Flow
|
||||
if (password && (isPasswordPrimary || !verificationCode)) {
|
||||
navigateToPasswordPage(phone);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Sms Passwordless SignIn Flow
|
||||
if (verificationCode) {
|
||||
await sendPasscode(phone);
|
||||
}
|
||||
},
|
||||
[isPasswordPrimary, navigateToPasswordPage, password, sendPasscode, verificationCode]
|
||||
);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
clearErrorMessage,
|
||||
onSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEmailSignIn;
|
|
@ -106,7 +106,6 @@ const PhonePassword = ({ className, autoFocus }: Props) => {
|
|||
className={styles.inputField}
|
||||
countryCallingCode={phoneNumber.countryCallingCode}
|
||||
nationalNumber={phoneNumber.nationalNumber}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
countryList={countryList}
|
||||
{...register('phone', phoneNumberValidation)}
|
||||
|
|
|
@ -62,7 +62,6 @@ const SetPassword = ({
|
|||
type="password"
|
||||
autoComplete="new-password"
|
||||
placeholder={t('input.password')}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
{...register('password', passwordValidation)}
|
||||
onClear={() => {
|
||||
|
|
|
@ -87,7 +87,6 @@ const UsernameSignIn = ({ className, autoFocus }: Props) => {
|
|||
return (
|
||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||
<Input
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
className={styles.inputField}
|
||||
name="username"
|
||||
|
|
|
@ -24,7 +24,6 @@ const ForgotPassword = () => {
|
|||
title="description.reset_password"
|
||||
description={`description.reset_password_description_${method === 'email' ? 'email' : 'sms'}`}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
|
||||
<PasswordlessForm autoFocus hasSwitch type={UserFlow.forgotPassword} hasTerms={false} />
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
|
|
|
@ -28,7 +28,6 @@ const PasswordRegisterWithUsername = () => {
|
|||
return (
|
||||
<SecondaryPageWrapper title="description.new_password">
|
||||
<SetPassword
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
onSubmit={(password) => {
|
||||
void register(state.username, password);
|
||||
|
|
|
@ -9,7 +9,6 @@ const ResetPassword = () => {
|
|||
return (
|
||||
<SecondaryPageWrapper title="description.new_password">
|
||||
<SetPassword
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
errorMessage={errorMessage}
|
||||
clearErrorMessage={clearErrorMessage}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { is } from 'superstruct';
|
||||
|
||||
|
@ -19,23 +18,6 @@ const SecondaryRegister = () => {
|
|||
const { method = '' } = useParams<Parameters>();
|
||||
const { signUpMethods, signUpSettings } = useSieMethods();
|
||||
|
||||
const registerForm = useMemo(() => {
|
||||
if (method === SignInIdentifier.Sms) {
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return <SmsRegister autoFocus />;
|
||||
}
|
||||
|
||||
if (method === SignInIdentifier.Email) {
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return <EmailRegister autoFocus />;
|
||||
}
|
||||
|
||||
if (method === SignInIdentifier.Username) {
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return <CreateAccount autoFocus />;
|
||||
}
|
||||
}, [method]);
|
||||
|
||||
// Validate the signUp method
|
||||
if (!is(method, SignInMethodGuard) || !signUpMethods.includes(method)) {
|
||||
return <ErrorPage />;
|
||||
|
@ -46,7 +28,17 @@ const SecondaryRegister = () => {
|
|||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
return <SecondaryPageWrapper title="action.create_account">{registerForm}</SecondaryPageWrapper>;
|
||||
return (
|
||||
<SecondaryPageWrapper title="action.create_account">
|
||||
{method === SignInIdentifier.Sms ? (
|
||||
<SmsRegister autoFocus />
|
||||
) : method === SignInIdentifier.Email ? (
|
||||
<EmailRegister autoFocus />
|
||||
) : (
|
||||
<CreateAccount autoFocus />
|
||||
)}
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecondaryRegister;
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('<SecondarySignIn />', () => {
|
|||
});
|
||||
|
||||
test('renders phone', async () => {
|
||||
const { queryByText, container } = renderWithPageContext(
|
||||
const { queryAllByText, container } = renderWithPageContext(
|
||||
<MemoryRouter initialEntries={['/sign-in/sms']}>
|
||||
<Routes>
|
||||
<Route
|
||||
|
@ -45,7 +45,7 @@ describe('<SecondarySignIn />', () => {
|
|||
</Routes>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(queryByText('action.sign_in')).not.toBeNull();
|
||||
expect(queryAllByText('action.sign_in')).toHaveLength(2);
|
||||
expect(container.querySelector('input[name="phone"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
import { useMemo } from 'react';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import SecondaryPageWrapper from '@/components/SecondaryPageWrapper';
|
||||
import EmailSignIn from '@/containers/EmailForm/EmailSignIn';
|
||||
import { PhonePasswordless } from '@/containers/Passwordless';
|
||||
import { EmailSignIn } from '@/containers/EmailForm';
|
||||
import { SmsSignIn } from '@/containers/PhoneForm';
|
||||
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||
import { useSieMethods } from '@/hooks/use-sie';
|
||||
import ErrorPage from '@/pages/ErrorPage';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
type Props = {
|
||||
method?: string;
|
||||
};
|
||||
|
||||
const SecondarySignIn = () => {
|
||||
const { method = 'username' } = useParams<Props>();
|
||||
const { method = '' } = useParams<Props>();
|
||||
const { signInMethods } = useSieMethods();
|
||||
const signInMethod = signInMethods.find(({ identifier }) => identifier === method);
|
||||
|
||||
const signInForm = useMemo(() => {
|
||||
if (method === 'sms') {
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return <PhonePasswordless autoFocus type={UserFlow.signIn} />;
|
||||
}
|
||||
|
||||
if (method === 'email') {
|
||||
const signInMethod = signInMethods.find(({ identifier }) => identifier === method);
|
||||
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return signInMethod && <EmailSignIn autoFocus signInMethod={signInMethod} />;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
return <UsernameSignIn autoFocus />;
|
||||
}, [method, signInMethods]);
|
||||
|
||||
if (!['email', 'sms', 'username'].includes(method)) {
|
||||
if (!signInMethod) {
|
||||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
if (!signInMethods.some(({ identifier }) => identifier === method)) {
|
||||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
return <SecondaryPageWrapper title="action.sign_in">{signInForm}</SecondaryPageWrapper>;
|
||||
return (
|
||||
<SecondaryPageWrapper title="action.sign_in">
|
||||
{signInMethod.identifier === SignInIdentifier.Sms ? (
|
||||
<SmsSignIn autoFocus signInMethod={signInMethod} />
|
||||
) : signInMethod.identifier === SignInIdentifier.Email ? (
|
||||
<EmailSignIn autoFocus signInMethod={signInMethod} />
|
||||
) : (
|
||||
<UsernameSignIn autoFocus />
|
||||
)}
|
||||
</SecondaryPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecondarySignIn;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import type { SignIn as SignInType, ConnectorMetadata } from '@logto/schemas';
|
||||
|
||||
import EmailSignIn from '@/containers/EmailForm/EmailSignIn';
|
||||
import { EmailSignIn } from '@/containers/EmailForm';
|
||||
import EmailPassword from '@/containers/EmailPassword';
|
||||
import { PhonePasswordless } from '@/containers/Passwordless';
|
||||
import { SmsSignIn } from '@/containers/PhoneForm';
|
||||
import PhonePassword from '@/containers/PhonePassword';
|
||||
import SocialSignIn from '@/containers/SocialSignIn';
|
||||
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||
import type { ArrayElement } from '@/types';
|
||||
import { UserFlow } from '@/types';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -32,7 +31,7 @@ const Main = ({ signInMethod, socialConnectors }: Props) => {
|
|||
return <PhonePassword className={styles.main} />;
|
||||
}
|
||||
|
||||
return <PhonePasswordless type={UserFlow.signIn} className={styles.main} />;
|
||||
return <SmsSignIn signInMethod={signInMethod} className={styles.main} />;
|
||||
}
|
||||
|
||||
case SignInIdentifier.Username: {
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('<SignIn />', () => {
|
|||
</SettingsProvider>
|
||||
);
|
||||
expect(container.querySelector('input[name="phone"]')).not.toBeNull();
|
||||
expect(queryByText('action.continue')).not.toBeNull();
|
||||
expect(queryByText('action.sign_in')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('renders with phone password as primary', async () => {
|
||||
|
|
Loading…
Reference in a new issue