mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(ui): simplify the usage of inputField error message (#3081)
This commit is contained in:
parent
2b7da7b228
commit
4f4c444442
14 changed files with 64 additions and 107 deletions
|
@ -31,7 +31,7 @@ describe('InputField Component', () => {
|
||||||
|
|
||||||
test('render error message', () => {
|
test('render error message', () => {
|
||||||
const errorCode = 'invalid_email';
|
const errorCode = 'invalid_email';
|
||||||
const { queryByText } = render(<InputField error={errorCode} />);
|
const { queryByText } = render(<InputField errorMessage={errorCode} />);
|
||||||
expect(queryByText(errorCode)).not.toBeNull();
|
expect(queryByText(errorCode)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@ import classNames from 'classnames';
|
||||||
import type { ForwardedRef, HTMLProps, ReactElement } from 'react';
|
import type { ForwardedRef, HTMLProps, ReactElement } from 'react';
|
||||||
import { forwardRef, cloneElement } from 'react';
|
import { forwardRef, cloneElement } from 'react';
|
||||||
|
|
||||||
import type { ErrorType } from '@/components/ErrorMessage';
|
|
||||||
import ErrorMessage from '@/components/ErrorMessage';
|
import ErrorMessage from '@/components/ErrorMessage';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
export type Props = Omit<HTMLProps<HTMLInputElement>, 'prefix'> & {
|
export type Props = Omit<HTMLProps<HTMLInputElement>, 'prefix'> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
error?: ErrorType;
|
errorMessage?: string;
|
||||||
isDanger?: boolean;
|
isDanger?: boolean;
|
||||||
prefix?: ReactElement;
|
prefix?: ReactElement;
|
||||||
isPrefixVisible?: boolean;
|
isPrefixVisible?: boolean;
|
||||||
|
@ -21,7 +20,7 @@ export type Props = Omit<HTMLProps<HTMLInputElement>, 'prefix'> & {
|
||||||
const InputField = (
|
const InputField = (
|
||||||
{
|
{
|
||||||
className,
|
className,
|
||||||
error,
|
errorMessage,
|
||||||
isDanger,
|
isDanger,
|
||||||
prefix,
|
prefix,
|
||||||
suffix,
|
suffix,
|
||||||
|
@ -49,7 +48,7 @@ const InputField = (
|
||||||
className: classNames([suffix.props.className, styles.suffix]),
|
className: classNames([suffix.props.className, styles.suffix]),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{error && <ErrorMessage error={error} className={styles.errorMessage} />}
|
{errorMessage && <ErrorMessage className={styles.errorMessage}>{errorMessage}</ErrorMessage>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe('Input Field UI Component', () => {
|
||||||
|
|
||||||
test('render error message', () => {
|
test('render error message', () => {
|
||||||
const errorCode = 'password_required';
|
const errorCode = 'password_required';
|
||||||
const { queryByText } = render(<PasswordInputField error={errorCode} />);
|
const { queryByText } = render(<PasswordInputField errorMessage={errorCode} />);
|
||||||
expect(queryByText(errorCode)).not.toBeNull();
|
expect(queryByText(errorCode)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { useImperativeHandle, useRef, forwardRef } from 'react';
|
||||||
|
|
||||||
import ClearIcon from '@/assets/icons/clear-icon.svg';
|
import ClearIcon from '@/assets/icons/clear-icon.svg';
|
||||||
import IconButton from '@/components/Button/IconButton';
|
import IconButton from '@/components/Button/IconButton';
|
||||||
import type { ErrorType } from '@/components/ErrorMessage';
|
|
||||||
|
|
||||||
import InputField from '../InputField';
|
import InputField from '../InputField';
|
||||||
import AnimatedPrefix from './AnimatedPrefix';
|
import AnimatedPrefix from './AnimatedPrefix';
|
||||||
|
@ -19,7 +18,7 @@ export type { IdentifierInputType, EnabledIdentifierTypes } from './use-smart-in
|
||||||
|
|
||||||
type Props = Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'prefix'> & {
|
type Props = Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'prefix'> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
error?: ErrorType;
|
errorMessage?: string;
|
||||||
isDanger?: boolean;
|
isDanger?: boolean;
|
||||||
|
|
||||||
enabledTypes?: EnabledIdentifierTypes;
|
enabledTypes?: EnabledIdentifierTypes;
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe('<SetPassword />', () => {
|
||||||
expect(clearError).toBeCalled();
|
expect(clearError).toBeCalled();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('password_required')).not.toBeNull();
|
expect(queryByText('error.password_required')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(submit).not.toBeCalled();
|
expect(submit).not.toBeCalled();
|
||||||
|
@ -54,7 +54,7 @@ describe('<SetPassword />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('password_min_length')).not.toBeNull();
|
expect(queryByText('error.password_min_length')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(submit).not.toBeCalled();
|
expect(submit).not.toBeCalled();
|
||||||
|
@ -67,7 +67,7 @@ describe('<SetPassword />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('password_min_length')).toBeNull();
|
expect(queryByText('error.password_min_length')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ describe('<SetPassword />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('passwords_do_not_match')).not.toBeNull();
|
expect(queryByText('error.passwords_do_not_match')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(submit).not.toBeCalled();
|
expect(submit).not.toBeCalled();
|
||||||
|
@ -103,7 +103,7 @@ describe('<SetPassword />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('passwords_do_not_match')).toBeNull();
|
expect(queryByText('error.passwords_do_not_match')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ describe('<SetPassword />', () => {
|
||||||
fireEvent.submit(submitButton);
|
fireEvent.submit(submitButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(queryByText('passwords_do_not_match')).toBeNull();
|
expect(queryByText('error.passwords_do_not_match')).toBeNull();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(submit).toBeCalledWith('123456');
|
expect(submit).toBeCalledWith('123456');
|
||||||
|
|
|
@ -8,7 +8,6 @@ import Button from '@/components/Button';
|
||||||
import IconButton from '@/components/Button/IconButton';
|
import IconButton from '@/components/Button/IconButton';
|
||||||
import ErrorMessage from '@/components/ErrorMessage';
|
import ErrorMessage from '@/components/ErrorMessage';
|
||||||
import { InputField } from '@/components/InputFields';
|
import { InputField } from '@/components/InputFields';
|
||||||
import { passwordErrorWatcher } from '@/utils/form';
|
|
||||||
|
|
||||||
import TogglePassword from './TogglePassword';
|
import TogglePassword from './TogglePassword';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -61,21 +60,24 @@ const SetPassword = ({
|
||||||
[clearErrorMessage, handleSubmit, onSubmit]
|
[clearErrorMessage, handleSubmit, onSubmit]
|
||||||
);
|
);
|
||||||
|
|
||||||
const newPasswordError = passwordErrorWatcher(errors.newPassword);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<InputField
|
<InputField
|
||||||
required
|
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
placeholder={t('input.password')}
|
placeholder={t('input.password')}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
isDanger={!!newPasswordError}
|
isDanger={!!errors.newPassword}
|
||||||
error={newPasswordError}
|
errorMessage={errors.newPassword?.message}
|
||||||
aria-invalid={!!newPasswordError}
|
aria-invalid={!!errors.newPassword}
|
||||||
{...register('newPassword', { required: true, minLength: 6 })}
|
{...register('newPassword', {
|
||||||
|
required: t('error.password_required'),
|
||||||
|
minLength: {
|
||||||
|
value: 6,
|
||||||
|
message: t('error.password_min_length', { length: 6 }),
|
||||||
|
},
|
||||||
|
})}
|
||||||
isSuffixFocusVisible={!!watch('newPassword')}
|
isSuffixFocusVisible={!!watch('newPassword')}
|
||||||
suffix={
|
suffix={
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -89,15 +91,14 @@ const SetPassword = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputField
|
<InputField
|
||||||
required
|
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
placeholder={t('input.confirm_password')}
|
placeholder={t('input.confirm_password')}
|
||||||
error={errors.confirmPassword && 'passwords_do_not_match'}
|
errorMessage={errors.confirmPassword?.message}
|
||||||
aria-invalid={!!errors.confirmPassword}
|
aria-invalid={!!errors.confirmPassword}
|
||||||
{...register('confirmPassword', {
|
{...register('confirmPassword', {
|
||||||
validate: (value) => value === watch('newPassword'),
|
validate: (value) => value === watch('newPassword') || t('error.passwords_do_not_match'),
|
||||||
})}
|
})}
|
||||||
isSuffixFocusVisible={!!watch('confirmPassword')}
|
isSuffixFocusVisible={!!watch('confirmPassword')}
|
||||||
suffix={
|
suffix={
|
||||||
|
|
|
@ -76,8 +76,6 @@ const useSocialSignInListener = (connectorId?: string) => {
|
||||||
|
|
||||||
const signInWithSocialHandler = useCallback(
|
const signInWithSocialHandler = useCallback(
|
||||||
async (connectorId: string, data: Record<string, unknown>) => {
|
async (connectorId: string, data: Record<string, unknown>) => {
|
||||||
console.log('triggered');
|
|
||||||
|
|
||||||
const [error, result] = await asyncSignInWithSocial({
|
const [error, result] = await asyncSignInWithSocial({
|
||||||
connectorId,
|
connectorId,
|
||||||
connectorData: {
|
connectorData: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import IdentifierSignInForm from './index';
|
||||||
jest.mock('i18next', () => ({
|
jest.mock('i18next', () => ({
|
||||||
...jest.requireActual('i18next'),
|
...jest.requireActual('i18next'),
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
t: (key: string) => key,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockedNavigate = jest.fn();
|
const mockedNavigate = jest.fn();
|
||||||
|
@ -54,7 +55,7 @@ describe('IdentifierSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getByText('general_required')).not.toBeNull();
|
expect(getByText('error.general_required')).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ describe('IdentifierSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getByText('general_invalid')).not.toBeNull();
|
expect(getByText('error.general_invalid')).not.toBeNull();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { IdentifierInputType } from '@/components/InputFields';
|
||||||
import { SmartInputField } from '@/components/InputFields';
|
import { SmartInputField } from '@/components/InputFields';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import useTerms from '@/hooks/use-terms';
|
import useTerms from '@/hooks/use-terms';
|
||||||
import { identifierErrorWatcher, validateIdentifierField } from '@/utils/form';
|
import { getGeneralIdentifierErrorMessage, validateIdentifierField } from '@/utils/form';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
import useOnSubmit from './use-on-submit';
|
import useOnSubmit from './use-on-submit';
|
||||||
|
@ -68,32 +68,25 @@ const IdentifierSignInForm = ({ className, autoFocus, signInMethods }: Props) =>
|
||||||
[clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation]
|
[clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation]
|
||||||
);
|
);
|
||||||
|
|
||||||
const identifierError = identifierErrorWatcher(enabledSignInMethods, errors.identifier);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<SmartInputField
|
<SmartInputField
|
||||||
required
|
|
||||||
autoComplete="identifier"
|
autoComplete="identifier"
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
currentType={inputType}
|
currentType={inputType}
|
||||||
isDanger={!!identifierError || !!errorMessage}
|
isDanger={!!errors.identifier || !!errorMessage}
|
||||||
error={identifierError}
|
errorMessage={errors.identifier?.message}
|
||||||
enabledTypes={enabledSignInMethods}
|
enabledTypes={enabledSignInMethods}
|
||||||
onTypeChange={setInputType}
|
onTypeChange={setInputType}
|
||||||
{...register('identifier', {
|
{...register('identifier', {
|
||||||
required: true,
|
required: getGeneralIdentifierErrorMessage(enabledSignInMethods, 'required'),
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
const errorMessage = validateIdentifierField(inputType, value);
|
const errorMessage = validateIdentifierField(inputType, value);
|
||||||
|
|
||||||
if (errorMessage) {
|
return errorMessage
|
||||||
return typeof errorMessage === 'string'
|
? getGeneralIdentifierErrorMessage(enabledSignInMethods, 'invalid')
|
||||||
? t(`error.${errorMessage}`)
|
: true;
|
||||||
: t(`error.${errorMessage.code}`, errorMessage.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
/* Overwrite default input onChange handler */
|
/* Overwrite default input onChange handler */
|
||||||
|
|
|
@ -22,6 +22,7 @@ jest.mock('react-device-detect', () => ({
|
||||||
jest.mock('i18next', () => ({
|
jest.mock('i18next', () => ({
|
||||||
...jest.requireActual('i18next'),
|
...jest.requireActual('i18next'),
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
t: (key: string) => key,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('UsernamePasswordSignInForm', () => {
|
describe('UsernamePasswordSignInForm', () => {
|
||||||
|
@ -77,8 +78,8 @@ describe('UsernamePasswordSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('general_required')).not.toBeNull();
|
expect(queryByText('error.general_required')).not.toBeNull();
|
||||||
expect(queryByText('password_required')).not.toBeNull();
|
expect(queryByText('error.password_required')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
const identifierInput = container.querySelector('input[name="identifier"]');
|
const identifierInput = container.querySelector('input[name="identifier"]');
|
||||||
|
@ -96,8 +97,8 @@ describe('UsernamePasswordSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('general_required')).toBeNull();
|
expect(queryByText('error.general_required')).toBeNull();
|
||||||
expect(queryByText('password_required')).toBeNull();
|
expect(queryByText('error.password_required')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ describe('UsernamePasswordSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('general_invalid')).not.toBeNull();
|
expect(queryByText('error.general_invalid')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -130,7 +131,7 @@ describe('UsernamePasswordSignInForm', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(queryByText('general_invalid')).toBeNull();
|
expect(queryByText('error.general_invalid')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,7 @@ import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import usePasswordSignIn from '@/hooks/use-password-sign-in';
|
import usePasswordSignIn from '@/hooks/use-password-sign-in';
|
||||||
import { useForgotPasswordSettings } from '@/hooks/use-sie';
|
import { useForgotPasswordSettings } from '@/hooks/use-sie';
|
||||||
import useTerms from '@/hooks/use-terms';
|
import useTerms from '@/hooks/use-terms';
|
||||||
import {
|
import { getGeneralIdentifierErrorMessage, validateIdentifierField } from '@/utils/form';
|
||||||
identifierErrorWatcher,
|
|
||||||
passwordErrorWatcher,
|
|
||||||
validateIdentifierField,
|
|
||||||
} from '@/utils/form';
|
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -76,33 +72,23 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => {
|
||||||
[clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation]
|
[clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation]
|
||||||
);
|
);
|
||||||
|
|
||||||
const identifierError = identifierErrorWatcher(signInMethods, errors.identifier);
|
|
||||||
const passwordError = passwordErrorWatcher(errors.password);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<SmartInputField
|
<SmartInputField
|
||||||
required
|
|
||||||
autoComplete="identifier"
|
autoComplete="identifier"
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
currentType={inputType}
|
currentType={inputType}
|
||||||
isDanger={!!identifierError}
|
isDanger={!!errors.identifier}
|
||||||
error={identifierError}
|
errorMessage={errors.identifier?.message}
|
||||||
enabledTypes={signInMethods}
|
enabledTypes={signInMethods}
|
||||||
onTypeChange={setInputType}
|
onTypeChange={setInputType}
|
||||||
{...register('identifier', {
|
{...register('identifier', {
|
||||||
required: true,
|
required: getGeneralIdentifierErrorMessage(signInMethods, 'required'),
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
const errorMessage = validateIdentifierField(inputType, value);
|
const errorMessage = validateIdentifierField(inputType, value);
|
||||||
|
|
||||||
if (errorMessage) {
|
return errorMessage ? getGeneralIdentifierErrorMessage(signInMethods, 'invalid') : true;
|
||||||
return typeof errorMessage === 'string'
|
|
||||||
? t(`error.${errorMessage}`)
|
|
||||||
: t(`error.${errorMessage.code}`, errorMessage.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
/* Overwrite default input onChange handler */
|
/* Overwrite default input onChange handler */
|
||||||
|
@ -112,13 +98,12 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PasswordInputField
|
<PasswordInputField
|
||||||
required
|
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
placeholder={t('input.password')}
|
placeholder={t('input.password')}
|
||||||
isDanger={!!passwordError}
|
isDanger={!!errors.password}
|
||||||
error={passwordError}
|
errorMessage={errors.password?.message}
|
||||||
{...register('password', { required: true })}
|
{...register('password', { required: t('error.password_required') })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe('PasswordSignInForm', () => {
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(signInWithPasswordIdentifier).not.toBeCalled();
|
expect(signInWithPasswordIdentifier).not.toBeCalled();
|
||||||
expect(queryByText('password_required')).not.toBeNull();
|
expect(queryByText('error.password_required')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
const passwordInput = container.querySelector('input[name="password"]');
|
const passwordInput = container.querySelector('input[name="password"]');
|
||||||
|
|
|
@ -69,14 +69,13 @@ const PasswordForm = ({
|
||||||
return (
|
return (
|
||||||
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
<form className={classNames(styles.form, className)} onSubmit={onSubmitHandler}>
|
||||||
<PasswordInputField
|
<PasswordInputField
|
||||||
required
|
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
placeholder={t('input.password')}
|
placeholder={t('input.password')}
|
||||||
isDanger={!!errors.password}
|
isDanger={!!errors.password}
|
||||||
error={errors.password && 'password_required'}
|
errorMessage={errors.password?.message}
|
||||||
{...register('password', { required: true })}
|
{...register('password', { required: t('error.password_required') })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
{errorMessage && <ErrorMessage className={styles.formErrors}>{errorMessage}</ErrorMessage>}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import { SignInIdentifier } from '@logto/schemas';
|
import { SignInIdentifier } from '@logto/schemas';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import type { FieldError } from 'react-hook-form';
|
|
||||||
import type { TFuncKey } from 'react-i18next';
|
import type { TFuncKey } from 'react-i18next';
|
||||||
|
|
||||||
import type { ErrorType } from '@/components/ErrorMessage';
|
|
||||||
import type { IdentifierInputType } from '@/components/InputFields';
|
import type { IdentifierInputType } from '@/components/InputFields';
|
||||||
|
|
||||||
import { validateUsername, validateEmail, validatePhone } from './field-validations';
|
import { validateUsername, validateEmail, validatePhone } from './field-validations';
|
||||||
|
|
||||||
// eslint-disable-next-line id-length
|
const { t } = i18next;
|
||||||
const t = (key: TFuncKey) => i18next.t<'translation', TFuncKey>(key);
|
|
||||||
|
|
||||||
export const identifierInputPlaceholderMap: { [K in IdentifierInputType]: TFuncKey } = {
|
export const identifierInputPlaceholderMap: { [K in IdentifierInputType]: TFuncKey } = {
|
||||||
[SignInIdentifier.Phone]: 'input.phone_number',
|
[SignInIdentifier.Phone]: 'input.phone_number',
|
||||||
|
@ -23,35 +20,19 @@ export const identifierInputDescriptionMap: { [K in IdentifierInputType]: TFuncK
|
||||||
[SignInIdentifier.Username]: 'description.username',
|
[SignInIdentifier.Username]: 'description.username',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const passwordErrorWatcher = (error?: FieldError): ErrorType | undefined => {
|
export const getGeneralIdentifierErrorMessage = (
|
||||||
switch (error?.type) {
|
|
||||||
case 'required':
|
|
||||||
return 'password_required';
|
|
||||||
case 'minLength':
|
|
||||||
return { code: 'password_min_length', data: { min: 6 } };
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const identifierErrorWatcher = (
|
|
||||||
enabledFields: IdentifierInputType[],
|
enabledFields: IdentifierInputType[],
|
||||||
error?: FieldError
|
type: 'required' | 'invalid'
|
||||||
): ErrorType | undefined => {
|
) => {
|
||||||
const data = { types: enabledFields.map((field) => t(identifierInputDescriptionMap[field])) };
|
const data = {
|
||||||
|
types: enabledFields.map((field) =>
|
||||||
|
t<'translation', TFuncKey>(identifierInputDescriptionMap[field])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
switch (error?.type) {
|
const code = type === 'required' ? 'error.general_required' : 'error.general_invalid';
|
||||||
case 'required':
|
|
||||||
return {
|
return t<'translation', TFuncKey>(code, data);
|
||||||
code: 'general_required',
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
case 'validate':
|
|
||||||
return {
|
|
||||||
code: 'general_invalid',
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateIdentifierField = (type: IdentifierInputType, value: string) => {
|
export const validateIdentifierField = (type: IdentifierInputType, value: string) => {
|
||||||
|
|
Loading…
Reference in a new issue