diff --git a/client/src/App.tsx b/client/src/App.tsx index 5afdf39..8aaf056 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,4 @@ -import { AuthProvider, ThemeProvider } from "@/lib/hooks"; +import { AuthProvider, ThemeProvider, UnsavedWarnProvider } from "@/lib/hooks"; import { TooltipProvider } from "@/ui"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; @@ -26,11 +26,13 @@ function App() { - }> - + + }> + - - + + + diff --git a/client/src/components/CustomDialog/CustomDialog.tsx b/client/src/components/CustomDialog/CustomDialog.tsx index 8b96df2..a5205de 100644 --- a/client/src/components/CustomDialog/CustomDialog.tsx +++ b/client/src/components/CustomDialog/CustomDialog.tsx @@ -23,7 +23,7 @@ interface CustomDialogProps { export const CustomDialog = ({ isOpen, - onCancel: onClose, + onCancel, onConfirm, title, description, @@ -39,7 +39,7 @@ export const CustomDialog = ({ - + {cancelLabel} diff --git a/client/src/components/Layout/Layout.tsx b/client/src/components/Layout/Layout.tsx index 4eee4ed..c2696e4 100644 --- a/client/src/components/Layout/Layout.tsx +++ b/client/src/components/Layout/Layout.tsx @@ -1,10 +1,9 @@ -import { UnsavedChangesNotifier, UnsavedWarnProvider } from "@/lib/hooks"; +import { UnsavedWarnProvider } from "@/lib/hooks"; import { PropsWithChildren } from "react"; export const Layout = ({ children }: PropsWithChildren) => (
{children}
-
); diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx index c49d5aa..80a0a66 100644 --- a/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx +++ b/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx @@ -1,18 +1,55 @@ -import { PropsWithChildren, createContext, useState } from "react"; +import { CustomDialog } from "@/components"; +import { NullOr } from "@shared/utilities"; +import { PropsWithChildren, createContext, useCallback, useMemo, useState } from "react"; +import { UnsavedChangesNotifierProps } from "./useUnsavedChangesNotifier"; export interface IUnsavedWarnContextState { - warnWhen?: boolean; - setWarnWhen?: (value: boolean) => void; + show: (options: NullOr) => void; } -export const UnsavedWarnContext = createContext({}); +export const UnsavedWarnContext = createContext>(null); export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => { - const [warnWhen, setWarnWhen] = useState(false); + const [confirm, setConfirm] = useState>(null); + + const [open, toggle] = useState(false); + + const show = useCallback( + (confirmOptions: NullOr) => { + setConfirm(confirmOptions); + toggle(true); + }, + [toggle, setConfirm] + ); + + const onConfirm = () => { + confirm?.onConfirm?.(); + toggle(false); + }; + + const onCancel = () => { + confirm?.onCancel?.(); + toggle(false); + }; + + const value = useMemo(() => ({ show }), [show]); return ( - + {children} + { + console.log("onCancel"); + onCancel(); + }} + onConfirm={() => onConfirm()} + title={confirm?.title} + description={confirm?.subtitle} + confirmLabel={confirm?.confirmText} + cancelLabel={confirm?.cancelText} + isOpen={open} + /> ); }; diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx deleted file mode 100644 index d435857..0000000 --- a/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/** - * `useBlocker` and `usePrompt` is no longer part of react-router-dom for the routers other than `DataRouter`. - * - * The previous workaround ( boolean, when = true) { - const { navigator } = React.useContext(NavigationContext); - - React.useEffect(() => { - if (!when) { - return; - } - - const go = navigator.go; - const push = navigator.push; - - navigator.push = (...args: Parameters) => { - const result = confirmExit(); - if (result !== false) { - push(...args); - } - }; - - navigator.go = (...args: Parameters) => { - const result = confirmExit(); - if (result !== false) { - go(...args); - } - }; - - return () => { - navigator.push = push; - navigator.go = go; - }; - }, [navigator, confirmExit, when]); -} - -export function usePrompt(message: string, when = true, onConfirm?: () => void, legacy = false) { - const warnWhenListener = React.useCallback( - (e: { preventDefault: () => void; returnValue: string }) => { - e.preventDefault(); - - e.returnValue = message; - - return e.returnValue; - }, - [message] - ); - - React.useEffect(() => { - if (when && !legacy) { - window.addEventListener("beforeunload", warnWhenListener); - } - - return () => { - window.removeEventListener("beforeunload", warnWhenListener); - }; - }, [warnWhenListener, when, legacy]); - - const confirmExit = React.useCallback(() => { - const confirm = window.confirm(message); - if (confirm && onConfirm) { - onConfirm(); - } - return confirm; - }, [message]); - - console.log(when); - - useConfirmExit(confirmExit, when); -} diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx index c73aa56..ed43115 100644 --- a/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx +++ b/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx @@ -1,44 +1,72 @@ -import { CustomDialog } from "@/components"; -import { t } from "i18next"; -import { useEffect, useMemo } from "react"; -import { useLocation } from "react-router-dom"; +import { useCallback, useEffect } from "react"; +import { useBlocker } from "react-router-dom"; import { useWarnAboutChange } from "./useWarnAboutChange"; -type UnsavedChangesNotifierProps = { - message?: string; +export type UnsavedChangesNotifierProps = { + title?: string; + subtitle?: string; + confirmText?: string; + cancelText?: string; + onConfirm?: () => void; + onCancel?: () => void; + type?: "success" | "error" | "warning" | "info"; }; -export const UnsavedChangesNotifier = ({ - message = t("unsaved_changes_prompt"), -}: UnsavedChangesNotifierProps) => { - const { pathname } = useLocation(); - const { warnWhen, setWarnWhen } = useWarnAboutChange(); +export const useUnsavedChangesNotifier = ({ + isDirty = false, + title = "Se han detectado cambios", + subtitle = "Atención, hay cambios pendientes de guardar en esta página.\nSi continúa, perderá los cambios.", + confirmText = "Continuar", + cancelText = "No continuar", + onConfirm, + onCancel, + type = "warning", +}: UnsavedChangesNotifierProps & { isDirty?: boolean }) => { + const blocker = useBlocker(isDirty); + const { show } = useWarnAboutChange(); + + const confirm = useCallback(() => { + if (!isDirty) return Promise.resolve(true); + + return new Promise((resolve) => { + show({ + title, + subtitle, + confirmText, + cancelText, + type, + onConfirm: () => { + resolve(true); + onConfirm?.(); + }, + onCancel: () => { + resolve(false); + onCancel?.(); + }, + }); + }); + }, [cancelText, confirmText, isDirty, onCancel, onConfirm, show, subtitle, title, type]); useEffect(() => { - return () => setWarnWhen?.(false); - }, [pathname]); + if (blocker.state === "blocked") { + confirm().then((result) => { + if (result) blocker.proceed(); + else blocker.reset(); + }); + } + }, [blocker, confirm]); - const warnMessage = useMemo(() => { - return t(message); - }, [message]); + useEffect(() => { + if (isDirty) { + window.onbeforeunload = () => subtitle; + } - return ( - {}} - title='titulo' - isOpen={warnWhen} - onConfirm={() => { - setWarnWhen?.(false); - }} - description={warnMessage} - /> - ); + return () => { + window.onbeforeunload = null; + }; + }, [isDirty, subtitle]); - /*usePrompt(warnMessage, warnWhen, () => { - setWarnWhen?.(false); - }); - - return null;*/ + return { + confirm, + }; }; diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx index 4c58d49..3bcf3a1 100644 --- a/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx +++ b/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx @@ -6,10 +6,5 @@ export const useWarnAboutChange = () => { if (context === null) throw new Error("useWarnAboutChange must be used within a UnsavedWarnProvider"); - const { warnWhen, setWarnWhen } = context; - - return { - warnWhen: Boolean(warnWhen), - setWarnWhen: setWarnWhen ?? (() => undefined), - }; + return context; };