From 4f4c4444423dc35c8a61c8bbf10167acfd8f31ab Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 14 Feb 2023 14:50:34 +0800 Subject: [PATCH] refactor(ui): simplify the usage of inputField error message (#3081) --- .../InputFields/InputField/index.test.tsx | 2 +- .../InputFields/InputField/index.tsx | 7 ++- .../PasswordInputField/index.test.tsx | 2 +- .../InputFields/SmartInputField/index.tsx | 3 +- .../src/containers/SetPassword/index.test.tsx | 12 +++--- .../ui/src/containers/SetPassword/index.tsx | 23 +++++----- .../src/hooks/use-social-sign-in-listener.ts | 2 - .../IdentifierSignInForm/index.test.tsx | 5 ++- .../SignIn/IdentifierSignInForm/index.tsx | 21 +++------ .../SignIn/PasswordSignInForm/index.test.tsx | 13 +++--- .../pages/SignIn/PasswordSignInForm/index.tsx | 31 ++++--------- .../PasswordForm/index.test.tsx | 2 +- .../SignInPassword/PasswordForm/index.tsx | 5 +-- packages/ui/src/utils/form.ts | 43 ++++++------------- 14 files changed, 64 insertions(+), 107 deletions(-) diff --git a/packages/ui/src/components/InputFields/InputField/index.test.tsx b/packages/ui/src/components/InputFields/InputField/index.test.tsx index 5c170ca41..0e4a53c73 100644 --- a/packages/ui/src/components/InputFields/InputField/index.test.tsx +++ b/packages/ui/src/components/InputFields/InputField/index.test.tsx @@ -31,7 +31,7 @@ describe('InputField Component', () => { test('render error message', () => { const errorCode = 'invalid_email'; - const { queryByText } = render(); + const { queryByText } = render(); expect(queryByText(errorCode)).not.toBeNull(); }); diff --git a/packages/ui/src/components/InputFields/InputField/index.tsx b/packages/ui/src/components/InputFields/InputField/index.tsx index 4e5ffa661..c247e33ed 100644 --- a/packages/ui/src/components/InputFields/InputField/index.tsx +++ b/packages/ui/src/components/InputFields/InputField/index.tsx @@ -2,14 +2,13 @@ import classNames from 'classnames'; import type { ForwardedRef, HTMLProps, ReactElement } from 'react'; import { forwardRef, cloneElement } from 'react'; -import type { ErrorType } from '@/components/ErrorMessage'; import ErrorMessage from '@/components/ErrorMessage'; import * as styles from './index.module.scss'; export type Props = Omit, 'prefix'> & { className?: string; - error?: ErrorType; + errorMessage?: string; isDanger?: boolean; prefix?: ReactElement; isPrefixVisible?: boolean; @@ -21,7 +20,7 @@ export type Props = Omit, 'prefix'> & { const InputField = ( { className, - error, + errorMessage, isDanger, prefix, suffix, @@ -49,7 +48,7 @@ const InputField = ( className: classNames([suffix.props.className, styles.suffix]), })} - {error && } + {errorMessage && {errorMessage}} ); diff --git a/packages/ui/src/components/InputFields/PasswordInputField/index.test.tsx b/packages/ui/src/components/InputFields/PasswordInputField/index.test.tsx index 25cc0eb62..2479d3692 100644 --- a/packages/ui/src/components/InputFields/PasswordInputField/index.test.tsx +++ b/packages/ui/src/components/InputFields/PasswordInputField/index.test.tsx @@ -31,7 +31,7 @@ describe('Input Field UI Component', () => { test('render error message', () => { const errorCode = 'password_required'; - const { queryByText } = render(); + const { queryByText } = render(); expect(queryByText(errorCode)).not.toBeNull(); }); diff --git a/packages/ui/src/components/InputFields/SmartInputField/index.tsx b/packages/ui/src/components/InputFields/SmartInputField/index.tsx index 06ead1dec..a0ff66233 100644 --- a/packages/ui/src/components/InputFields/SmartInputField/index.tsx +++ b/packages/ui/src/components/InputFields/SmartInputField/index.tsx @@ -6,7 +6,6 @@ import { useImperativeHandle, useRef, forwardRef } from 'react'; import ClearIcon from '@/assets/icons/clear-icon.svg'; import IconButton from '@/components/Button/IconButton'; -import type { ErrorType } from '@/components/ErrorMessage'; import InputField from '../InputField'; import AnimatedPrefix from './AnimatedPrefix'; @@ -19,7 +18,7 @@ export type { IdentifierInputType, EnabledIdentifierTypes } from './use-smart-in type Props = Omit, 'onChange' | 'prefix'> & { className?: string; - error?: ErrorType; + errorMessage?: string; isDanger?: boolean; enabledTypes?: EnabledIdentifierTypes; diff --git a/packages/ui/src/containers/SetPassword/index.test.tsx b/packages/ui/src/containers/SetPassword/index.test.tsx index daaf29c3f..845bebb2a 100644 --- a/packages/ui/src/containers/SetPassword/index.test.tsx +++ b/packages/ui/src/containers/SetPassword/index.test.tsx @@ -34,7 +34,7 @@ describe('', () => { expect(clearError).toBeCalled(); await waitFor(() => { - expect(queryByText('password_required')).not.toBeNull(); + expect(queryByText('error.password_required')).not.toBeNull(); }); expect(submit).not.toBeCalled(); @@ -54,7 +54,7 @@ describe('', () => { }); await waitFor(() => { - expect(queryByText('password_min_length')).not.toBeNull(); + expect(queryByText('error.password_min_length')).not.toBeNull(); }); expect(submit).not.toBeCalled(); @@ -67,7 +67,7 @@ describe('', () => { }); await waitFor(() => { - expect(queryByText('password_min_length')).toBeNull(); + expect(queryByText('error.password_min_length')).toBeNull(); }); }); @@ -90,7 +90,7 @@ describe('', () => { }); await waitFor(() => { - expect(queryByText('passwords_do_not_match')).not.toBeNull(); + expect(queryByText('error.passwords_do_not_match')).not.toBeNull(); }); expect(submit).not.toBeCalled(); @@ -103,7 +103,7 @@ describe('', () => { }); await waitFor(() => { - expect(queryByText('passwords_do_not_match')).toBeNull(); + expect(queryByText('error.passwords_do_not_match')).toBeNull(); }); }); @@ -125,7 +125,7 @@ describe('', () => { fireEvent.submit(submitButton); }); - expect(queryByText('passwords_do_not_match')).toBeNull(); + expect(queryByText('error.passwords_do_not_match')).toBeNull(); await waitFor(() => { expect(submit).toBeCalledWith('123456'); diff --git a/packages/ui/src/containers/SetPassword/index.tsx b/packages/ui/src/containers/SetPassword/index.tsx index 3f8b65f58..409b4a094 100644 --- a/packages/ui/src/containers/SetPassword/index.tsx +++ b/packages/ui/src/containers/SetPassword/index.tsx @@ -8,7 +8,6 @@ import Button from '@/components/Button'; import IconButton from '@/components/Button/IconButton'; import ErrorMessage from '@/components/ErrorMessage'; import { InputField } from '@/components/InputFields'; -import { passwordErrorWatcher } from '@/utils/form'; import TogglePassword from './TogglePassword'; import * as styles from './index.module.scss'; @@ -61,21 +60,24 @@ const SetPassword = ({ [clearErrorMessage, handleSubmit, onSubmit] ); - const newPasswordError = passwordErrorWatcher(errors.newPassword); - return (
value === watch('newPassword'), + validate: (value) => value === watch('newPassword') || t('error.passwords_do_not_match'), })} isSuffixFocusVisible={!!watch('confirmPassword')} suffix={ diff --git a/packages/ui/src/hooks/use-social-sign-in-listener.ts b/packages/ui/src/hooks/use-social-sign-in-listener.ts index 2cc5ed311..e32afe033 100644 --- a/packages/ui/src/hooks/use-social-sign-in-listener.ts +++ b/packages/ui/src/hooks/use-social-sign-in-listener.ts @@ -76,8 +76,6 @@ const useSocialSignInListener = (connectorId?: string) => { const signInWithSocialHandler = useCallback( async (connectorId: string, data: Record) => { - console.log('triggered'); - const [error, result] = await asyncSignInWithSocial({ connectorId, connectorData: { diff --git a/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.test.tsx b/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.test.tsx index 6eca2319e..f2bb9bc72 100644 --- a/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.test.tsx +++ b/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.test.tsx @@ -15,6 +15,7 @@ import IdentifierSignInForm from './index'; jest.mock('i18next', () => ({ ...jest.requireActual('i18next'), language: 'en', + t: (key: string) => key, })); const mockedNavigate = jest.fn(); @@ -54,7 +55,7 @@ describe('IdentifierSignInForm', () => { }); await waitFor(() => { - expect(getByText('general_required')).not.toBeNull(); + expect(getByText('error.general_required')).not.toBeNull(); }); }); @@ -78,7 +79,7 @@ describe('IdentifierSignInForm', () => { }); await waitFor(() => { - expect(getByText('general_invalid')).not.toBeNull(); + expect(getByText('error.general_invalid')).not.toBeNull(); }); } ); diff --git a/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.tsx b/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.tsx index 3971112bf..b0195d816 100644 --- a/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.tsx +++ b/packages/ui/src/pages/SignIn/IdentifierSignInForm/index.tsx @@ -11,7 +11,7 @@ import type { IdentifierInputType } from '@/components/InputFields'; import { SmartInputField } from '@/components/InputFields'; import TermsOfUse from '@/containers/TermsOfUse'; 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 useOnSubmit from './use-on-submit'; @@ -68,32 +68,25 @@ const IdentifierSignInForm = ({ className, autoFocus, signInMethods }: Props) => [clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation] ); - const identifierError = identifierErrorWatcher(enabledSignInMethods, errors.identifier); - return ( { const errorMessage = validateIdentifierField(inputType, value); - if (errorMessage) { - return typeof errorMessage === 'string' - ? t(`error.${errorMessage}`) - : t(`error.${errorMessage.code}`, errorMessage.data); - } - - return true; + return errorMessage + ? getGeneralIdentifierErrorMessage(enabledSignInMethods, 'invalid') + : true; }, })} /* Overwrite default input onChange handler */ diff --git a/packages/ui/src/pages/SignIn/PasswordSignInForm/index.test.tsx b/packages/ui/src/pages/SignIn/PasswordSignInForm/index.test.tsx index 219065ddb..ec30c803e 100644 --- a/packages/ui/src/pages/SignIn/PasswordSignInForm/index.test.tsx +++ b/packages/ui/src/pages/SignIn/PasswordSignInForm/index.test.tsx @@ -22,6 +22,7 @@ jest.mock('react-device-detect', () => ({ jest.mock('i18next', () => ({ ...jest.requireActual('i18next'), language: 'en', + t: (key: string) => key, })); describe('UsernamePasswordSignInForm', () => { @@ -77,8 +78,8 @@ describe('UsernamePasswordSignInForm', () => { }); await waitFor(() => { - expect(queryByText('general_required')).not.toBeNull(); - expect(queryByText('password_required')).not.toBeNull(); + expect(queryByText('error.general_required')).not.toBeNull(); + expect(queryByText('error.password_required')).not.toBeNull(); }); const identifierInput = container.querySelector('input[name="identifier"]'); @@ -96,8 +97,8 @@ describe('UsernamePasswordSignInForm', () => { }); await waitFor(() => { - expect(queryByText('general_required')).toBeNull(); - expect(queryByText('password_required')).toBeNull(); + expect(queryByText('error.general_required')).toBeNull(); + expect(queryByText('error.password_required')).toBeNull(); }); }); @@ -122,7 +123,7 @@ describe('UsernamePasswordSignInForm', () => { }); await waitFor(() => { - expect(queryByText('general_invalid')).not.toBeNull(); + expect(queryByText('error.general_invalid')).not.toBeNull(); }); act(() => { @@ -130,7 +131,7 @@ describe('UsernamePasswordSignInForm', () => { }); await waitFor(() => { - expect(queryByText('general_invalid')).toBeNull(); + expect(queryByText('error.general_invalid')).toBeNull(); }); }); diff --git a/packages/ui/src/pages/SignIn/PasswordSignInForm/index.tsx b/packages/ui/src/pages/SignIn/PasswordSignInForm/index.tsx index c1334a6ec..6887e5a01 100644 --- a/packages/ui/src/pages/SignIn/PasswordSignInForm/index.tsx +++ b/packages/ui/src/pages/SignIn/PasswordSignInForm/index.tsx @@ -13,11 +13,7 @@ import TermsOfUse from '@/containers/TermsOfUse'; import usePasswordSignIn from '@/hooks/use-password-sign-in'; import { useForgotPasswordSettings } from '@/hooks/use-sie'; import useTerms from '@/hooks/use-terms'; -import { - identifierErrorWatcher, - passwordErrorWatcher, - validateIdentifierField, -} from '@/utils/form'; +import { getGeneralIdentifierErrorMessage, validateIdentifierField } from '@/utils/form'; import * as styles from './index.module.scss'; @@ -76,33 +72,23 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => { [clearErrorMessage, handleSubmit, inputType, onSubmit, termsValidation] ); - const identifierError = identifierErrorWatcher(signInMethods, errors.identifier); - const passwordError = passwordErrorWatcher(errors.password); - return ( { const errorMessage = validateIdentifierField(inputType, value); - if (errorMessage) { - return typeof errorMessage === 'string' - ? t(`error.${errorMessage}`) - : t(`error.${errorMessage.code}`, errorMessage.data); - } - - return true; + return errorMessage ? getGeneralIdentifierErrorMessage(signInMethods, 'invalid') : true; }, })} /* Overwrite default input onChange handler */ @@ -112,13 +98,12 @@ const PasswordSignInForm = ({ className, autoFocus, signInMethods }: Props) => { /> {errorMessage && {errorMessage}} diff --git a/packages/ui/src/pages/SignInPassword/PasswordForm/index.test.tsx b/packages/ui/src/pages/SignInPassword/PasswordForm/index.test.tsx index bf5257c91..9e7669ad5 100644 --- a/packages/ui/src/pages/SignInPassword/PasswordForm/index.test.tsx +++ b/packages/ui/src/pages/SignInPassword/PasswordForm/index.test.tsx @@ -56,7 +56,7 @@ describe('PasswordSignInForm', () => { await waitFor(() => { expect(signInWithPasswordIdentifier).not.toBeCalled(); - expect(queryByText('password_required')).not.toBeNull(); + expect(queryByText('error.password_required')).not.toBeNull(); }); const passwordInput = container.querySelector('input[name="password"]'); diff --git a/packages/ui/src/pages/SignInPassword/PasswordForm/index.tsx b/packages/ui/src/pages/SignInPassword/PasswordForm/index.tsx index ec768a8ab..395ebd772 100644 --- a/packages/ui/src/pages/SignInPassword/PasswordForm/index.tsx +++ b/packages/ui/src/pages/SignInPassword/PasswordForm/index.tsx @@ -69,14 +69,13 @@ const PasswordForm = ({ return ( {errorMessage && {errorMessage}} diff --git a/packages/ui/src/utils/form.ts b/packages/ui/src/utils/form.ts index a29ee0bd8..920d073ab 100644 --- a/packages/ui/src/utils/form.ts +++ b/packages/ui/src/utils/form.ts @@ -1,15 +1,12 @@ import { SignInIdentifier } from '@logto/schemas'; import i18next from 'i18next'; -import type { FieldError } from 'react-hook-form'; import type { TFuncKey } from 'react-i18next'; -import type { ErrorType } from '@/components/ErrorMessage'; import type { IdentifierInputType } from '@/components/InputFields'; import { validateUsername, validateEmail, validatePhone } from './field-validations'; -// eslint-disable-next-line id-length -const t = (key: TFuncKey) => i18next.t<'translation', TFuncKey>(key); +const { t } = i18next; export const identifierInputPlaceholderMap: { [K in IdentifierInputType]: TFuncKey } = { [SignInIdentifier.Phone]: 'input.phone_number', @@ -23,35 +20,19 @@ export const identifierInputDescriptionMap: { [K in IdentifierInputType]: TFuncK [SignInIdentifier.Username]: 'description.username', }; -export const passwordErrorWatcher = (error?: FieldError): ErrorType | undefined => { - switch (error?.type) { - case 'required': - return 'password_required'; - case 'minLength': - return { code: 'password_min_length', data: { min: 6 } }; - default: - } -}; - -export const identifierErrorWatcher = ( +export const getGeneralIdentifierErrorMessage = ( enabledFields: IdentifierInputType[], - error?: FieldError -): ErrorType | undefined => { - const data = { types: enabledFields.map((field) => t(identifierInputDescriptionMap[field])) }; + type: 'required' | 'invalid' +) => { + const data = { + types: enabledFields.map((field) => + t<'translation', TFuncKey>(identifierInputDescriptionMap[field]) + ), + }; - switch (error?.type) { - case 'required': - return { - code: 'general_required', - data, - }; - case 'validate': - return { - code: 'general_invalid', - data, - }; - default: - } + const code = type === 'required' ? 'error.general_required' : 'error.general_invalid'; + + return t<'translation', TFuncKey>(code, data); }; export const validateIdentifierField = (type: IdentifierInputType, value: string) => {