From ede343ac127500896f1eb27927847e3bc0cfbfa9 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 28 Aug 2024 16:32:22 +0800 Subject: [PATCH] refactor(experience): refactor the code verificatin api refactor the code verification api --- packages/experience/src/apis/utils.ts | 69 +++++++++---------- .../VerificationCode/index.test.tsx | 12 ++-- .../use-continue-flow-code-verification.ts | 6 +- .../use-resend-verification-code.ts | 35 ++++++++-- 4 files changed, 69 insertions(+), 53 deletions(-) diff --git a/packages/experience/src/apis/utils.ts b/packages/experience/src/apis/utils.ts index 0f9935a8458..0d5ef846fbe 100644 --- a/packages/experience/src/apis/utils.ts +++ b/packages/experience/src/apis/utils.ts @@ -1,51 +1,44 @@ import { InteractionEvent, type VerificationCodeIdentifier } from '@logto/schemas'; +import { validate } from 'superstruct'; import { type ContinueFlowInteractionEvent, UserFlow } from '@/types'; +import { continueFlowStateGuard } from '@/types/guard'; import { initInteraction, sendVerificationCode } from './experience'; -/** Move to API */ +// Consider deprecate this, remove the `UserFlow.Continue` case +// Align the flow definition with the interaction event +export const userFlowToInteractionEventMap = Object.freeze({ + [UserFlow.SignIn]: InteractionEvent.SignIn, + [UserFlow.Register]: InteractionEvent.Register, + [UserFlow.ForgotPassword]: InteractionEvent.ForgotPassword, +}); + +/** + * This method is used to get the interaction event from the location state + * For continue flow, the interaction event is stored in the location state, + * we need to retrieve it from the state in order to send the verification code with the correct interaction event template + */ +export const getInteractionEventFromState = (state: unknown) => { + if (!state) { + return; + } + + const [, continueFlowState] = validate(state, continueFlowStateGuard); + + return continueFlowState?.interactionEvent; +}; + export const sendVerificationCodeApi = async ( - type: UserFlow, + flow: UserFlow, identifier: VerificationCodeIdentifier, interactionEvent?: ContinueFlowInteractionEvent ) => { - switch (type) { - case UserFlow.SignIn: { - await initInteraction(InteractionEvent.SignIn); - return sendVerificationCode(InteractionEvent.SignIn, identifier); - } - case UserFlow.Register: { - await initInteraction(InteractionEvent.Register); - return sendVerificationCode(InteractionEvent.Register, identifier); - } - case UserFlow.ForgotPassword: { - await initInteraction(InteractionEvent.ForgotPassword); - return sendVerificationCode(InteractionEvent.ForgotPassword, identifier); - } - case UserFlow.Continue: { - return sendVerificationCode(interactionEvent ?? InteractionEvent.SignIn, identifier); - } + if (flow === UserFlow.Continue) { + return sendVerificationCode(interactionEvent ?? InteractionEvent.SignIn, identifier); } -}; -export const resendVerificationCodeApi = async ( - type: UserFlow, - identifier: VerificationCodeIdentifier -) => { - switch (type) { - case UserFlow.SignIn: { - return sendVerificationCode(InteractionEvent.SignIn, identifier); - } - case UserFlow.Register: { - return sendVerificationCode(InteractionEvent.Register, identifier); - } - case UserFlow.ForgotPassword: { - return sendVerificationCode(InteractionEvent.ForgotPassword, identifier); - } - case UserFlow.Continue: { - // Continue flow does not have its own email template, always use sign-in template for now - return sendVerificationCode(InteractionEvent.SignIn, identifier); - } - } + const event = userFlowToInteractionEventMap[flow]; + await initInteraction(event); + return sendVerificationCode(event, identifier); }; diff --git a/packages/experience/src/containers/VerificationCode/index.test.tsx b/packages/experience/src/containers/VerificationCode/index.test.tsx index ee37b49c0e2..3d8e21751b3 100644 --- a/packages/experience/src/containers/VerificationCode/index.test.tsx +++ b/packages/experience/src/containers/VerificationCode/index.test.tsx @@ -7,8 +7,11 @@ import { import { act, fireEvent, waitFor } from '@testing-library/react'; import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; -import { identifyWithVerificationCode, updateProfileWithVerificationCode } from '@/apis/experience'; -import { resendVerificationCodeApi } from '@/apis/utils'; +import { + identifyWithVerificationCode, + updateProfileWithVerificationCode, + sendVerificationCode, +} from '@/apis/experience'; import { setupI18nForTesting } from '@/jest.setup'; import { UserFlow } from '@/types'; @@ -29,11 +32,12 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('@/apis/utils', () => ({ + ...jest.requireActual('@/apis/utils'), sendVerificationCodeApi: jest.fn(), - resendVerificationCodeApi: jest.fn(), })); jest.mock('@/apis/experience', () => ({ + sendVerificationCode: jest.fn(), identifyWithVerificationCode: jest.fn().mockResolvedValue({ redirectTo: '/redirect' }), updateProfileWithVerificationCode: jest.fn().mockResolvedValue({ redirectTo: '/redirect' }), })); @@ -123,7 +127,7 @@ describe('', () => { fireEvent.click(resendButton); }); - expect(resendVerificationCodeApi).toBeCalledWith(UserFlow.SignIn, emailIdentifier); + expect(sendVerificationCode).toBeCalledWith(InteractionEvent.SignIn, emailIdentifier); // Reset i18n await setupI18nForTesting(); diff --git a/packages/experience/src/containers/VerificationCode/use-continue-flow-code-verification.ts b/packages/experience/src/containers/VerificationCode/use-continue-flow-code-verification.ts index 292e1ba707f..e338bb85501 100644 --- a/packages/experience/src/containers/VerificationCode/use-continue-flow-code-verification.ts +++ b/packages/experience/src/containers/VerificationCode/use-continue-flow-code-verification.ts @@ -2,17 +2,16 @@ import type { VerificationCodeIdentifier } from '@logto/schemas'; import { VerificationType } from '@logto/schemas'; import { useCallback, useContext, useMemo } from 'react'; import { useLocation, useSearchParams } from 'react-router-dom'; -import { validate } from 'superstruct'; import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext'; import { updateProfileWithVerificationCode } from '@/apis/experience'; +import { getInteractionEventFromState } from '@/apis/utils'; import useApi from '@/hooks/use-api'; import type { ErrorHandlers } from '@/hooks/use-error-handler'; import useErrorHandler from '@/hooks/use-error-handler'; import useGlobalRedirectTo from '@/hooks/use-global-redirect-to'; import usePreSignInErrorHandler from '@/hooks/use-pre-sign-in-error-handler'; import { SearchParameters } from '@/types'; -import { continueFlowStateGuard } from '@/types/guard'; import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler'; import useIdentifierErrorAlert, { IdentifierErrorType } from './use-identifier-error-alert'; @@ -27,9 +26,8 @@ const useContinueFlowCodeVerification = ( const redirectTo = useGlobalRedirectTo(); const { state } = useLocation(); - const [, continueFlowState] = validate(state, continueFlowStateGuard); const { verificationIdsMap } = useContext(UserInteractionContext); - const interactionEvent = continueFlowState?.interactionEvent; + const interactionEvent = getInteractionEventFromState(state); const handleError = useErrorHandler(); const verifyVerificationCode = useApi(updateProfileWithVerificationCode); diff --git a/packages/experience/src/containers/VerificationCode/use-resend-verification-code.ts b/packages/experience/src/containers/VerificationCode/use-resend-verification-code.ts index 29956c66ebd..1024501cd21 100644 --- a/packages/experience/src/containers/VerificationCode/use-resend-verification-code.ts +++ b/packages/experience/src/containers/VerificationCode/use-resend-verification-code.ts @@ -1,14 +1,16 @@ -import { type VerificationCodeIdentifier } from '@logto/schemas'; +import { InteractionEvent, type VerificationCodeIdentifier } from '@logto/schemas'; import { t } from 'i18next'; -import { useCallback, useContext } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; import { useTimer } from 'react-timer-hook'; import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext'; -import { resendVerificationCodeApi } from '@/apis/utils'; +import { sendVerificationCode } from '@/apis/experience'; +import { getInteractionEventFromState, userFlowToInteractionEventMap } from '@/apis/utils'; import useApi from '@/hooks/use-api'; import useErrorHandler from '@/hooks/use-error-handler'; import useToast from '@/hooks/use-toast'; -import type { UserFlow } from '@/types'; +import { UserFlow } from '@/types'; import { codeVerificationTypeMap } from '@/utils/sign-in-experience'; export const timeRange = 59; @@ -22,6 +24,17 @@ const getTimeout = () => { const useResendVerificationCode = (flow: UserFlow, identifier: VerificationCodeIdentifier) => { const { setToast } = useToast(); + const { state } = useLocation(); + + const interactionEvent = useMemo(() => { + if (flow === UserFlow.Continue) { + const interactionEvent = getInteractionEventFromState(state); + console.log('interactionEvent', interactionEvent); + return interactionEvent ?? InteractionEvent.SignIn; + } + + return userFlowToInteractionEventMap[flow]; + }, [flow, state]); const { seconds, isRunning, restart } = useTimer({ autoStart: true, @@ -29,11 +42,11 @@ const useResendVerificationCode = (flow: UserFlow, identifier: VerificationCodeI }); const handleError = useErrorHandler(); - const sendVerificationCode = useApi(resendVerificationCodeApi); + const resendVerificationCode = useApi(sendVerificationCode); const { setVerificationId } = useContext(UserInteractionContext); const onResendVerificationCode = useCallback(async () => { - const [error, result] = await sendVerificationCode(flow, identifier); + const [error, result] = await resendVerificationCode(interactionEvent, identifier); if (error) { await handleError(error); @@ -47,7 +60,15 @@ const useResendVerificationCode = (flow: UserFlow, identifier: VerificationCodeI setToast(t('description.passcode_sent')); restart(getTimeout(), true); } - }, [flow, handleError, identifier, restart, sendVerificationCode, setToast, setVerificationId]); + }, [ + resendVerificationCode, + interactionEvent, + identifier, + handleError, + setVerificationId, + setToast, + restart, + ]); return { seconds,