Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / hooks / useAsyncModalHandles.ts
blob5e7be1f3f40331ba6f288fd5a54ac35ff704da30
1 import { useCallback, useMemo, useRef, useState } from 'react';
3 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
4 import type { MaybePromise } from '@proton/pass/types';
5 import noop from '@proton/utils/noop';
7 import { useRerender } from './useRerender';
9 export class AsyncModalAbortedError extends Error {}
10 type ModalState<T> = T & Omit<ModalProps, 'onSubmit'> & { loading: boolean };
11 type HookOptions<T> = { getInitialModalState: () => T };
12 export type UseAsyncModalHandle<V, T> = (options: UseAsyncModalHandlerOptions<V, T>) => Promise<void>;
14 type UseAsyncModalHandlerOptions<V, T> = Partial<T> & {
15     onError?: (error: unknown) => MaybePromise<void>;
16     onAbort?: () => MaybePromise<void>;
17     onSubmit: (value: V) => MaybePromise<unknown>;
20 export const useAsyncModalHandles = <V, T = {}>(options: HookOptions<T>) => {
21     const [key, next] = useRerender();
22     const [state, setState] = useState<ModalState<T>>({
23         ...options.getInitialModalState(),
24         open: false,
25         loading: false,
26     });
28     const resolver = useRef<(value: V) => void>(noop);
29     const rejector = useRef<(error: unknown) => void>(noop);
30     const resolve = useCallback((value: V) => resolver.current?.(value), []);
32     const abort = useCallback(() => rejector.current?.(new AsyncModalAbortedError()), []);
34     const handler = useCallback<UseAsyncModalHandle<V, T>>(
35         async (opts) => {
36             next();
38             const { onSubmit, onError, onAbort, ...modalOptions } = opts;
39             setState({ ...options.getInitialModalState(), ...modalOptions, open: true, loading: false });
41             try {
42                 const value = await new Promise<V>((resolve, reject) => {
43                     resolver.current = resolve;
44                     rejector.current = reject;
45                 });
47                 setState((state) => ({ ...state, loading: true }));
48                 await onSubmit(value);
49             } catch (error) {
50                 setState((state) => ({ ...state, loading: false }));
52                 if (error instanceof AsyncModalAbortedError) await onAbort?.();
53                 else await onError?.(error);
54             } finally {
55                 setState((state) => ({ ...state, open: false, loading: false }));
56             }
57         },
58         [options.getInitialModalState]
59     );
61     return useMemo(() => ({ handler, abort, state, resolver: resolve, key }), [handler, abort, state, key]);