From 62b25dbf14613e65f11582fe0bce551c64e0fd98 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Wed, 16 Nov 2022 18:48:33 +0800 Subject: [PATCH] refactor(console): form validations on sign-up and sign-in page (#2448) --- .../tabs/SignUpAndSignInTab/SignInForm.tsx | 40 +-- .../tabs/SignUpAndSignInTab/SignUpForm.tsx | 77 ++++- .../SignInMethodEditBox/SignInMethodItem.tsx | 135 +++++---- .../SignInMethodEditBox/index.module.scss | 9 + .../components/SignInMethodEditBox/index.tsx | 277 ++++++++---------- .../SignInMethodEditBox/utilities.ts | 43 --- .../src/pages/SignInExperience/types.ts | 2 +- .../src/pages/SignInExperience/utilities.ts | 8 +- .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + .../translation/admin-console/sign-in-exp.ts | 1 + 15 files changed, 286 insertions(+), 312 deletions(-) diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignInForm.tsx b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignInForm.tsx index fccd63813..3a645161e 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignInForm.tsx +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignInForm.tsx @@ -1,33 +1,13 @@ -import { Controller, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import FormField from '@/components/FormField'; -import type { SignInExperienceForm } from '../../types'; import SignInMethodEditBox from './components/SignInMethodEditBox'; -import { - signUpIdentifierToRequiredConnectorMapping, - signUpToSignInIdentifierMapping, -} from './constants'; import * as styles from './index.module.scss'; const SignInForm = () => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { control, watch } = useFormContext(); - - const signUpIdentifier = watch('signUp.identifier'); - const setupPasswordAtSignUp = watch('signUp.password'); - const setupVerificationAtSignUp = watch('signUp.verify'); - - if ( - !signUpIdentifier || - setupPasswordAtSignUp === undefined || - setupVerificationAtSignUp === undefined - ) { - return null; - } - return ( <>
{t('sign_in_exp.sign_up_and_sign_in.sign_in.title')}
@@ -35,25 +15,7 @@ const SignInForm = () => {
{t('sign_in_exp.sign_up_and_sign_in.sign_in.description')}
- { - return ( - - ); - }} - /> + ); diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignUpForm.tsx b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignUpForm.tsx index 968ffd21f..83937f0fd 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignUpForm.tsx +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/SignUpForm.tsx @@ -10,10 +10,15 @@ import useEnabledConnectorTypes from '@/hooks/use-enabled-connector-types'; import type { SignInExperienceForm } from '../../types'; import ConnectorSetupWarning from './components/ConnectorSetupWarning'; +import { + getSignInMethodPasswordCheckState, + getSignInMethodVerificationCodeCheckState, +} from './components/SignInMethodEditBox/utilities'; import { requiredVerifySignUpIdentifiers, signUpIdentifiers, signUpIdentifierToRequiredConnectorMapping, + signUpToSignInIdentifierMapping, } from './constants'; import * as styles from './index.module.scss'; @@ -22,12 +27,14 @@ const SignUpForm = () => { const { control, setValue, + getValues, watch, - formState: { errors }, + trigger, + formState: { errors, submitCount }, } = useFormContext(); const { isConnectorTypeEnabled } = useEnabledConnectorTypes(); - const signUpIdentifier = watch('signUp.identifier'); + const { identifier: signUpIdentifier } = watch('signUp') ?? {}; if (!signUpIdentifier) { return null; @@ -53,6 +60,53 @@ const SignUpForm = () => { } }; + const refreshSignInMethods = () => { + const signUpIdentifier = getValues('signUp.identifier'); + const signInMethods = getValues('signIn.methods'); + const isSignUpPasswordRequired = getValues('signUp.password'); + + // Note: append required sign-in methods according to the sign-up identifier config + const requiredSignInIdentifiers = signUpToSignInIdentifierMapping[signUpIdentifier]; + const allSignInMethods = requiredSignInIdentifiers.reduce((methods, requiredIdentifier) => { + if (signInMethods.some(({ identifier }) => identifier === requiredIdentifier)) { + return methods; + } + + return [ + ...methods, + { + identifier: requiredIdentifier, + password: getSignInMethodPasswordCheckState(requiredIdentifier, isSignUpPasswordRequired), + verificationCode: getSignInMethodVerificationCodeCheckState(requiredIdentifier), + isPasswordPrimary: true, + }, + ]; + }, signInMethods); + + setValue( + 'signIn.methods', + // Note: refresh sign-in authentications according to the sign-up authentications config + allSignInMethods.map((method) => { + const { identifier, password } = method; + + return { + ...method, + password: getSignInMethodPasswordCheckState( + identifier, + isSignUpPasswordRequired, + password + ), + verificationCode: getSignInMethodVerificationCodeCheckState(identifier), + }; + }) + ); + + // Note: we need to revalidate the sign-in methods after we have submitted + if (submitCount) { + void trigger('signIn.methods'); + } + }; + return ( <>
{t('sign_in_exp.sign_up_and_sign_in.sign_up.title')}
@@ -65,10 +119,6 @@ const SignUpForm = () => { control={control} rules={{ validate: (value) => { - if (!value) { - return false; - } - return signUpIdentifierToRequiredConnectorMapping[value].every((connectorType) => isConnectorTypeEnabled(connectorType) ); @@ -101,6 +151,7 @@ const SignUpForm = () => { } onChange(value); postSignUpIdentifierChange(value); + refreshSignInMethods(); }} /> )} @@ -122,9 +173,12 @@ const SignUpForm = () => { { + onChange(value); + refreshSignInMethods(); + }} /> )} /> @@ -135,10 +189,13 @@ const SignUpForm = () => { render={({ field: { value, onChange } }) => ( { + onChange(value); + refreshSignInMethods(); + }} /> )} /> diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/SignInMethodItem.tsx b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/SignInMethodItem.tsx index 34c73c463..6cc88ccf8 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/SignInMethodItem.tsx +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/SignInMethodItem.tsx @@ -1,3 +1,4 @@ +import type { ConnectorType } from '@logto/schemas'; import { SignInIdentifier } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import classNames from 'classnames'; @@ -10,6 +11,7 @@ import SwitchArrowIcon from '@/assets/images/switch-arrow.svg'; import Checkbox from '@/components/Checkbox'; import IconButton from '@/components/IconButton'; +import ConnectorSetupWarning from '../ConnectorSetupWarning'; import * as styles from './index.module.scss'; import type { SignInMethod } from './types'; @@ -18,13 +20,15 @@ type Props = { isPasswordCheckable: boolean; isVerificationCodeCheckable: boolean; isDeletable: boolean; + requiredConnectors: ConnectorType[]; + hasError?: boolean; + errorMessage?: string; onVerificationStateChange: ( - identifier: SignInIdentifier, verification: 'password' | 'verificationCode', checked: boolean ) => void; - onToggleVerificationPrimary: (identifier: SignInIdentifier) => void; - onDelete: (identifier: SignInIdentifier) => void; + onToggleVerificationPrimary: () => void; + onDelete: () => void; }; const SignInMethodItem = ({ @@ -32,6 +36,9 @@ const SignInMethodItem = ({ isPasswordCheckable, isVerificationCodeCheckable, isDeletable, + requiredConnectors, + hasError, + errorMessage, onVerificationStateChange, onToggleVerificationPrimary, onDelete, @@ -39,71 +46,71 @@ const SignInMethodItem = ({ const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); return ( -
-
-
- - {t('sign_in_exp.sign_up_and_sign_in.identifiers', { - context: snakeCase(identifier), - })} +
+
+
+
+ + {t('sign_in_exp.sign_up_and_sign_in.identifiers', { + context: snakeCase(identifier), + })} +
+
+ { + onVerificationStateChange('password', checked); + }} + /> + {identifier !== SignInIdentifier.Username && ( + <> + + + + { + onVerificationStateChange('verificationCode', checked); + }} + /> + + )} +
-
- { - onVerificationStateChange(identifier, 'password', checked); - }} - /> - {identifier !== SignInIdentifier.Username && ( - <> - { - onToggleVerificationPrimary(identifier); - }} - > - - - { - onVerificationStateChange(identifier, 'verificationCode', checked); - }} - /> - - )} -
+ +
- { - onDelete(identifier); - }} - > - - + {errorMessage &&
{errorMessage}
} +
); }; diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.module.scss b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.module.scss index 6309679d5..dafeed18f 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.module.scss +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.module.scss @@ -22,6 +22,10 @@ cursor: move; color: var(--color-text); + &.error { + outline: 1px solid var(--color-error); + } + .identifier { width: 130px; display: flex; @@ -67,3 +71,8 @@ .addSignInMethodDropDown { min-width: unset; } + +.errorMessage { + font: var(--font-body-medium); + color: var(--color-error); +} diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.tsx b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.tsx index fbc8f105e..fc3c7c31e 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.tsx +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/index.tsx @@ -1,178 +1,153 @@ -import type { ConnectorType } from '@logto/schemas'; import { SignInIdentifier } from '@logto/schemas'; -import { useCallback, useEffect, useRef } from 'react'; +import { conditional } from '@silverhand/essentials'; +import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import DragDropProvider from '@/components/Transfer/DragDropProvider'; import DraggableItem from '@/components/Transfer/DraggableItem'; +import useEnabledConnectorTypes from '@/hooks/use-enabled-connector-types'; +import type { SignInExperienceForm } from '@/pages/SignInExperience/types'; -import { signInIdentifiers, signInIdentifierToRequiredConnectorMapping } from '../../constants'; -import ConnectorSetupWarning from '../ConnectorSetupWarning'; +import { + signInIdentifiers, + signInIdentifierToRequiredConnectorMapping, + signUpIdentifierToRequiredConnectorMapping, + signUpToSignInIdentifierMapping, +} from '../../constants'; import AddButton from './AddButton'; import SignInMethodItem from './SignInMethodItem'; import * as styles from './index.module.scss'; -import type { SignInMethod } from './types'; import { - computeOnSignInMethodAppended, - computeOnVerificationStateChanged, - computeOnPasswordPrimaryFlagToggled, getSignInMethodPasswordCheckState, getSignInMethodVerificationCodeCheckState, } from './utilities'; -type Props = { - value: SignInMethod[]; - onChange: (value: SignInMethod[]) => void; - requiredSignInIdentifiers: SignInIdentifier[]; - ignoredWarningConnectors: ConnectorType[]; - isSignUpPasswordRequired: boolean; - isSignUpVerificationRequired: boolean; -}; +const SignInMethodEditBox = () => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { + control, + watch, + trigger, + formState: { submitCount }, + } = useFormContext(); + const signUp = watch('signUp'); -const SignInMethodEditBox = ({ - value, - onChange, - requiredSignInIdentifiers, - ignoredWarningConnectors, - isSignUpPasswordRequired, - isSignUpVerificationRequired, -}: Props) => { - const signInIdentifierOptions = signInIdentifiers.filter((candidateIdentifier) => - value.every(({ identifier }) => identifier !== candidateIdentifier) - ); + const { fields, swap, update, remove, append } = useFieldArray({ + control, + name: 'signIn.methods', + }); - // Note: add a reference to avoid infinite loop when change the value by `useEffect` - const signInMethods = useRef(value); - - const handleChange = useCallback( - (value: SignInMethod[]) => { - // eslint-disable-next-line @silverhand/fp/no-mutation - signInMethods.current = value; - onChange(value); - }, - [onChange] - ); - - const addSignInMethod = useCallback( - (identifier: SignInIdentifier) => { - handleChange( - computeOnSignInMethodAppended(value, { - identifier, - password: getSignInMethodPasswordCheckState(identifier, isSignUpPasswordRequired), - verificationCode: getSignInMethodVerificationCodeCheckState(identifier), - isPasswordPrimary: true, - }) - ); - }, - [handleChange, value, isSignUpPasswordRequired] - ); - - useEffect(() => { - const allSignInMethods = requiredSignInIdentifiers.reduce( - (previous, current) => - computeOnSignInMethodAppended(previous, { - identifier: current, - password: getSignInMethodPasswordCheckState(current, isSignUpPasswordRequired), - verificationCode: getSignInMethodVerificationCodeCheckState(current), - isPasswordPrimary: true, - }), - signInMethods.current - ); - - handleChange( - allSignInMethods.map((method) => ({ - ...method, - password: getSignInMethodPasswordCheckState( - method.identifier, - isSignUpPasswordRequired, - method.password - ), - verificationCode: getSignInMethodVerificationCodeCheckState(method.identifier), - })) - ); - }, [ - handleChange, - isSignUpPasswordRequired, - isSignUpVerificationRequired, - requiredSignInIdentifiers, - ]); - - const onMoveItem = (dragIndex: number, hoverIndex: number) => { - const dragItem = value[dragIndex]; - const hoverItem = value[hoverIndex]; - - if (!dragItem || !hoverItem) { - return; + const revalidate = () => { + if (submitCount) { + void trigger(`signIn.methods`); } - - handleChange( - value.map((value_, index) => { - if (index === dragIndex) { - return hoverItem; - } - - if (index === hoverIndex) { - return dragItem; - } - - return value_; - }) - ); }; + const { isConnectorTypeEnabled } = useEnabledConnectorTypes(); + + if (!signUp) { + return null; + } + + const { + identifier: signUpIdentifier, + password: isSignUpPasswordRequired, + verify: isSignUpVerificationRequired, + } = signUp; + + const requiredSignInIdentifiers = signUpToSignInIdentifierMapping[signUpIdentifier]; + const ignoredWarningConnectors = signUpIdentifierToRequiredConnectorMapping[signUpIdentifier]; + + const signInIdentifierOptions = signInIdentifiers.filter((candidateIdentifier) => + fields.every(({ identifier }) => identifier !== candidateIdentifier) + ); + return (
- {value.map((signInMethod, index) => ( - - { - handleChange( - computeOnVerificationStateChanged(value, identifier, verification, checked) - ); - }} - onToggleVerificationPrimary={(identifier) => { - handleChange(computeOnPasswordPrimaryFlagToggled(value, identifier)); - }} - onDelete={(identifier) => { - handleChange(value.filter((method) => method.identifier !== identifier)); - }} - /> - - ))} + {fields.map((signInMethod, index) => { + const { id, identifier, verificationCode, isPasswordPrimary } = signInMethod; + + const requiredConnectors = + conditional( + verificationCode && + signInIdentifierToRequiredConnectorMapping[identifier].filter( + (connector) => !ignoredWarningConnectors.includes(connector) + ) + ) ?? []; + + return ( + + { + if (!password && !verificationCode) { + return t('sign_in_exp.sign_up_and_sign_in.sign_in.require_auth_factor'); + } + + if ( + verificationCode && + requiredConnectors.some( + (connectorType) => !isConnectorTypeEnabled(connectorType) + ) + ) { + // Note: when required connectors are not all enabled, we show error state without error message for we have the connector setup warning + return false; + } + + return true; + }, + }} + render={({ field: { value }, fieldState: { error } }) => ( + { + update(index, { ...value, [verification]: checked }); + revalidate(); + }} + onToggleVerificationPrimary={() => { + update(index, { ...value, isPasswordPrimary: !isPasswordPrimary }); + revalidate(); + }} + onDelete={() => { + remove(index); + }} + /> + )} + /> + + ); + })} - ( - (connectors, { identifier: signInIdentifier, verificationCode }) => { - return [ - ...connectors, - ...(verificationCode - ? signInIdentifierToRequiredConnectorMapping[signInIdentifier] - : []), - ]; - }, - [] - ) - .filter((connector) => !ignoredWarningConnectors.includes(connector))} - /> 0} - onSelected={addSignInMethod} + hasSelectedIdentifiers={fields.length > 0} + onSelected={(identifier) => { + append({ + identifier, + password: getSignInMethodPasswordCheckState(identifier, isSignUpPasswordRequired), + verificationCode: getSignInMethodVerificationCodeCheckState(identifier), + isPasswordPrimary: true, + }); + }} />
); diff --git a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/utilities.ts b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/utilities.ts index ab1721273..2ffc2a5a8 100644 --- a/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/utilities.ts +++ b/packages/console/src/pages/SignInExperience/tabs/SignUpAndSignInTab/components/SignInMethodEditBox/utilities.ts @@ -1,48 +1,5 @@ import { SignInIdentifier } from '@logto/schemas'; -import type { SignInMethod } from './types'; - -export const computeOnVerificationStateChanged = ( - oldValue: SignInMethod[], - identifier: SignInIdentifier, - verification: 'password' | 'verificationCode', - checked: boolean -) => - oldValue.map((method) => - method.identifier === identifier - ? { - ...method, - [verification]: checked, - } - : method - ); - -export const computeOnSignInMethodAppended = ( - appendTo: SignInMethod[], - appended: SignInMethod -): SignInMethod[] => { - const { identifier: signInIdentifier } = appended; - - if (appendTo.some((method) => method.identifier === signInIdentifier)) { - return appendTo; - } - - return [...appendTo, appended]; -}; - -export const computeOnPasswordPrimaryFlagToggled = ( - oldValue: SignInMethod[], - identifier: SignInIdentifier -) => - oldValue.map((method) => - method.identifier === identifier - ? { - ...method, - isPasswordPrimary: !method.isPasswordPrimary, - } - : method - ); - export const getSignInMethodPasswordCheckState = ( signInIdentifier: SignInIdentifier, isSignUpPasswordRequired: boolean, diff --git a/packages/console/src/pages/SignInExperience/types.ts b/packages/console/src/pages/SignInExperience/types.ts index 661475158..0eba70ef4 100644 --- a/packages/console/src/pages/SignInExperience/types.ts +++ b/packages/console/src/pages/SignInExperience/types.ts @@ -1,6 +1,6 @@ import type { SignInExperience, SignUp } from '@logto/schemas'; export type SignInExperienceForm = Omit & { - signUp: Partial; + signUp?: SignUp; createAccountEnabled: boolean; }; diff --git a/packages/console/src/pages/SignInExperience/utilities.ts b/packages/console/src/pages/SignInExperience/utilities.ts index 288099f66..401f79471 100644 --- a/packages/console/src/pages/SignInExperience/utilities.ts +++ b/packages/console/src/pages/SignInExperience/utilities.ts @@ -30,10 +30,10 @@ export const signInExperienceParser = { darkLogoUrl: conditional(branding.darkLogoUrl?.length && branding.darkLogoUrl), slogan: conditional(branding.slogan?.length && branding.slogan), }, - signUp: { - identifier: signUp.identifier ?? SignUpIdentifier.Username, - password: Boolean(signUp.password), - verify: Boolean(signUp.verify), + signUp: signUp ?? { + identifier: SignUpIdentifier.Username, + password: true, + verify: false, }, signInMode: createAccountEnabled ? SignInMode.SignInAndRegister : SignInMode.SignIn, }; diff --git a/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts index 81feba345..6e4a24548 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts @@ -46,6 +46,7 @@ const sign_in_exp = { password_auth: 'Password', // UNTRANSLATED verification_code_auth: 'Verification code', // UNTRANSLATED auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', // UNTRANSLATED + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: 'SOCIAL SIGN-IN', // UNTRANSLATED diff --git a/packages/phrases/src/locales/en/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/en/translation/admin-console/sign-in-exp.ts index 52a0cd985..c3d7d4922 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/sign-in-exp.ts @@ -68,6 +68,7 @@ const sign_in_exp = { password_auth: 'Password', verification_code_auth: 'Verification code', auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', + require_auth_factor: 'You have to select at least one authentication factor.', }, social_sign_in: { title: 'SOCIAL SIGN-IN', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/fr/translation/admin-console/sign-in-exp.ts index 5063d1838..d0627dfff 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/sign-in-exp.ts @@ -70,6 +70,7 @@ const sign_in_exp = { password_auth: 'Password', // UNTRANSLATED verification_code_auth: 'Verification code', // UNTRANSLATED auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', // UNTRANSLATED + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: 'SOCIAL SIGN-IN', // UNTRANSLATED diff --git a/packages/phrases/src/locales/ko/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/ko/translation/admin-console/sign-in-exp.ts index d6612a723..e97067920 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/sign-in-exp.ts @@ -65,6 +65,7 @@ const sign_in_exp = { password_auth: 'Password', // UNTRANSLATED verification_code_auth: 'Verification code', // UNTRANSLATED auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', // UNTRANSLATED + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: 'SOCIAL SIGN-IN', // UNTRANSLATED diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/sign-in-exp.ts index 966c3ce13..62f0fc525 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/sign-in-exp.ts @@ -68,6 +68,7 @@ const sign_in_exp = { password_auth: 'Password', // UNTRANSLATED verification_code_auth: 'Verification code', // UNTRANSLATED auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', // UNTRANSLATED + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: 'SOCIAL SIGN-IN', // UNTRANSLATED diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/sign-in-exp.ts index 2a9e19036..0f8bb22b7 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/sign-in-exp.ts @@ -69,6 +69,7 @@ const sign_in_exp = { password_auth: 'Password', // UNTRANSLATED verification_code_auth: 'Verification code', // UNTRANSLATED auth_swap_tip: 'Swap the options below to determine which appears first in the flow.', // UNTRANSLATED + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: 'SOCIAL SIGN-IN', // UNTRANSLATED diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/sign-in-exp.ts index 4ff62ccca..bea28b8d1 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/sign-in-exp.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/sign-in-exp.ts @@ -63,6 +63,7 @@ const sign_in_exp = { password_auth: '密码', verification_code_auth: '验证码', auth_swap_tip: '交换以下选项的位置即可设定它们在用户登录流程中出现的先后。', + require_auth_factor: 'You have to select at least one authentication factor.', // UNTRANSLATED }, social_sign_in: { title: '社交登录',