import { Box, Button, Text } from "@prismicio/editor-ui"; import { uniqueId } from "lodash"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState, } from "react"; import { toast } from "react-toastify"; export type ActionQueueStatus = "pending" | "done" | "failed"; type UseActionQueueArgs = { actionQueueStatusDelay?: number; errorMessage: string; retryDelay?: number; retryMessage?: string; }; type UseActionQueueReturnType = { actionQueueStatus: ActionQueueStatus; setNextAction: (nextAction: NextAction) => void; }; type ActionQueueStack = { pendingAction: NextAction | undefined; nextAction: NextAction | undefined; }; type NextAction = () => Promise; export const useActionQueue = ( args: UseActionQueueArgs, ): UseActionQueueReturnType => { const { actionQueueStatusDelay = 300, errorMessage, retryDelay = 1000, retryMessage = "Retry", } = args; const [actionQueueStack, setActionQueueStack] = useState({ pendingAction: undefined, nextAction: undefined, }); const [actionQueueStatusActual, setActionQueueStatusActual] = useState("done"); const [actionQueueStatusDelayed, setActionQueueStatusDelayed] = useState(actionQueueStatusActual); const setNextAction = useCallback((nextAction: NextAction) => { setActionQueueStack((prevState) => ({ ...prevState, nextAction, })); }, []); const executeAction = useCallback( async (nextAction?: NextAction) => { if (nextAction) { setActionQueueStatusActual("pending"); try { await nextAction(); setActionQueueStatusActual("done"); } catch (error) { setActionQueueStatusActual("failed"); console.error(errorMessage, error); toastError({ errorMessage, retryDelay, retryMessage, setActionQueueStatusActual, setActionQueueStack, }); } } }, [errorMessage, retryDelay, retryMessage], ); useEffect(() => { if (actionQueueStatusActual === "done" && actionQueueStack.nextAction) { void executeAction(actionQueueStack.nextAction); setActionQueueStack({ pendingAction: actionQueueStack.nextAction, nextAction: undefined, }); } }, [actionQueueStatusActual, actionQueueStack, executeAction]); useEffect(() => { if (actionQueueStatusActual === "pending") { setActionQueueStatusDelayed("pending"); } else { const delayedTimeout = setTimeout(() => { setActionQueueStatusDelayed(actionQueueStatusActual); }, actionQueueStatusDelay); return () => { clearTimeout(delayedTimeout); }; } return; }, [actionQueueStatusActual, actionQueueStatusDelay]); return useMemo( () => ({ actionQueueStatus: actionQueueStatusDelayed, setNextAction, }), [actionQueueStatusDelayed, setNextAction], ); }; type ToastErrorArgs = { errorMessage: string; retryDelay: number; retryMessage: string; setActionQueueStatusActual: Dispatch>; setActionQueueStack: Dispatch>; }; function toastError(args: ToastErrorArgs) { const { errorMessage, retryDelay, retryMessage, setActionQueueStatusActual, setActionQueueStack, } = args; const toastId = uniqueId(); toast.error( () => ( {errorMessage} ), { autoClose: false, closeOnClick: false, draggable: false, toastId, }, ); }