From b6c684874ae4fffdb135522de2ae96b0bd7a3d3f Mon Sep 17 00:00:00 2001 From: simeng-li <simeng@silverhand.io> Date: Wed, 11 Jan 2023 15:41:02 +0800 Subject: [PATCH] refactor(ui): simplify code verification hooks (#2898) --- packages/ui/src/apis/interaction.ts | 2 +- .../src/containers/VerificationCode/index.tsx | 21 ++- .../use-continue-flow-code-verification.ts | 81 ++++++++++++ ...-set-email-verification-code-validation.ts | 64 --------- ...-set-phone-verification-code-validation.ts | 59 --------- ...word-email-verification-code-validation.ts | 60 --------- ...-forgot-password-flow-code-verification.ts | 71 ++++++++++ ...word-phone-verification-code-validation.ts | 60 --------- ...eneral-verification-code-error-handler.ts} | 8 +- .../use-identifier-error-alert.ts | 54 ++++---- .../use-register-flow-code-verification.ts | 122 ++++++++++++++++++ ...with-email-verification-code-validation.ts | 108 ---------------- ...with-phone-verification-code-validation.ts | 111 ---------------- ... => use-sign-in-flow-code-verification.ts} | 86 ++++++------ ...with-email-verification-code-validation.ts | 119 ----------------- .../src/containers/VerificationCode/utils.ts | 45 ++----- .../ui/src/pages/VerificationCode/index.tsx | 11 +- packages/ui/src/types/guard.ts | 12 +- packages/ui/src/types/index.ts | 9 +- 19 files changed, 407 insertions(+), 696 deletions(-) create mode 100644 packages/ui/src/containers/VerificationCode/use-continue-flow-code-verification.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-continue-set-email-verification-code-validation.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-continue-set-phone-verification-code-validation.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-forgot-password-email-verification-code-validation.ts create mode 100644 packages/ui/src/containers/VerificationCode/use-forgot-password-flow-code-verification.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-forgot-password-phone-verification-code-validation.ts rename packages/ui/src/containers/VerificationCode/{use-shared-error-handler.ts => use-general-verification-code-error-handler.ts} (72%) create mode 100644 packages/ui/src/containers/VerificationCode/use-register-flow-code-verification.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-register-with-email-verification-code-validation.ts delete mode 100644 packages/ui/src/containers/VerificationCode/use-register-with-phone-verification-code-validation.ts rename packages/ui/src/containers/VerificationCode/{use-sign-in-with-phone-verification-code-validation.ts => use-sign-in-flow-code-verification.ts} (50%) delete mode 100644 packages/ui/src/containers/VerificationCode/use-sign-in-with-email-verification-code-validation.ts diff --git a/packages/ui/src/apis/interaction.ts b/packages/ui/src/apis/interaction.ts index cf66711c7..c917ce7c6 100644 --- a/packages/ui/src/apis/interaction.ts +++ b/packages/ui/src/apis/interaction.ts @@ -134,7 +134,7 @@ export const verifyForgotPasswordVerificationCodeIdentifier = async ( return api.post(`${interactionPrefix}/submit`).json<Response>(); }; -export const signInWithVerifierIdentifier = async () => { +export const signInWithVerifiedIdentifier = async () => { await api.delete(`${interactionPrefix}/profile`); await api.put(`${interactionPrefix}/event`, { diff --git a/packages/ui/src/containers/VerificationCode/index.tsx b/packages/ui/src/containers/VerificationCode/index.tsx index 0c356a1f1..bad802940 100644 --- a/packages/ui/src/containers/VerificationCode/index.tsx +++ b/packages/ui/src/containers/VerificationCode/index.tsx @@ -1,4 +1,4 @@ -import type { SignInIdentifier } from '@logto/schemas'; +import { SignInIdentifier } from '@logto/schemas'; import classNames from 'classnames'; import { useState, useEffect, useCallback } from 'react'; import { useTranslation, Trans } from 'react-i18next'; @@ -10,7 +10,7 @@ import { UserFlow } from '@/types'; import PasswordSignInLink from './PasswordSignInLink'; import * as styles from './index.module.scss'; import useResendVerificationCode from './use-resend-verification-code'; -import { getVerificationCodeHook } from './utils'; +import { getCodeVerificationHookByFlow } from './utils'; type Props = { type: UserFlow; @@ -23,13 +23,18 @@ type Props = { const VerificationCode = ({ type, method, className, hasPasswordButton, target }: Props) => { const [code, setCode] = useState<string[]>([]); const { t } = useTranslation(); - const useVerificationCode = getVerificationCodeHook(type, method); + + const useVerificationCode = getCodeVerificationHookByFlow(type); const errorCallback = useCallback(() => { setCode([]); }, []); - const { errorMessage, clearErrorMessage, onSubmit } = useVerificationCode(target, errorCallback); + const { errorMessage, clearErrorMessage, onSubmit } = useVerificationCode( + method, + target, + errorCallback + ); const { seconds, isRunning, onResendVerificationCode } = useResendVerificationCode( type, @@ -39,9 +44,13 @@ const VerificationCode = ({ type, method, className, hasPasswordButton, target } useEffect(() => { if (code.length === defaultLength && code.every(Boolean)) { - void onSubmit(code.join('')); + const payload = + method === SignInIdentifier.Email + ? { email: target, verificationCode: code.join('') } + : { phone: target, verificationCode: code.join('') }; + void onSubmit(payload); } - }, [code, onSubmit, target]); + }, [code, method, onSubmit, target]); return ( <form className={classNames(styles.form, className)}> diff --git a/packages/ui/src/containers/VerificationCode/use-continue-flow-code-verification.ts b/packages/ui/src/containers/VerificationCode/use-continue-flow-code-verification.ts new file mode 100644 index 000000000..abb4d67a4 --- /dev/null +++ b/packages/ui/src/containers/VerificationCode/use-continue-flow-code-verification.ts @@ -0,0 +1,81 @@ +import type { EmailVerificationCodePayload, PhoneVerificationCodePayload } from '@logto/schemas'; +import { SignInIdentifier } from '@logto/schemas'; +import { useMemo, useCallback } from 'react'; + +import { addProfileWithVerificationCodeIdentifier } from '@/apis/interaction'; +import type { ErrorHandlers } from '@/hooks/use-api'; +import useApi from '@/hooks/use-api'; +import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; +import type { VerificationCodeIdentifier } from '@/types'; +import { SearchParameters } from '@/types'; +import { getSearchParameters } from '@/utils'; + +import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler'; +import useIdentifierErrorAlert, { IdentifierErrorType } from './use-identifier-error-alert'; + +const useContinueFlowCodeVerification = ( + _method: VerificationCodeIdentifier, + target: string, + errorCallback?: () => void +) => { + const { generalVerificationCodeErrorHandlers, errorMessage, clearErrorMessage } = + useGeneralVerificationCodeErrorHandler(); + + const requiredProfileErrorHandler = useRequiredProfileErrorHandler(true); + + const identifierErrorHandler = useIdentifierErrorAlert(); + + const verifyVerificationCodeErrorHandlers: ErrorHandlers = useMemo( + () => ({ + 'user.phone_already_in_use': () => { + void identifierErrorHandler( + IdentifierErrorType.IdentifierAlreadyExists, + SignInIdentifier.Phone, + target + ); + }, + 'user.email_already_in_use': () => { + void identifierErrorHandler( + IdentifierErrorType.IdentifierAlreadyExists, + SignInIdentifier.Email, + target + ); + }, + ...requiredProfileErrorHandler, + ...generalVerificationCodeErrorHandlers, + callback: errorCallback, + }), + [ + errorCallback, + target, + identifierErrorHandler, + requiredProfileErrorHandler, + generalVerificationCodeErrorHandlers, + ] + ); + + const { run: verifyVerificationCode } = useApi( + addProfileWithVerificationCodeIdentifier, + verifyVerificationCodeErrorHandlers + ); + + const onSubmit = useCallback( + async (payload: EmailVerificationCodePayload | PhoneVerificationCodePayload) => { + const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial); + const result = await verifyVerificationCode(payload, socialToBind); + + if (result?.redirectTo) { + window.location.replace(result.redirectTo); + } + }, + [verifyVerificationCode] + ); + + return { + errorMessage, + clearErrorMessage, + onSubmit, + }; +}; + +export default useContinueFlowCodeVerification; diff --git a/packages/ui/src/containers/VerificationCode/use-continue-set-email-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-continue-set-email-verification-code-validation.ts deleted file mode 100644 index 3f3938e26..000000000 --- a/packages/ui/src/containers/VerificationCode/use-continue-set-email-verification-code-validation.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { SignInIdentifier } from '@logto/schemas'; -import { useMemo, useCallback } from 'react'; - -import { addProfileWithVerificationCodeIdentifier } from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; -import { UserFlow, SearchParameters } from '@/types'; -import { getSearchParameters } from '@/utils'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useContinueSetEmailVerificationCode = (email: string, errorCallback?: () => void) => { - const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler(); - - const requiredProfileErrorHandler = useRequiredProfileErrorHandler(true); - - const identifierNotExistErrorHandler = useIdentifierErrorAlert( - UserFlow.continue, - SignInIdentifier.Email, - email - ); - - const verifyVerificationCodeErrorHandlers: ErrorHandlers = useMemo( - () => ({ - 'user.email_already_in_use': identifierNotExistErrorHandler, - ...requiredProfileErrorHandler, - ...sharedErrorHandlers, - callback: errorCallback, - }), - [ - errorCallback, - identifierNotExistErrorHandler, - requiredProfileErrorHandler, - sharedErrorHandlers, - ] - ); - - const { run: verifyVerificationCode } = useApi( - addProfileWithVerificationCodeIdentifier, - verifyVerificationCodeErrorHandlers - ); - - const onSubmit = useCallback( - async (verificationCode: string) => { - const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial); - const result = await verifyVerificationCode({ email, verificationCode }, socialToBind); - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, - [email, verifyVerificationCode] - ); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useContinueSetEmailVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-continue-set-phone-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-continue-set-phone-verification-code-validation.ts deleted file mode 100644 index 541253441..000000000 --- a/packages/ui/src/containers/VerificationCode/use-continue-set-phone-verification-code-validation.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { SignInIdentifier } from '@logto/schemas'; -import { useMemo, useCallback } from 'react'; - -import { addProfileWithVerificationCodeIdentifier } from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; -import { UserFlow, SearchParameters } from '@/types'; -import { getSearchParameters } from '@/utils'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useContinueSetPhoneVerificationCode = (phone: string, errorCallback?: () => void) => { - const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler(); - - const requiredProfileErrorHandler = useRequiredProfileErrorHandler(true); - - const identifierExistErrorHandler = useIdentifierErrorAlert( - UserFlow.continue, - SignInIdentifier.Phone, - phone - ); - - const verifyVerificationCodeErrorHandlers: ErrorHandlers = useMemo( - () => ({ - 'user.phone_already_in_use': identifierExistErrorHandler, - ...requiredProfileErrorHandler, - ...sharedErrorHandlers, - callback: errorCallback, - }), - [errorCallback, identifierExistErrorHandler, requiredProfileErrorHandler, sharedErrorHandlers] - ); - - const { run: verifyVerificationCode } = useApi( - addProfileWithVerificationCodeIdentifier, - verifyVerificationCodeErrorHandlers - ); - - const onSubmit = useCallback( - async (verificationCode: string) => { - const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial); - const result = await verifyVerificationCode({ phone, verificationCode }, socialToBind); - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, - [phone, verifyVerificationCode] - ); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useContinueSetPhoneVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-forgot-password-email-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-forgot-password-email-verification-code-validation.ts deleted file mode 100644 index a2993652c..000000000 --- a/packages/ui/src/containers/VerificationCode/use-forgot-password-email-verification-code-validation.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { SignInIdentifier } from '@logto/schemas'; -import { useMemo, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { verifyForgotPasswordVerificationCodeIdentifier } from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import { UserFlow } from '@/types'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useForgotPasswordEmailVerificationCode = (email: string, errorCallback?: () => void) => { - const navigate = useNavigate(); - const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler(); - - const identifierNotExistErrorHandler = useIdentifierErrorAlert( - UserFlow.forgotPassword, - SignInIdentifier.Email, - email - ); - - const errorHandlers: ErrorHandlers = useMemo( - () => ({ - 'user.user_not_exist': identifierNotExistErrorHandler, - 'user.new_password_required_in_profile': () => { - navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true }); - }, - ...sharedErrorHandlers, - callback: errorCallback, - }), - [identifierNotExistErrorHandler, sharedErrorHandlers, errorCallback, navigate] - ); - - const { result, run: verifyVerificationCode } = useApi( - verifyForgotPasswordVerificationCodeIdentifier, - errorHandlers - ); - - const onSubmit = useCallback( - async (verificationCode: string) => { - return verifyVerificationCode({ email, verificationCode }); - }, - [email, verifyVerificationCode] - ); - - useEffect(() => { - if (result) { - navigate(`/${UserFlow.signIn}`, { replace: true }); - } - }, [navigate, result]); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useForgotPasswordEmailVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-forgot-password-flow-code-verification.ts b/packages/ui/src/containers/VerificationCode/use-forgot-password-flow-code-verification.ts new file mode 100644 index 000000000..0f0fe64c9 --- /dev/null +++ b/packages/ui/src/containers/VerificationCode/use-forgot-password-flow-code-verification.ts @@ -0,0 +1,71 @@ +import type { EmailVerificationCodePayload, PhoneVerificationCodePayload } from '@logto/schemas'; +import { useMemo, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { verifyForgotPasswordVerificationCodeIdentifier } from '@/apis/interaction'; +import type { ErrorHandlers } from '@/hooks/use-api'; +import useApi from '@/hooks/use-api'; +import type { VerificationCodeIdentifier } from '@/types'; +import { UserFlow } from '@/types'; + +import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler'; +import useIdentifierErrorAlert, { IdentifierErrorType } from './use-identifier-error-alert'; + +const useForgotPasswordFlowCodeVerification = ( + method: VerificationCodeIdentifier, + target: string, + errorCallback?: () => void +) => { + const navigate = useNavigate(); + const { generalVerificationCodeErrorHandlers, errorMessage, clearErrorMessage } = + useGeneralVerificationCodeErrorHandler(); + + const identifierErrorHandler = useIdentifierErrorAlert(); + + const errorHandlers: ErrorHandlers = useMemo( + () => ({ + 'user.user_not_exist': () => { + void identifierErrorHandler(IdentifierErrorType.IdentifierNotExist, method, target); + }, + 'user.new_password_required_in_profile': () => { + navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true }); + }, + ...generalVerificationCodeErrorHandlers, + callback: errorCallback, + }), + [ + generalVerificationCodeErrorHandlers, + errorCallback, + identifierErrorHandler, + method, + target, + navigate, + ] + ); + + const { result, run: verifyVerificationCode } = useApi( + verifyForgotPasswordVerificationCodeIdentifier, + errorHandlers + ); + + const onSubmit = useCallback( + async (payload: EmailVerificationCodePayload | PhoneVerificationCodePayload) => { + return verifyVerificationCode(payload); + }, + [verifyVerificationCode] + ); + + useEffect(() => { + if (result) { + navigate(`/${UserFlow.signIn}`, { replace: true }); + } + }, [navigate, result]); + + return { + errorMessage, + clearErrorMessage, + onSubmit, + }; +}; + +export default useForgotPasswordFlowCodeVerification; diff --git a/packages/ui/src/containers/VerificationCode/use-forgot-password-phone-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-forgot-password-phone-verification-code-validation.ts deleted file mode 100644 index 7fcc7cc64..000000000 --- a/packages/ui/src/containers/VerificationCode/use-forgot-password-phone-verification-code-validation.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { SignInIdentifier } from '@logto/schemas'; -import { useMemo, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { verifyForgotPasswordVerificationCodeIdentifier } from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import { UserFlow } from '@/types'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useForgotPasswordPhoneVerificationCode = (phone: string, errorCallback?: () => void) => { - const navigate = useNavigate(); - const { sharedErrorHandlers, errorMessage, clearErrorMessage } = useSharedErrorHandler(); - - const identifierNotExistErrorHandler = useIdentifierErrorAlert( - UserFlow.forgotPassword, - SignInIdentifier.Phone, - phone - ); - - const errorHandlers: ErrorHandlers = useMemo( - () => ({ - 'user.user_not_exist': identifierNotExistErrorHandler, - 'user.new_password_required_in_profile': () => { - navigate(`/${UserFlow.forgotPassword}/reset`, { replace: true }); - }, - ...sharedErrorHandlers, - callback: errorCallback, - }), - [identifierNotExistErrorHandler, sharedErrorHandlers, errorCallback, navigate] - ); - - const { result, run: verifyVerificationCode } = useApi( - verifyForgotPasswordVerificationCodeIdentifier, - errorHandlers - ); - - const onSubmit = useCallback( - async (verificationCode: string) => { - return verifyVerificationCode({ phone, verificationCode }); - }, - [phone, verifyVerificationCode] - ); - - useEffect(() => { - if (result) { - navigate(`/${UserFlow.signIn}`, { replace: true }); - } - }, [navigate, result]); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useForgotPasswordPhoneVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-shared-error-handler.ts b/packages/ui/src/containers/VerificationCode/use-general-verification-code-error-handler.ts similarity index 72% rename from packages/ui/src/containers/VerificationCode/use-shared-error-handler.ts rename to packages/ui/src/containers/VerificationCode/use-general-verification-code-error-handler.ts index 71f87f946..9c5fd75a7 100644 --- a/packages/ui/src/containers/VerificationCode/use-shared-error-handler.ts +++ b/packages/ui/src/containers/VerificationCode/use-general-verification-code-error-handler.ts @@ -2,11 +2,11 @@ import { useState, useMemo } from 'react'; import type { ErrorHandlers } from '@/hooks/use-api'; -const useSharedErrorHandler = () => { +const useGeneralVerificationCodeErrorHandler = () => { const [errorMessage, setErrorMessage] = useState<string>(); // Have to wrap up in a useMemo hook otherwise the handler updates on every cycle - const sharedErrorHandlers: ErrorHandlers = useMemo( + const generalVerificationCodeErrorHandlers: ErrorHandlers = useMemo( () => ({ 'verification_code.expired': (error) => { setErrorMessage(error.message); @@ -20,11 +20,11 @@ const useSharedErrorHandler = () => { return { errorMessage, - sharedErrorHandlers, + generalVerificationCodeErrorHandlers, clearErrorMessage: () => { setErrorMessage(''); }, }; }; -export default useSharedErrorHandler; +export default useGeneralVerificationCodeErrorHandler; diff --git a/packages/ui/src/containers/VerificationCode/use-identifier-error-alert.ts b/packages/ui/src/containers/VerificationCode/use-identifier-error-alert.ts index 39b9a4d64..240bd666b 100644 --- a/packages/ui/src/containers/VerificationCode/use-identifier-error-alert.ts +++ b/packages/ui/src/containers/VerificationCode/use-identifier-error-alert.ts @@ -4,34 +4,44 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useConfirmModal } from '@/hooks/use-confirm-modal'; -import { UserFlow } from '@/types'; +import type { VerificationCodeIdentifier } from '@/types'; -const useIdentifierErrorAlert = ( - flow: UserFlow, - method: SignInIdentifier.Email | SignInIdentifier.Phone, - value: string -) => { +export enum IdentifierErrorType { + IdentifierNotExist = 'IdentifierNotExist', + IdentifierAlreadyExists = 'IdentifierAlreadyExists', +} + +const useIdentifierErrorAlert = () => { const { show } = useConfirmModal(); const navigate = useNavigate(); const { t } = useTranslation(); // Have to wrap up in a useCallback hook otherwise the handler updates on every cycle - return useCallback(async () => { - await show({ - type: 'alert', - ModalContent: t( - flow === UserFlow.register || flow === UserFlow.continue - ? 'description.create_account_id_exists_alert' - : 'description.sign_in_id_does_not_exist_alert', - { - type: t(`description.${method === SignInIdentifier.Email ? 'email' : 'phone_number'}`), - value, - } - ), - cancelText: 'action.got_it', - }); - navigate(-1); - }, [flow, method, navigate, show, t, value]); + return useCallback( + async ( + errorType: IdentifierErrorType, + identifierType: VerificationCodeIdentifier, + identifier: string + ) => { + await show({ + type: 'alert', + ModalContent: t( + errorType === IdentifierErrorType.IdentifierAlreadyExists + ? 'description.create_account_id_exists_alert' + : 'description.sign_in_id_does_not_exist_alert', + { + type: t( + `description.${identifierType === SignInIdentifier.Email ? 'email' : 'phone_number'}` + ), + identifier, + } + ), + cancelText: 'action.got_it', + }); + navigate(-1); + }, + [navigate, show, t] + ); }; export default useIdentifierErrorAlert; diff --git a/packages/ui/src/containers/VerificationCode/use-register-flow-code-verification.ts b/packages/ui/src/containers/VerificationCode/use-register-flow-code-verification.ts new file mode 100644 index 000000000..e33cde0c2 --- /dev/null +++ b/packages/ui/src/containers/VerificationCode/use-register-flow-code-verification.ts @@ -0,0 +1,122 @@ +import type { EmailVerificationCodePayload, PhoneVerificationCodePayload } from '@logto/schemas'; +import { SignInIdentifier, SignInMode } from '@logto/schemas'; +import { useMemo, useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import { + addProfileWithVerificationCodeIdentifier, + signInWithVerifiedIdentifier, +} from '@/apis/interaction'; +import type { ErrorHandlers } from '@/hooks/use-api'; +import useApi from '@/hooks/use-api'; +import { useConfirmModal } from '@/hooks/use-confirm-modal'; +import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; +import { useSieMethods } from '@/hooks/use-sie'; +import type { VerificationCodeIdentifier } from '@/types'; + +import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler'; +import useIdentifierErrorAlert, { IdentifierErrorType } from './use-identifier-error-alert'; + +const useRegisterFlowCodeVerification = ( + method: VerificationCodeIdentifier, + target: string, + errorCallback?: () => void +) => { + const { t } = useTranslation(); + const { show } = useConfirmModal(); + const navigate = useNavigate(); + const { errorMessage, clearErrorMessage, generalVerificationCodeErrorHandlers } = + useGeneralVerificationCodeErrorHandler(); + + const { signInMode } = useSieMethods(); + + const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true); + + const { run: signInWithIdentifierAsync } = useApi( + signInWithVerifiedIdentifier, + requiredProfileErrorHandlers + ); + + const showIdentifierErrorAlert = useIdentifierErrorAlert(); + + const identifierExistErrorHandler = useCallback(async () => { + // Should not redirect user to sign-in if is register-only mode + if (signInMode === SignInMode.Register) { + void showIdentifierErrorAlert(IdentifierErrorType.IdentifierAlreadyExists, method, target); + + return; + } + + const [confirm] = await show({ + confirmText: 'action.sign_in', + ModalContent: t('description.create_account_id_exists', { + type: t(`description.${method === SignInIdentifier.Email ? 'email' : 'phone_number'}`), + value: target, + }), + }); + + if (!confirm) { + navigate(-1); + + return; + } + + const result = await signInWithIdentifierAsync(); + + if (result?.redirectTo) { + window.location.replace(result.redirectTo); + } + }, [ + method, + navigate, + show, + showIdentifierErrorAlert, + signInMode, + signInWithIdentifierAsync, + t, + target, + ]); + + const errorHandlers = useMemo<ErrorHandlers>( + () => ({ + 'user.email_already_in_use': identifierExistErrorHandler, + 'user.phone_already_in_use': identifierExistErrorHandler, + ...generalVerificationCodeErrorHandlers, + ...requiredProfileErrorHandlers, + callback: errorCallback, + }), + [ + errorCallback, + identifierExistErrorHandler, + requiredProfileErrorHandlers, + generalVerificationCodeErrorHandlers, + ] + ); + + const { result, run: verifyVerificationCode } = useApi( + addProfileWithVerificationCodeIdentifier, + errorHandlers + ); + + const onSubmit = useCallback( + async (payload: EmailVerificationCodePayload | PhoneVerificationCodePayload) => { + return verifyVerificationCode(payload); + }, + [verifyVerificationCode] + ); + + useEffect(() => { + if (result?.redirectTo) { + window.location.replace(result.redirectTo); + } + }, [result]); + + return { + errorMessage, + clearErrorMessage, + onSubmit, + }; +}; + +export default useRegisterFlowCodeVerification; diff --git a/packages/ui/src/containers/VerificationCode/use-register-with-email-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-register-with-email-verification-code-validation.ts deleted file mode 100644 index e709bbd05..000000000 --- a/packages/ui/src/containers/VerificationCode/use-register-with-email-verification-code-validation.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { SignInIdentifier, SignInMode } from '@logto/schemas'; -import { useMemo, useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; - -import { - addProfileWithVerificationCodeIdentifier, - signInWithVerifierIdentifier, -} from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import { useConfirmModal } from '@/hooks/use-confirm-modal'; -import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; -import { useSieMethods } from '@/hooks/use-sie'; -import { UserFlow } from '@/types'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useRegisterWithEmailVerificationCode = (email: string, errorCallback?: () => void) => { - const { t } = useTranslation(); - const { show } = useConfirmModal(); - const navigate = useNavigate(); - const { errorMessage, clearErrorMessage, sharedErrorHandlers } = useSharedErrorHandler(); - - const { signInMode } = useSieMethods(); - - const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true); - - const { run: signInWithEmailAsync } = useApi( - signInWithVerifierIdentifier, - requiredProfileErrorHandlers - ); - - const identifierExistErrorHandler = useIdentifierErrorAlert( - UserFlow.register, - SignInIdentifier.Email, - email - ); - - const emailExistSignInErrorHandler = useCallback(async () => { - const [confirm] = await show({ - confirmText: 'action.sign_in', - ModalContent: t('description.create_account_id_exists', { - type: t(`description.email`), - value: email, - }), - }); - - if (!confirm) { - navigate(-1); - - return; - } - - const result = await signInWithEmailAsync(); - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [email, navigate, show, signInWithEmailAsync, t]); - - const errorHandlers = useMemo<ErrorHandlers>( - () => ({ - 'user.email_already_in_use': - signInMode === SignInMode.Register - ? identifierExistErrorHandler - : emailExistSignInErrorHandler, - ...sharedErrorHandlers, - ...requiredProfileErrorHandlers, - callback: errorCallback, - }), - [ - emailExistSignInErrorHandler, - errorCallback, - identifierExistErrorHandler, - requiredProfileErrorHandlers, - sharedErrorHandlers, - signInMode, - ] - ); - - const { result, run: verifyVerificationCode } = useApi( - addProfileWithVerificationCodeIdentifier, - errorHandlers - ); - - const onSubmit = useCallback( - async (verificationCode: string) => { - return verifyVerificationCode({ email, verificationCode }); - }, - [email, verifyVerificationCode] - ); - - useEffect(() => { - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [result]); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useRegisterWithEmailVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-register-with-phone-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-register-with-phone-verification-code-validation.ts deleted file mode 100644 index 977f4cc79..000000000 --- a/packages/ui/src/containers/VerificationCode/use-register-with-phone-verification-code-validation.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SignInIdentifier, SignInMode } from '@logto/schemas'; -import { useMemo, useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; - -import { - addProfileWithVerificationCodeIdentifier, - signInWithVerifierIdentifier, -} from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import { useConfirmModal } from '@/hooks/use-confirm-modal'; -import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; -import { useSieMethods } from '@/hooks/use-sie'; -import { UserFlow } from '@/types'; -import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useRegisterWithPhoneVerificationCode = (phone: string, errorCallback?: () => void) => { - const { t } = useTranslation(); - const { show } = useConfirmModal(); - const navigate = useNavigate(); - const { errorMessage, clearErrorMessage, sharedErrorHandlers } = useSharedErrorHandler(); - const { signInMode } = useSieMethods(); - - const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true); - - const { run: signInWithPhoneAsync } = useApi( - signInWithVerifierIdentifier, - requiredProfileErrorHandlers - ); - - const identifierExistErrorHandler = useIdentifierErrorAlert( - UserFlow.register, - SignInIdentifier.Phone, - formatPhoneNumberWithCountryCallingCode(phone) - ); - - const phoneExistSignInErrorHandler = useCallback(async () => { - const [confirm] = await show({ - confirmText: 'action.sign_in', - ModalContent: t('description.create_account_id_exists', { - type: t(`description.phone_number`), - value: phone, - }), - }); - - if (!confirm) { - navigate(-1); - - return; - } - - const result = await signInWithPhoneAsync(); - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [phone, navigate, show, signInWithPhoneAsync, t]); - - const errorHandlers = useMemo<ErrorHandlers>( - () => ({ - 'user.phone_already_in_use': - signInMode === SignInMode.Register - ? identifierExistErrorHandler - : phoneExistSignInErrorHandler, - ...sharedErrorHandlers, - ...requiredProfileErrorHandlers, - callback: errorCallback, - }), - [ - signInMode, - identifierExistErrorHandler, - phoneExistSignInErrorHandler, - sharedErrorHandlers, - requiredProfileErrorHandlers, - errorCallback, - ] - ); - - const { result, run: verifyVerificationCode } = useApi( - addProfileWithVerificationCodeIdentifier, - errorHandlers - ); - - useEffect(() => { - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [result]); - - const onSubmit = useCallback( - async (verificationCode: string) => { - return verifyVerificationCode({ - phone, - verificationCode, - }); - }, - [phone, verifyVerificationCode] - ); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useRegisterWithPhoneVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/use-sign-in-with-phone-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-sign-in-flow-code-verification.ts similarity index 50% rename from packages/ui/src/containers/VerificationCode/use-sign-in-with-phone-verification-code-validation.ts rename to packages/ui/src/containers/VerificationCode/use-sign-in-flow-code-verification.ts index d11ba4483..f422cf79c 100644 --- a/packages/ui/src/containers/VerificationCode/use-sign-in-with-phone-verification-code-validation.ts +++ b/packages/ui/src/containers/VerificationCode/use-sign-in-flow-code-verification.ts @@ -1,3 +1,4 @@ +import type { EmailVerificationCodePayload, PhoneVerificationCodePayload } from '@logto/schemas'; import { SignInIdentifier, SignInMode } from '@logto/schemas'; import { useMemo, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -12,41 +13,51 @@ import useApi from '@/hooks/use-api'; import { useConfirmModal } from '@/hooks/use-confirm-modal'; import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; import { useSieMethods } from '@/hooks/use-sie'; -import { UserFlow, SearchParameters } from '@/types'; +import type { VerificationCodeIdentifier } from '@/types'; +import { SearchParameters } from '@/types'; import { getSearchParameters } from '@/utils'; -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; +import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler'; +import useIdentifierErrorAlert, { IdentifierErrorType } from './use-identifier-error-alert'; -const useSignInWithPhoneVerificationCode = (phone: string, errorCallback?: () => void) => { +const useSignInFlowCodeVerification = ( + method: VerificationCodeIdentifier, + target: string, + errorCallback?: () => void +) => { const { t } = useTranslation(); const { show } = useConfirmModal(); const navigate = useNavigate(); - const { errorMessage, clearErrorMessage, sharedErrorHandlers } = useSharedErrorHandler(); + + const { errorMessage, clearErrorMessage, generalVerificationCodeErrorHandlers } = + useGeneralVerificationCodeErrorHandler(); const { signInMode } = useSieMethods(); const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true); - const { run: registerWithPhoneAsync } = useApi( + const { run: registerWithIdentifierAsync } = useApi( registerWithVerifiedIdentifier, requiredProfileErrorHandlers ); const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial); - const identifierNotExistErrorHandler = useIdentifierErrorAlert( - UserFlow.signIn, - SignInIdentifier.Phone, - phone - ); + const showIdentifierErrorAlert = useIdentifierErrorAlert(); + + const identifierNotExistErrorHandler = useCallback(async () => { + // Should not redirect user to register if is sign-in only mode or bind social flow + if (signInMode === SignInMode.SignIn || socialToBind) { + void showIdentifierErrorAlert(IdentifierErrorType.IdentifierNotExist, method, target); + + return; + } - const phoneNotExistRegisterErrorHandler = useCallback(async () => { const [confirm] = await show({ confirmText: 'action.create', ModalContent: t('description.sign_in_id_does_not_exist', { - type: t(`description.phone_number`), - value: phone, + ype: t(`description.${method === SignInIdentifier.Email ? 'email' : 'phone_number'}`), + value: target, }), }); @@ -56,32 +67,37 @@ const useSignInWithPhoneVerificationCode = (phone: string, errorCallback?: () => return; } - const result = await registerWithPhoneAsync({ phone }); + const result = await registerWithIdentifierAsync( + method === SignInIdentifier.Email ? { email: target } : { phone: target } + ); if (result?.redirectTo) { window.location.replace(result.redirectTo); } - }, [phone, navigate, show, registerWithPhoneAsync, t]); + }, [ + method, + navigate, + registerWithIdentifierAsync, + show, + showIdentifierErrorAlert, + signInMode, + socialToBind, + t, + target, + ]); const errorHandlers = useMemo<ErrorHandlers>( () => ({ - 'user.user_not_exist': - // Block user auto register if is bind social or sign-in only flow - signInMode === SignInMode.SignIn || socialToBind - ? identifierNotExistErrorHandler - : phoneNotExistRegisterErrorHandler, - ...sharedErrorHandlers, + 'user.user_not_exist': identifierNotExistErrorHandler, + ...generalVerificationCodeErrorHandlers, ...requiredProfileErrorHandlers, callback: errorCallback, }), [ - signInMode, - socialToBind, - identifierNotExistErrorHandler, - phoneNotExistRegisterErrorHandler, - sharedErrorHandlers, - requiredProfileErrorHandlers, errorCallback, + identifierNotExistErrorHandler, + requiredProfileErrorHandlers, + generalVerificationCodeErrorHandlers, ] ); @@ -97,16 +113,10 @@ const useSignInWithPhoneVerificationCode = (phone: string, errorCallback?: () => }, [result]); const onSubmit = useCallback( - async (verificationCode: string) => { - return asyncSignInWithVerificationCodeIdentifier( - { - phone, - verificationCode, - }, - socialToBind - ); + async (payload: EmailVerificationCodePayload | PhoneVerificationCodePayload) => { + return asyncSignInWithVerificationCodeIdentifier(payload, socialToBind); }, - [phone, socialToBind, asyncSignInWithVerificationCodeIdentifier] + [asyncSignInWithVerificationCodeIdentifier, socialToBind] ); return { @@ -116,4 +126,4 @@ const useSignInWithPhoneVerificationCode = (phone: string, errorCallback?: () => }; }; -export default useSignInWithPhoneVerificationCode; +export default useSignInFlowCodeVerification; diff --git a/packages/ui/src/containers/VerificationCode/use-sign-in-with-email-verification-code-validation.ts b/packages/ui/src/containers/VerificationCode/use-sign-in-with-email-verification-code-validation.ts deleted file mode 100644 index bb834c0ab..000000000 --- a/packages/ui/src/containers/VerificationCode/use-sign-in-with-email-verification-code-validation.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { SignInIdentifier, SignInMode } from '@logto/schemas'; -import { useMemo, useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; - -import { - signInWithVerificationCodeIdentifier, - registerWithVerifiedIdentifier, -} from '@/apis/interaction'; -import type { ErrorHandlers } from '@/hooks/use-api'; -import useApi from '@/hooks/use-api'; -import { useConfirmModal } from '@/hooks/use-confirm-modal'; -import useRequiredProfileErrorHandler from '@/hooks/use-required-profile-error-handler'; -import { useSieMethods } from '@/hooks/use-sie'; -import { UserFlow, SearchParameters } from '@/types'; -import { getSearchParameters } from '@/utils'; - -import useIdentifierErrorAlert from './use-identifier-error-alert'; -import useSharedErrorHandler from './use-shared-error-handler'; - -const useSignInWithEmailVerificationCode = (email: string, errorCallback?: () => void) => { - const { t } = useTranslation(); - const { show } = useConfirmModal(); - const navigate = useNavigate(); - const { errorMessage, clearErrorMessage, sharedErrorHandlers } = useSharedErrorHandler(); - - const { signInMode } = useSieMethods(); - - const requiredProfileErrorHandlers = useRequiredProfileErrorHandler(true); - - const { run: registerWithEmailAsync } = useApi( - registerWithVerifiedIdentifier, - requiredProfileErrorHandlers - ); - - const socialToBind = getSearchParameters(location.search, SearchParameters.bindWithSocial); - - const identifierNotExistErrorHandler = useIdentifierErrorAlert( - UserFlow.signIn, - SignInIdentifier.Email, - email - ); - - const emailNotExistRegisterErrorHandler = useCallback(async () => { - const [confirm] = await show({ - confirmText: 'action.create', - ModalContent: t('description.sign_in_id_does_not_exist', { - type: t(`description.email`), - value: email, - }), - }); - - if (!confirm) { - navigate(-1); - - return; - } - - const result = await registerWithEmailAsync({ email }); - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [email, navigate, show, registerWithEmailAsync, t]); - - const errorHandlers = useMemo<ErrorHandlers>( - () => ({ - 'user.user_not_exist': - // Block user auto register if is bind social or sign-in only flow - signInMode === SignInMode.SignIn || socialToBind - ? identifierNotExistErrorHandler - : emailNotExistRegisterErrorHandler, - ...sharedErrorHandlers, - ...requiredProfileErrorHandlers, - callback: errorCallback, - }), - [ - emailNotExistRegisterErrorHandler, - errorCallback, - identifierNotExistErrorHandler, - requiredProfileErrorHandlers, - sharedErrorHandlers, - signInMode, - socialToBind, - ] - ); - - const { result, run: asyncSignInWithVerificationCodeIdentifier } = useApi( - signInWithVerificationCodeIdentifier, - errorHandlers - ); - - useEffect(() => { - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, [result]); - - const onSubmit = useCallback( - async (verificationCode: string) => { - return asyncSignInWithVerificationCodeIdentifier( - { - email, - verificationCode, - }, - socialToBind - ); - }, - [asyncSignInWithVerificationCodeIdentifier, email, socialToBind] - ); - - return { - errorMessage, - clearErrorMessage, - onSubmit, - }; -}; - -export default useSignInWithEmailVerificationCode; diff --git a/packages/ui/src/containers/VerificationCode/utils.ts b/packages/ui/src/containers/VerificationCode/utils.ts index 5a04246d9..f78d5ac43 100644 --- a/packages/ui/src/containers/VerificationCode/utils.ts +++ b/packages/ui/src/containers/VerificationCode/utils.ts @@ -1,36 +1,15 @@ -import { SignInIdentifier } from '@logto/schemas'; - import { UserFlow } from '@/types'; -import useContinueSetEmailVerificationCode from './use-continue-set-email-verification-code-validation'; -import useContinueSetPhoneVerificationCode from './use-continue-set-phone-verification-code-validation'; -import useForgotPasswordEmailVerificationCode from './use-forgot-password-email-verification-code-validation'; -import useForgotPasswordPhoneVerificationCode from './use-forgot-password-phone-verification-code-validation'; -import useRegisterWithEmailVerificationCode from './use-register-with-email-verification-code-validation'; -import useRegisterWithPhoneVerificationCode from './use-register-with-phone-verification-code-validation'; -import useSignInWithEmailVerificationCode from './use-sign-in-with-email-verification-code-validation'; -import useSignInWithPhoneVerificationCode from './use-sign-in-with-phone-verification-code-validation'; +import useContinueFlowCodeVerification from './use-continue-flow-code-verification'; +import useForgotPasswordFlowCodeVerification from './use-forgot-password-flow-code-verification'; +import useRegisterFlowCodeVerification from './use-register-flow-code-verification'; +import useSignInFlowCodeVerification from './use-sign-in-flow-code-verification'; -export const getVerificationCodeHook = ( - type: UserFlow, - method: SignInIdentifier.Email | SignInIdentifier.Phone -) => { - switch (type) { - case UserFlow.signIn: - return method === SignInIdentifier.Email - ? useSignInWithEmailVerificationCode - : useSignInWithPhoneVerificationCode; - case UserFlow.register: - return method === SignInIdentifier.Email - ? useRegisterWithEmailVerificationCode - : useRegisterWithPhoneVerificationCode; - case UserFlow.forgotPassword: - return method === SignInIdentifier.Email - ? useForgotPasswordEmailVerificationCode - : useForgotPasswordPhoneVerificationCode; - default: - return method === SignInIdentifier.Email - ? useContinueSetEmailVerificationCode - : useContinueSetPhoneVerificationCode; - } -}; +export const codeVerificationHooks = Object.freeze({ + [UserFlow.signIn]: useSignInFlowCodeVerification, + [UserFlow.register]: useRegisterFlowCodeVerification, + [UserFlow.forgotPassword]: useForgotPasswordFlowCodeVerification, + [UserFlow.continue]: useContinueFlowCodeVerification, +}); + +export const getCodeVerificationHookByFlow = (flow: UserFlow) => codeVerificationHooks[flow]; diff --git a/packages/ui/src/pages/VerificationCode/index.tsx b/packages/ui/src/pages/VerificationCode/index.tsx index 590e17c6f..34448a45a 100644 --- a/packages/ui/src/pages/VerificationCode/index.tsx +++ b/packages/ui/src/pages/VerificationCode/index.tsx @@ -1,6 +1,6 @@ import { t } from 'i18next'; import { useParams, useLocation } from 'react-router-dom'; -import { is } from 'superstruct'; +import { is, validate } from 'superstruct'; import SecondaryPageWrapper from '@/components/SecondaryPageWrapper'; import VerificationCodeContainer from '@/containers/VerificationCode'; @@ -15,7 +15,7 @@ import { import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code'; type Parameters = { - type: UserFlow; + type: string; method: string; }; @@ -24,11 +24,12 @@ const VerificationCode = () => { const { signInMethods } = useSieMethods(); const { state } = useLocation(); - const invalidType = !is(type, userFlowGuard); const invalidMethod = !is(method, verificationCodeMethodGuard); const invalidState = !is(state, verificationCodeStateGuard); - if (invalidType || invalidMethod) { + const [, flow] = validate(type, userFlowGuard); + + if (!flow || invalidMethod) { return <ErrorPage />; } @@ -55,7 +56,7 @@ const VerificationCode = () => { }} > <VerificationCodeContainer - type={type} + type={flow} method={method} target={target} hasPasswordButton={type === UserFlow.signIn && methodSettings?.password} diff --git a/packages/ui/src/types/guard.ts b/packages/ui/src/types/guard.ts index 11bfa1aa4..a1a48c94a 100644 --- a/packages/ui/src/types/guard.ts +++ b/packages/ui/src/types/guard.ts @@ -1,6 +1,8 @@ import { SignInIdentifier, MissingProfile } from '@logto/schemas'; import * as s from 'superstruct'; +import { UserFlow } from '.'; + export const bindSocialStateGuard = s.object({ relatedUser: s.object({ type: s.union([s.literal('email'), s.literal('phone')]), @@ -24,11 +26,11 @@ export const SignInMethodGuard = s.union([ s.literal(SignInIdentifier.Username), ]); -export const userFlowGuard = s.union([ - s.literal('sign-in'), - s.literal('register'), - s.literal('forgot-password'), - s.literal('continue'), +export const userFlowGuard = s.enums([ + UserFlow.signIn, + UserFlow.register, + UserFlow.forgotPassword, + UserFlow.continue, ]); export const continueMethodGuard = s.union([ diff --git a/packages/ui/src/types/index.ts b/packages/ui/src/types/index.ts index 78e52188a..ede6e284b 100644 --- a/packages/ui/src/types/index.ts +++ b/packages/ui/src/types/index.ts @@ -1,4 +1,9 @@ -import type { SignInExperience, ConnectorMetadata, AppearanceMode } from '@logto/schemas'; +import type { + SignInExperience, + ConnectorMetadata, + AppearanceMode, + SignInIdentifier, +} from '@logto/schemas'; export enum UserFlow { signIn = 'sign-in', @@ -18,6 +23,8 @@ export type Platform = 'web' | 'mobile'; // TODO: @simeng, @sijie, @charles should we combine this with admin console? export type Theme = 'dark' | 'light'; +export type VerificationCodeIdentifier = SignInIdentifier.Email | SignInIdentifier.Phone; + // Omit socialSignInConnectorTargets since it is being translated into socialConnectors export type SignInExperienceResponse = Omit<SignInExperience, 'socialSignInConnectorTargets'> & { socialConnectors: ConnectorMetadata[];