0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

refactor(experience): refactor usePreSignInErrorHandler hook (#7178)

refactor(experience): refactor preSignInErrorHandler hook

refactor preSignInErrorHandler hook
This commit is contained in:
simeng-li 2025-03-25 13:34:58 +08:00 committed by GitHub
parent 552a368482
commit 08b7b010c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 139 additions and 77 deletions

View file

@ -1,3 +1,4 @@
import { InteractionEvent } from '@logto/schemas';
import { useState, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@ -6,8 +7,8 @@ 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 { useSieMethods } from '@/hooks/use-sie';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
const useRegisterWithUsername = () => {
const navigate = useNavigate();
@ -30,7 +31,9 @@ const useRegisterWithUsername = () => {
[]
);
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
const preRegisterErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.Register, {
replace: true,
});
const handleError = useErrorHandler();
const asyncRegister = useApi(registerWithUsername);
@ -41,14 +44,14 @@ const useRegisterWithUsername = () => {
const [error, result] = await asyncSubmitInteraction();
if (error) {
await handleError(error, preSignInErrorHandler);
await handleError(error, preRegisterErrorHandler);
return;
}
if (result) {
await redirectTo(result.redirectTo);
}
}, [asyncSubmitInteraction, handleError, preSignInErrorHandler, redirectTo]);
}, [asyncSubmitInteraction, handleError, preRegisterErrorHandler, redirectTo]);
const onSubmit = useCallback(
async (username: string) => {

View file

@ -1,14 +1,15 @@
import { InteractionEvent } from '@logto/schemas';
import { useCallback } from 'react';
import { bindSocialRelatedUser } from '@/apis/experience';
import useApi from '@/hooks/use-api';
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 useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
const useBindSocialRelatedUser = () => {
const handleError = useErrorHandler();
const preSignInErrorHandler = usePreSignInErrorHandler();
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn);
const redirectTo = useGlobalRedirectTo();
const asyncBindSocialRelatedUser = useApi(bindSocialRelatedUser);

View file

@ -1,5 +1,5 @@
import type { VerificationCodeIdentifier } from '@logto/schemas';
import { VerificationType } from '@logto/schemas';
import { InteractionEvent, VerificationType } from '@logto/schemas';
import { useCallback, useContext, useMemo } from 'react';
import { useLocation, useSearchParams } from 'react-router-dom';
@ -10,7 +10,7 @@ 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 useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import { SearchParameters } from '@/types';
import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler';
@ -27,7 +27,7 @@ const useContinueFlowCodeVerification = (
const { state } = useLocation();
const { verificationIdsMap } = useContext(UserInteractionContext);
const interactionEvent = getInteractionEventFromState(state);
const interactionEvent = getInteractionEventFromState(state) ?? InteractionEvent.SignIn;
const handleError = useErrorHandler();
const verifyVerificationCode = useApi(updateProfileWithVerificationCode);
@ -35,7 +35,9 @@ const useContinueFlowCodeVerification = (
const { generalVerificationCodeErrorHandlers, errorMessage, clearErrorMessage } =
useGeneralVerificationCodeErrorHandler();
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true, interactionEvent });
const submitInteractionErrorHandler = useSubmitInteractionErrorHandler(interactionEvent, {
replace: true,
});
const showIdentifierErrorAlert = useIdentifierErrorAlert();
const showLinkSocialConfirmModal = useLinkSocialConfirmModal();
@ -65,10 +67,14 @@ const useContinueFlowCodeVerification = (
() => ({
'user.phone_already_in_use': identifierExistsErrorHandler,
'user.email_already_in_use': identifierExistsErrorHandler,
...preSignInErrorHandler,
...submitInteractionErrorHandler,
...generalVerificationCodeErrorHandlers,
}),
[preSignInErrorHandler, generalVerificationCodeErrorHandlers, identifierExistsErrorHandler]
[
submitInteractionErrorHandler,
generalVerificationCodeErrorHandlers,
identifierExistsErrorHandler,
]
);
const onSubmit = useCallback(

View file

@ -13,8 +13,8 @@ import { useConfirmModal } from '@/hooks/use-confirm-modal';
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 { useSieMethods } from '@/hooks/use-sie';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code';
import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler';
@ -40,12 +40,11 @@ const useRegisterFlowCodeVerification = (
const { errorMessage, clearErrorMessage, generalVerificationCodeErrorHandlers } =
useGeneralVerificationCodeErrorHandler();
const preRegisterErrorHandler = usePreSignInErrorHandler({
const preRegisterErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.Register, {
replace: true,
interactionEvent: InteractionEvent.Register,
});
const preSignInErrorHandler = usePreSignInErrorHandler({
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});

View file

@ -13,8 +13,8 @@ import { useConfirmModal } from '@/hooks/use-confirm-modal';
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 { useSieMethods } from '@/hooks/use-sie';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code';
import useGeneralVerificationCodeErrorHandler from './use-general-verification-code-error-handler';
@ -37,10 +37,11 @@ const useSignInFlowCodeVerification = (
const { errorMessage, clearErrorMessage, generalVerificationCodeErrorHandlers } =
useGeneralVerificationCodeErrorHandler();
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
const preRegisterErrorHandler = usePreSignInErrorHandler({
interactionEvent: InteractionEvent.Register,
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});
const preRegisterErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.Register, {
replace: true,
});
const showIdentifierErrorAlert = useIdentifierErrorAlert();

View file

@ -1,4 +1,8 @@
import { SignInIdentifier, type PasswordVerificationPayload } from '@logto/schemas';
import {
InteractionEvent,
SignInIdentifier,
type PasswordVerificationPayload,
} from '@logto/schemas';
import { useCallback, useMemo, useState } from 'react';
import { signInWithPasswordIdentifier } from '@/apis/experience';
@ -8,7 +12,7 @@ import type { ErrorHandlers } from '@/hooks/use-error-handler';
import useErrorHandler from '@/hooks/use-error-handler';
import useGlobalRedirectTo from './use-global-redirect-to';
import usePreSignInErrorHandler from './use-pre-sign-in-error-handler';
import useSubmitInteractionErrorHandler from './use-submit-interaction-error-handler';
const usePasswordSignIn = () => {
const [errorMessage, setErrorMessage] = useState<string>();
@ -21,7 +25,7 @@ const usePasswordSignIn = () => {
const handleError = useErrorHandler();
const asyncSignIn = useApi(signInWithPasswordIdentifier);
const preSignInErrorHandler = usePreSignInErrorHandler();
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn);
const errorHandlers: ErrorHandlers = useMemo(
() => ({

View file

@ -1,26 +0,0 @@
import { useMemo } from 'react';
import { type ErrorHandlers } from './use-error-handler';
import useMfaErrorHandler, {
type Options as UseMfaVerificationErrorHandlerOptions,
} from './use-mfa-error-handler';
import useRequiredProfileErrorHandler, {
type Options as UseRequiredProfileErrorHandlerOptions,
} from './use-required-profile-error-handler';
type Options = UseRequiredProfileErrorHandlerOptions & UseMfaVerificationErrorHandlerOptions;
const usePreSignInErrorHandler = ({ replace, ...rest }: Options = {}): ErrorHandlers => {
const requiredProfileErrorHandler = useRequiredProfileErrorHandler({ replace, ...rest });
const mfaErrorHandler = useMfaErrorHandler({ replace });
return useMemo(
() => ({
...requiredProfileErrorHandler,
...mfaErrorHandler,
}),
[mfaErrorHandler, requiredProfileErrorHandler]
);
};
export default usePreSignInErrorHandler;

View file

@ -1,4 +1,4 @@
import { type BindMfaPayload, type VerifyMfaPayload } from '@logto/schemas';
import { InteractionEvent, type BindMfaPayload, type VerifyMfaPayload } from '@logto/schemas';
import { useCallback } from 'react';
import { bindMfa, verifyMfa } from '@/apis/experience';
@ -7,7 +7,7 @@ import { UserMfaFlow } from '@/types';
import useApi from './use-api';
import useErrorHandler, { type ErrorHandlers } from './use-error-handler';
import useGlobalRedirectTo from './use-global-redirect-to';
import usePreSignInErrorHandler from './use-pre-sign-in-error-handler';
import useSubmitInteractionErrorHandler from './use-submit-interaction-error-handler';
export type SendMfaPayloadApiOptions =
| {
@ -30,7 +30,16 @@ const sendMfaPayloadApi = async ({ flow, payload, verificationId }: SendMfaPaylo
const useSendMfaPayload = () => {
const asyncSendMfaPayload = useApi(sendMfaPayloadApi);
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
/**
* TODO: @simeng-li
* Need to find a better implementation.
* In the registration event, MFA binding flow is triggered after user creation,
* so the error handle logic is same as the pre-sign-in event.
* This is confusing and should be refactored.
*/
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});
const handleError = useErrorHandler();
const redirectTo = useGlobalRedirectTo();

View file

@ -1,3 +1,4 @@
import { InteractionEvent } from '@logto/schemas';
import { useCallback } from 'react';
import { skipMfa } from '@/apis/experience';
@ -5,14 +6,23 @@ import { skipMfa } from '@/apis/experience';
import useApi from './use-api';
import useErrorHandler from './use-error-handler';
import useGlobalRedirectTo from './use-global-redirect-to';
import usePreSignInErrorHandler from './use-pre-sign-in-error-handler';
import useSubmitInteractionErrorHandler from './use-submit-interaction-error-handler';
const useSkipMfa = () => {
const asyncSkipMfa = useApi(skipMfa);
const redirectTo = useGlobalRedirectTo();
const handleError = useErrorHandler();
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
/**
* TODO: @simeng-li
* Need to find a better implementation.
* In the registration event, MFA binding flow is triggered after user creation,
* so the error handle logic is same as the pre-sign-in event.
* This is confusing and should be refactored.
*/
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});
return useCallback(async () => {
const [error, result] = await asyncSkipMfa();

View file

@ -1,3 +1,4 @@
import { InteractionEvent } from '@logto/schemas';
import { useCallback } from 'react';
import { signInAndLinkWithSocial } from '@/apis/experience';
@ -5,13 +6,15 @@ import useApi from '@/hooks/use-api';
import useErrorHandler from './use-error-handler';
import useGlobalRedirectTo from './use-global-redirect-to';
import usePreSignInErrorHandler from './use-pre-sign-in-error-handler';
import useSubmitInteractionErrorHandler from './use-submit-interaction-error-handler';
const useLinkSocial = () => {
const handleError = useErrorHandler();
const asyncLinkWithSocial = useApi(signInAndLinkWithSocial);
const redirectTo = useGlobalRedirectTo();
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});
return useCallback(
async (identifierVerificationId: string, socialVerificationId: string) => {

View file

@ -7,7 +7,7 @@ import { registerWithVerifiedIdentifier } from '@/apis/experience';
import useApi from './use-api';
import useErrorHandler from './use-error-handler';
import useGlobalRedirectTo from './use-global-redirect-to';
import usePreSignInErrorHandler from './use-pre-sign-in-error-handler';
import useSubmitInteractionErrorHandler from './use-submit-interaction-error-handler';
import useTerms from './use-terms';
const useSocialRegister = (connectorId: string, replace?: boolean) => {
@ -17,10 +17,9 @@ const useSocialRegister = (connectorId: string, replace?: boolean) => {
const { termsValidation, agreeToTermsPolicy } = useTerms();
const navigate = useNavigate();
const preRegisterErrorHandler = usePreSignInErrorHandler({
const preRegisterErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.Register, {
linkSocial: connectorId,
replace,
interactionEvent: InteractionEvent.Register,
});
return useCallback(

View file

@ -0,0 +1,49 @@
import { useMemo } from 'react';
import { type ContinueFlowInteractionEvent } from '@/types';
import { type ErrorHandlers } from './use-error-handler';
import useMfaErrorHandler, {
type Options as UseMfaVerificationErrorHandlerOptions,
} from './use-mfa-error-handler';
import useRequiredProfileErrorHandler, {
type Options as UseRequiredProfileErrorHandlerOptions,
} from './use-required-profile-error-handler';
type Options = Omit<UseRequiredProfileErrorHandlerOptions, 'interactionEvent'> &
UseMfaVerificationErrorHandlerOptions;
/**
* Error handlers for sign-in and registration interaction submissions.
* Handles both profile completion and MFA verification requirements.
*
* Flow:
* - Sign-in: Profile completion and MFA verification are triggered during interaction submission
* - Register: Profile completion is triggered during user creation (identification phase)
*/
const useSubmitInteractionErrorHandler = (
/**
* Current interaction event 'SignIn' or 'Register'.
* This value is passed to the profile fulfillment flow
* when additional user profile information is required.
*/
interactionEvent: ContinueFlowInteractionEvent,
{ replace, ...rest }: Options = {}
): ErrorHandlers => {
const requiredProfileErrorHandler = useRequiredProfileErrorHandler({
replace,
interactionEvent,
...rest,
});
const mfaErrorHandler = useMfaErrorHandler({ replace });
return useMemo(
() => ({
...requiredProfileErrorHandler,
...mfaErrorHandler,
}),
[mfaErrorHandler, requiredProfileErrorHandler]
);
};
export default useSubmitInteractionErrorHandler;

View file

@ -11,8 +11,8 @@ import useErrorHandler from '@/hooks/use-error-handler';
import useGlobalRedirectTo from '@/hooks/use-global-redirect-to';
import usePasswordPolicyChecker from '@/hooks/use-password-policy-checker';
import usePasswordRejectionErrorHandler from '@/hooks/use-password-rejection-handler';
import usePreSignInErrorHandler from '@/hooks/use-pre-sign-in-error-handler';
import { usePasswordPolicy } from '@/hooks/use-sie';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import { type ContinueFlowInteractionEvent } from '@/types';
type Props = {
@ -35,7 +35,9 @@ const SetPassword = ({ interactionEvent }: Props) => {
const handleError = useErrorHandler();
const passwordRejectionErrorHandler = usePasswordRejectionErrorHandler({ setErrorMessage });
const preSignInErrorHandler = usePreSignInErrorHandler({ interactionEvent, replace: true });
const submitInteractionErrorHandler = useSubmitInteractionErrorHandler(interactionEvent, {
replace: true,
});
const errorHandlers: ErrorHandlers = useMemo(
() => ({
@ -43,10 +45,10 @@ const SetPassword = ({ interactionEvent }: Props) => {
await show({ type: 'alert', ModalContent: error.message, cancelText: 'action.got_it' });
navigate(-1);
},
...preSignInErrorHandler,
...submitInteractionErrorHandler,
...passwordRejectionErrorHandler,
}),
[navigate, passwordRejectionErrorHandler, preSignInErrorHandler, show]
[navigate, passwordRejectionErrorHandler, submitInteractionErrorHandler, show]
);
const onSubmitHandler = useCallback(

View file

@ -6,7 +6,7 @@ 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 useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import { type ContinueFlowInteractionEvent } from '@/types';
const useSetUsername = (interactionEvent: ContinueFlowInteractionEvent) => {
@ -20,18 +20,16 @@ const useSetUsername = (interactionEvent: ContinueFlowInteractionEvent) => {
const handleError = useErrorHandler();
const redirectTo = useGlobalRedirectTo();
const preSignInErrorHandler = usePreSignInErrorHandler({
interactionEvent,
});
const submitInteractionErrorHandler = useSubmitInteractionErrorHandler(interactionEvent);
const errorHandlers: ErrorHandlers = useMemo(
() => ({
'user.username_already_in_use': (error) => {
setErrorMessage(error.message);
},
...preSignInErrorHandler,
...submitInteractionErrorHandler,
}),
[preSignInErrorHandler]
[submitInteractionErrorHandler]
);
const onSubmit = useCallback(

View file

@ -1,4 +1,4 @@
import { SignInIdentifier } from '@logto/schemas';
import { InteractionEvent, SignInIdentifier } from '@logto/schemas';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@ -11,8 +11,8 @@ import useErrorHandler, { type ErrorHandlers } from '@/hooks/use-error-handler';
import useGlobalRedirectTo from '@/hooks/use-global-redirect-to';
import usePasswordPolicyChecker from '@/hooks/use-password-policy-checker';
import usePasswordRejectionErrorHandler from '@/hooks/use-password-rejection-handler';
import usePreSignInErrorHandler from '@/hooks/use-pre-sign-in-error-handler';
import { usePasswordPolicy, useSieMethods } from '@/hooks/use-sie';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import ErrorPage from '../ErrorPage';
@ -31,7 +31,9 @@ const RegisterPassword = () => {
const asyncRegisterPassword = useApi(continueRegisterWithPassword);
const handleError = useErrorHandler();
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
const preRegisterErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.Register, {
replace: true,
});
const passwordRejectionErrorHandler = usePasswordRejectionErrorHandler({ setErrorMessage });
const errorHandlers: ErrorHandlers = useMemo(
@ -41,10 +43,10 @@ const RegisterPassword = () => {
await show({ type: 'alert', ModalContent: error.message, cancelText: 'action.got_it' });
navigate(-1);
},
...preSignInErrorHandler,
...preRegisterErrorHandler,
...passwordRejectionErrorHandler,
}),
[preSignInErrorHandler, passwordRejectionErrorHandler, show, navigate]
[preRegisterErrorHandler, passwordRejectionErrorHandler, show, navigate]
);
const onSubmitHandler = useCallback(

View file

@ -16,9 +16,9 @@ import useBindSocialRelatedUser from '@/containers/SocialLinkAccount/use-social-
import useApi from '@/hooks/use-api';
import type { ErrorHandlers } from '@/hooks/use-error-handler';
import useErrorHandler from '@/hooks/use-error-handler';
import usePreSignInErrorHandler from '@/hooks/use-pre-sign-in-error-handler';
import { useSieMethods } from '@/hooks/use-sie';
import useSocialRegister from '@/hooks/use-social-register';
import useSubmitInteractionErrorHandler from '@/hooks/use-submit-interaction-error-handler';
import useToast from '@/hooks/use-toast';
import { socialAccountNotExistErrorDataGuard } from '@/types/guard';
import { parseQueryParameters } from '@/utils';
@ -102,7 +102,9 @@ const useSocialSignInListener = (connectorId: string) => {
[navigate, setToast]
);
const preSignInErrorHandler = usePreSignInErrorHandler({ replace: true });
const preSignInErrorHandler = useSubmitInteractionErrorHandler(InteractionEvent.SignIn, {
replace: true,
});
const signInWithSocialErrorHandlers: ErrorHandlers = useMemo(
() => ({