mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(experience): support and apply modal loading state (#6236)
* refactor(experience): support and apply modal loading state * feat(experience): support cancel loading for modal
This commit is contained in:
parent
bc2ccf671e
commit
6c4f051cfe
14 changed files with 350 additions and 158 deletions
|
@ -6,30 +6,38 @@ import type { ModalProps } from '@/components/ConfirmModal';
|
|||
import { WebModal, MobileModal } from '@/components/ConfirmModal';
|
||||
import usePlatform from '@/hooks/use-platform';
|
||||
|
||||
export type ModalContentRenderProps = {
|
||||
confirm: (data?: unknown) => void;
|
||||
cancel: (data?: unknown) => void;
|
||||
};
|
||||
|
||||
type ConfirmModalType = 'alert' | 'confirm';
|
||||
|
||||
type ConfirmModalState = Omit<ModalProps, 'onClose' | 'onConfirm' | 'children'> & {
|
||||
ModalContent: string | ((props: ModalContentRenderProps) => Nullable<JSX.Element>);
|
||||
ModalContent: string | (() => Nullable<JSX.Element>);
|
||||
type: ConfirmModalType;
|
||||
isConfirmLoading?: boolean;
|
||||
isCancelLoading?: boolean;
|
||||
};
|
||||
|
||||
type ConfirmModalProps = Omit<ConfirmModalState, 'isOpen' | 'type'> & { type?: ConfirmModalType };
|
||||
/**
|
||||
* Props for promise-based modal usage
|
||||
*/
|
||||
type PromiseConfirmModalProps = Omit<ConfirmModalState, 'isOpen' | 'type' | 'isConfirmLoading'> & {
|
||||
type?: ConfirmModalType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for callback-based modal usage
|
||||
*/
|
||||
export type CallbackConfirmModalProps = PromiseConfirmModalProps & {
|
||||
onConfirm?: () => Promise<void> | void;
|
||||
onCancel?: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
type ConfirmModalContextType = {
|
||||
show: (props: ConfirmModalProps) => Promise<[boolean, unknown?]>;
|
||||
confirm: (data?: unknown) => void;
|
||||
cancel: (data?: unknown) => void;
|
||||
showPromise: (props: PromiseConfirmModalProps) => Promise<[boolean, unknown?]>;
|
||||
showCallback: (props: CallbackConfirmModalProps) => void;
|
||||
};
|
||||
|
||||
export const ConfirmModalContext = createContext<ConfirmModalContextType>({
|
||||
show: async () => [true],
|
||||
confirm: noop,
|
||||
cancel: noop,
|
||||
showPromise: async () => [true],
|
||||
showCallback: noop,
|
||||
});
|
||||
|
||||
type Props = {
|
||||
|
@ -40,49 +48,90 @@ const defaultModalState: ConfirmModalState = {
|
|||
isOpen: false,
|
||||
type: 'confirm',
|
||||
ModalContent: () => null,
|
||||
isConfirmLoading: false,
|
||||
isCancelLoading: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* ConfirmModalProvider component
|
||||
*
|
||||
* This component provides a context for managing confirm modals throughout the application.
|
||||
* It supports both promise-based and callback-based usage patterns. see `usePromiseConfirmModal` and `useConfirmModal` hooks.
|
||||
*/
|
||||
const ConfirmModalProvider = ({ children }: Props) => {
|
||||
const [modalState, setModalState] = useState<ConfirmModalState>(defaultModalState);
|
||||
|
||||
const resolver = useRef<(value: [result: boolean, data?: unknown]) => void>();
|
||||
const callbackRef = useRef<{
|
||||
onConfirm?: () => Promise<void> | void;
|
||||
onCancel?: () => Promise<void> | void;
|
||||
}>({});
|
||||
|
||||
const { isMobile } = usePlatform();
|
||||
|
||||
const ConfirmModal = isMobile ? MobileModal : WebModal;
|
||||
|
||||
const handleShow = useCallback(async ({ type = 'confirm', ...props }: ConfirmModalProps) => {
|
||||
resolver.current?.([false]);
|
||||
const handleShowPromise = useCallback(
|
||||
async ({ type = 'confirm', ...props }: PromiseConfirmModalProps) => {
|
||||
resolver.current?.([false]);
|
||||
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
type,
|
||||
...props,
|
||||
});
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
type,
|
||||
isConfirmLoading: false,
|
||||
isCancelLoading: false,
|
||||
...props,
|
||||
});
|
||||
|
||||
return new Promise<[result: boolean, data?: unknown]>((resolve) => {
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
resolver.current = resolve;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleShowCallback = useCallback(
|
||||
({ type = 'confirm', onConfirm, onCancel, ...props }: CallbackConfirmModalProps) => {
|
||||
resolver.current?.([false]);
|
||||
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
type,
|
||||
isConfirmLoading: false,
|
||||
...props,
|
||||
});
|
||||
|
||||
return new Promise<[result: boolean, data?: unknown]>((resolve) => {
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
resolver.current = resolve;
|
||||
});
|
||||
}, []);
|
||||
callbackRef.current = { onConfirm, onCancel };
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleConfirm = useCallback((data?: unknown) => {
|
||||
const handleConfirm = useCallback(async (data?: unknown) => {
|
||||
if (callbackRef.current.onConfirm) {
|
||||
setModalState((previous) => ({ ...previous, isConfirmLoading: true }));
|
||||
await callbackRef.current.onConfirm();
|
||||
}
|
||||
resolver.current?.([true, data]);
|
||||
setModalState(defaultModalState);
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback((data?: unknown) => {
|
||||
const handleCancel = useCallback(async (data?: unknown) => {
|
||||
if (callbackRef.current.onCancel) {
|
||||
setModalState((previous) => ({ ...previous, isCancelLoading: true }));
|
||||
await callbackRef.current.onCancel();
|
||||
}
|
||||
resolver.current?.([false, data]);
|
||||
setModalState(defaultModalState);
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
show: handleShow,
|
||||
confirm: handleConfirm,
|
||||
cancel: handleCancel,
|
||||
showPromise: handleShowPromise,
|
||||
showCallback: handleShowCallback,
|
||||
}),
|
||||
[handleCancel, handleConfirm, handleShow]
|
||||
[handleShowPromise, handleShowCallback]
|
||||
);
|
||||
|
||||
const { ModalContent, type, ...restProps } = modalState;
|
||||
|
@ -95,19 +144,15 @@ const ConfirmModalProvider = ({ children }: Props) => {
|
|||
onConfirm={
|
||||
type === 'confirm'
|
||||
? () => {
|
||||
handleConfirm();
|
||||
void handleConfirm();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onClose={() => {
|
||||
handleCancel();
|
||||
void handleCancel();
|
||||
}}
|
||||
>
|
||||
{typeof ModalContent === 'string' ? (
|
||||
ModalContent
|
||||
) : (
|
||||
<ModalContent confirm={handleConfirm} cancel={handleCancel} />
|
||||
)}
|
||||
{typeof ModalContent === 'string' ? ModalContent : <ModalContent />}
|
||||
</ConfirmModal>
|
||||
</ConfirmModalContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { useConfirmModal, usePromiseConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
|
||||
import ConfirmModalProvider from '.';
|
||||
|
||||
const confirmHandler = jest.fn();
|
||||
const cancelHandler = jest.fn();
|
||||
|
||||
const ConfirmModalTestComponent = () => {
|
||||
const { show } = useConfirmModal();
|
||||
const PromiseConfirmModalTestComponent = () => {
|
||||
const { show } = usePromiseConfirmModal();
|
||||
|
||||
const onClick = async () => {
|
||||
const [result] = await show({ ModalContent: 'confirm modal content' });
|
||||
|
@ -26,82 +26,178 @@ const ConfirmModalTestComponent = () => {
|
|||
return <button onClick={onClick}>show modal</button>;
|
||||
};
|
||||
|
||||
const CallbackConfirmModalTestComponent = () => {
|
||||
const { show } = useConfirmModal();
|
||||
|
||||
const onClick = () => {
|
||||
show({
|
||||
ModalContent: 'confirm modal content',
|
||||
onConfirm: confirmHandler,
|
||||
onCancel: cancelHandler,
|
||||
});
|
||||
};
|
||||
|
||||
return <button onClick={onClick}>show modal</button>;
|
||||
};
|
||||
|
||||
describe('confirm modal provider', () => {
|
||||
it('render confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<ConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
describe('promise confirm modal', () => {
|
||||
it('render confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<PromiseConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
it('confirm callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<PromiseConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
});
|
||||
|
||||
const confirm = getByText('action.confirm');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(confirm);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(confirmHandler).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('cancel callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<PromiseConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
});
|
||||
|
||||
const cancel = getByText('action.cancel');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(cancel);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(cancelHandler).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('confirm callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<ConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
describe('callback confirm modal', () => {
|
||||
it('render confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<CallbackConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
it('confirm callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<CallbackConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.confirm')).not.toBeNull();
|
||||
});
|
||||
|
||||
const confirm = getByText('action.confirm');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(confirm);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(confirmHandler).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const confirm = getByText('action.confirm');
|
||||
it('cancel callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<CallbackConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(confirm);
|
||||
});
|
||||
const trigger = getByText('show modal');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(confirmHandler).toBeCalled();
|
||||
});
|
||||
});
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
|
||||
it('cancel callback of confirm modal', async () => {
|
||||
const { queryByText, getByText } = render(
|
||||
<ConfirmModalProvider>
|
||||
<ConfirmModalTestComponent />
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
});
|
||||
|
||||
const trigger = getByText('show modal');
|
||||
const cancel = getByText('action.cancel');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger);
|
||||
});
|
||||
act(() => {
|
||||
fireEvent.click(cancel);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('confirm modal content')).not.toBeNull();
|
||||
expect(queryByText('action.cancel')).not.toBeNull();
|
||||
});
|
||||
|
||||
const cancel = getByText('action.cancel');
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(cancel);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(cancelHandler).toBeCalled();
|
||||
await waitFor(() => {
|
||||
expect(cancelHandler).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,8 @@ import type { ModalProps } from './type';
|
|||
const AcModal = ({
|
||||
className,
|
||||
isOpen = false,
|
||||
isConfirmLoading = false,
|
||||
isCancelLoading = false,
|
||||
children,
|
||||
cancelText = 'action.cancel',
|
||||
confirmText = 'action.confirm',
|
||||
|
@ -62,6 +64,7 @@ const AcModal = ({
|
|||
type="secondary"
|
||||
i18nProps={cancelTextI18nProps}
|
||||
size="small"
|
||||
isLoading={isCancelLoading}
|
||||
onClick={onClose}
|
||||
/>
|
||||
{onConfirm && (
|
||||
|
@ -69,6 +72,7 @@ const AcModal = ({
|
|||
title={confirmText}
|
||||
i18nProps={confirmTextI18nProps}
|
||||
size="small"
|
||||
isLoading={isConfirmLoading}
|
||||
onClick={onConfirm}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -11,6 +11,8 @@ import type { ModalProps } from './type';
|
|||
const MobileModal = ({
|
||||
className,
|
||||
isOpen = false,
|
||||
isConfirmLoading = false,
|
||||
isCancelLoading = false,
|
||||
children,
|
||||
cancelText = 'action.cancel',
|
||||
confirmText = 'action.confirm',
|
||||
|
@ -34,11 +36,17 @@ const MobileModal = ({
|
|||
<Button
|
||||
title={cancelText}
|
||||
i18nProps={cancelTextI18nProps}
|
||||
isLoading={isCancelLoading}
|
||||
type="secondary"
|
||||
onClick={onClose}
|
||||
/>
|
||||
{onConfirm && (
|
||||
<Button title={confirmText} i18nProps={confirmTextI18nProps} onClick={onConfirm} />
|
||||
<Button
|
||||
title={confirmText}
|
||||
i18nProps={confirmTextI18nProps}
|
||||
isLoading={isConfirmLoading}
|
||||
onClick={onConfirm}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,8 @@ import type { ReactNode } from 'react';
|
|||
export type ModalProps = {
|
||||
className?: string;
|
||||
isOpen?: boolean;
|
||||
isConfirmLoading?: boolean;
|
||||
isCancelLoading?: boolean;
|
||||
children: ReactNode;
|
||||
cancelText?: TFuncKey;
|
||||
confirmText?: TFuncKey;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { usePromiseConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import type { VerificationCodeIdentifier } from '@/types';
|
||||
import { formatPhoneNumberWithCountryCallingCode } from '@/utils/country-code';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export enum IdentifierErrorType {
|
|||
}
|
||||
|
||||
const useIdentifierErrorAlert = () => {
|
||||
const { show } = useConfirmModal();
|
||||
const { show } = usePromiseConfirmModal();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const useLinkSocialConfirmModal = () => {
|
|||
|
||||
return useCallback(
|
||||
async (method: VerificationCodeIdentifier, target: string, connectorId: string) => {
|
||||
const [confirm] = await show({
|
||||
show({
|
||||
confirmText: 'action.bind_and_continue',
|
||||
cancelText: 'action.change',
|
||||
cancelTextI18nProps: {
|
||||
|
@ -29,15 +29,13 @@ const useLinkSocialConfirmModal = () => {
|
|||
? formatPhoneNumberWithCountryCallingCode(target)
|
||||
: target,
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
await linkWithSocial(connectorId);
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
navigate(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await linkWithSocial(connectorId);
|
||||
},
|
||||
[linkWithSocial, navigate, show, t]
|
||||
);
|
||||
|
|
|
@ -50,7 +50,7 @@ const useRegisterFlowCodeVerification = (
|
|||
return;
|
||||
}
|
||||
|
||||
const [confirm] = await show({
|
||||
show({
|
||||
confirmText: 'action.sign_in',
|
||||
ModalContent: t('description.create_account_id_exists', {
|
||||
type: t(`description.${method === SignInIdentifier.Email ? 'email' : 'phone_number'}`),
|
||||
|
@ -59,25 +59,23 @@ const useRegisterFlowCodeVerification = (
|
|||
? formatPhoneNumberWithCountryCallingCode(target)
|
||||
: target,
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
const [error, result] = await signInWithIdentifierAsync();
|
||||
|
||||
if (error) {
|
||||
await handleError(error, preSignInErrorHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.redirectTo) {
|
||||
redirectTo(result.redirectTo);
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
navigate(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const [error, result] = await signInWithIdentifierAsync();
|
||||
|
||||
if (error) {
|
||||
await handleError(error, preSignInErrorHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.redirectTo) {
|
||||
redirectTo(result.redirectTo);
|
||||
}
|
||||
}, [
|
||||
handleError,
|
||||
method,
|
||||
|
|
|
@ -51,7 +51,7 @@ const useSignInFlowCodeVerification = (
|
|||
return;
|
||||
}
|
||||
|
||||
const [confirm] = await show({
|
||||
show({
|
||||
confirmText: 'action.create',
|
||||
ModalContent: t('description.sign_in_id_does_not_exist', {
|
||||
type: t(`description.${method === SignInIdentifier.Email ? 'email' : 'phone_number'}`),
|
||||
|
@ -60,27 +60,25 @@ const useSignInFlowCodeVerification = (
|
|||
? formatPhoneNumberWithCountryCallingCode(target)
|
||||
: target,
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
const [error, result] = await registerWithIdentifierAsync(
|
||||
method === SignInIdentifier.Email ? { email: target } : { phone: target }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
await handleError(error, preSignInErrorHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.redirectTo) {
|
||||
redirectTo(result.redirectTo);
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(-1);
|
||||
},
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
navigate(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const [error, result] = await registerWithIdentifierAsync(
|
||||
method === SignInIdentifier.Email ? { email: target } : { phone: target }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
await handleError(error, preSignInErrorHandler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.redirectTo) {
|
||||
redirectTo(result.redirectTo);
|
||||
}
|
||||
}, [
|
||||
signInMode,
|
||||
show,
|
||||
|
|
|
@ -2,6 +2,49 @@ import { useContext } from 'react';
|
|||
|
||||
import { ConfirmModalContext } from '@/Providers/ConfirmModalProvider';
|
||||
|
||||
export type { ModalContentRenderProps } from '@/Providers/ConfirmModalProvider';
|
||||
/**
|
||||
* Hook for using the promise-based confirm modal
|
||||
*
|
||||
* @returns An object with a `show` method that returns a promise
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const { show } = usePromiseConfirmModal();
|
||||
* const [result] = await show({ ModalContent: 'Are you sure?' });
|
||||
* if (result) {
|
||||
* // User confirmed
|
||||
* }
|
||||
*```
|
||||
*/
|
||||
export const usePromiseConfirmModal = () => {
|
||||
const { showPromise } = useContext(ConfirmModalContext);
|
||||
|
||||
export const useConfirmModal = () => useContext(ConfirmModalContext);
|
||||
return { show: showPromise };
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for using the callback-based confirm modal
|
||||
*
|
||||
* @returns An object with a `show` method that accepts callbacks
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* const { show } = useConfirmModal();
|
||||
* show({
|
||||
* ModalContent: 'Are you sure?',
|
||||
* onConfirm: async () => {
|
||||
* // This will automatically set the confirm button to loading state
|
||||
* await someAsyncOperation();
|
||||
* },
|
||||
* onCancel: async () => {
|
||||
* // This will automatically set the cancel button to loading state
|
||||
* await someAsyncOperation();
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const useConfirmModal = () => {
|
||||
const { showCallback } = useContext(ConfirmModalContext);
|
||||
|
||||
return { show: showCallback };
|
||||
};
|
||||
|
|
|
@ -5,11 +5,11 @@ import { useCallback, useContext, useMemo } from 'react';
|
|||
import PageContext from '@/Providers/PageContextProvider/PageContext';
|
||||
import TermsAndPrivacyConfirmModalContent from '@/containers/TermsAndPrivacyConfirmModalContent';
|
||||
|
||||
import { useConfirmModal } from './use-confirm-modal';
|
||||
import { usePromiseConfirmModal } from './use-confirm-modal';
|
||||
|
||||
const useTerms = () => {
|
||||
const { termsAgreement, setTermsAgreement, experienceSettings } = useContext(PageContext);
|
||||
const { show } = useConfirmModal();
|
||||
const { show } = usePromiseConfirmModal();
|
||||
|
||||
const { termsOfUseUrl, privacyPolicyUrl, isTermsDisabled, agreeToTermsPolicy } = useMemo(() => {
|
||||
const { termsOfUseUrl, privacyPolicyUrl, agreeToTermsPolicy } = experienceSettings ?? {};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
|
||||
import { addProfile } from '@/apis/interaction';
|
||||
import SetPasswordForm from '@/containers/SetPassword';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { usePromiseConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import type { ErrorHandlers } from '@/hooks/use-error-handler';
|
||||
import useGlobalRedirectTo from '@/hooks/use-global-redirect-to';
|
||||
import usePasswordAction, { type SuccessHandler } from '@/hooks/use-password-action';
|
||||
|
@ -18,7 +18,7 @@ const SetPassword = () => {
|
|||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { show } = useConfirmModal();
|
||||
const { show } = usePromiseConfirmModal();
|
||||
const redirectTo = useGlobalRedirectTo();
|
||||
|
||||
const preSignInErrorHandler = usePreSignInErrorHandler();
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
|
||||
import { setUserPassword } from '@/apis/interaction';
|
||||
import SetPassword from '@/containers/SetPassword';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { usePromiseConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { type ErrorHandlers } from '@/hooks/use-error-handler';
|
||||
import useGlobalRedirectTo from '@/hooks/use-global-redirect-to';
|
||||
import useMfaErrorHandler from '@/hooks/use-mfa-error-handler';
|
||||
|
@ -19,7 +19,7 @@ const RegisterPassword = () => {
|
|||
|
||||
const navigate = useNavigate();
|
||||
const redirectTo = useGlobalRedirectTo();
|
||||
const { show } = useConfirmModal();
|
||||
const { show } = usePromiseConfirmModal();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const clearErrorMessage = useCallback(() => {
|
||||
setErrorMessage(undefined);
|
||||
|
|
|
@ -6,7 +6,7 @@ import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
|
|||
import UserInteractionContext from '@/Providers/UserInteractionContextProvider/UserInteractionContext';
|
||||
import { setUserPassword } from '@/apis/interaction';
|
||||
import SetPassword from '@/containers/SetPassword';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { usePromiseConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { type ErrorHandlers } from '@/hooks/use-error-handler';
|
||||
import usePasswordAction, { type SuccessHandler } from '@/hooks/use-password-action';
|
||||
import { usePasswordPolicy } from '@/hooks/use-sie';
|
||||
|
@ -20,7 +20,7 @@ const ResetPassword = () => {
|
|||
const { t } = useTranslation();
|
||||
const { setToast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { show } = useConfirmModal();
|
||||
const { show } = usePromiseConfirmModal();
|
||||
const { setForgotPasswordIdentifierInputValue } = useContext(UserInteractionContext);
|
||||
const errorHandlers: ErrorHandlers = useMemo(
|
||||
() => ({
|
||||
|
|
Loading…
Reference in a new issue