Hook para preguntar si se quieren guardar los cambios en pantallas de edición
This commit is contained in:
parent
738a30d7fa
commit
86a29ad082
@ -1,4 +1,4 @@
|
|||||||
import { AuthProvider, ThemeProvider } from "@/lib/hooks";
|
import { AuthProvider, ThemeProvider, UnsavedWarnProvider } from "@/lib/hooks";
|
||||||
import { TooltipProvider } from "@/ui";
|
import { TooltipProvider } from "@/ui";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
@ -26,11 +26,13 @@ function App() {
|
|||||||
<AuthProvider authActions={createAxiosAuthActions(import.meta.env.VITE_API_URL)}>
|
<AuthProvider authActions={createAxiosAuthActions(import.meta.env.VITE_API_URL)}>
|
||||||
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
|
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Suspense fallback={<LoadingOverlay />}>
|
<UnsavedWarnProvider>
|
||||||
<Routes />
|
<Suspense fallback={<LoadingOverlay />}>
|
||||||
|
<Routes />
|
||||||
|
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</UnsavedWarnProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
<TailwindIndicator />
|
<TailwindIndicator />
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
|
|||||||
@ -23,7 +23,7 @@ interface CustomDialogProps {
|
|||||||
|
|
||||||
export const CustomDialog = ({
|
export const CustomDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onCancel: onClose,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -39,7 +39,7 @@ export const CustomDialog = ({
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>
|
||||||
<Link to='#' onClick={onClose}>
|
<Link to='#' onClick={onCancel}>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</Link>
|
</Link>
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { UnsavedChangesNotifier, UnsavedWarnProvider } from "@/lib/hooks";
|
import { UnsavedWarnProvider } from "@/lib/hooks";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
export const Layout = ({ children }: PropsWithChildren) => (
|
export const Layout = ({ children }: PropsWithChildren) => (
|
||||||
<UnsavedWarnProvider>
|
<UnsavedWarnProvider>
|
||||||
<div className='flex flex-col w-full min-h-screen'>{children}</div>
|
<div className='flex flex-col w-full min-h-screen'>{children}</div>
|
||||||
<UnsavedChangesNotifier />
|
|
||||||
</UnsavedWarnProvider>
|
</UnsavedWarnProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
export interface IUnsavedWarnContextState {
|
||||||
warnWhen?: boolean;
|
show: (options: NullOr<UnsavedChangesNotifierProps>) => void;
|
||||||
setWarnWhen?: (value: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UnsavedWarnContext = createContext<IUnsavedWarnContextState>({});
|
export const UnsavedWarnContext = createContext<NullOr<IUnsavedWarnContextState>>(null);
|
||||||
|
|
||||||
export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => {
|
export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => {
|
||||||
const [warnWhen, setWarnWhen] = useState(false);
|
const [confirm, setConfirm] = useState<NullOr<UnsavedChangesNotifierProps>>(null);
|
||||||
|
|
||||||
|
const [open, toggle] = useState(false);
|
||||||
|
|
||||||
|
const show = useCallback(
|
||||||
|
(confirmOptions: NullOr<UnsavedChangesNotifierProps>) => {
|
||||||
|
setConfirm(confirmOptions);
|
||||||
|
toggle(true);
|
||||||
|
},
|
||||||
|
[toggle, setConfirm]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
confirm?.onConfirm?.();
|
||||||
|
toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
confirm?.onCancel?.();
|
||||||
|
toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = useMemo(() => ({ show }), [show]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnsavedWarnContext.Provider value={{ warnWhen, setWarnWhen }}>
|
<UnsavedWarnContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
|
<CustomDialog
|
||||||
|
//type='warning'
|
||||||
|
onCancel={() => {
|
||||||
|
console.log("onCancel");
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
onConfirm={() => onConfirm()}
|
||||||
|
title={confirm?.title}
|
||||||
|
description={confirm?.subtitle}
|
||||||
|
confirmLabel={confirm?.confirmText}
|
||||||
|
cancelLabel={confirm?.cancelText}
|
||||||
|
isOpen={open}
|
||||||
|
/>
|
||||||
</UnsavedWarnContext.Provider>
|
</UnsavedWarnContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* `useBlocker` and `usePrompt` is no longer part of react-router-dom for the routers other than `DataRouter`.
|
|
||||||
*
|
|
||||||
* The previous workaround (<v6.4) was to use `block` function in `UNSAFE_NavigationContext` which is now removed.
|
|
||||||
*
|
|
||||||
* We're using a workaround from the gist https://gist.github.com/MarksCode/64e438c82b0b2a1161e01c88ca0d0355 with some modifications
|
|
||||||
* Thanks to @MarksCode(https://github.com/MarksCode) for the workaround.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";
|
|
||||||
|
|
||||||
function useConfirmExit(confirmExit: () => 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<typeof push>) => {
|
|
||||||
const result = confirmExit();
|
|
||||||
if (result !== false) {
|
|
||||||
push(...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
navigator.go = (...args: Parameters<typeof go>) => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@ -1,44 +1,72 @@
|
|||||||
import { CustomDialog } from "@/components";
|
import { useCallback, useEffect } from "react";
|
||||||
import { t } from "i18next";
|
import { useBlocker } from "react-router-dom";
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import { useWarnAboutChange } from "./useWarnAboutChange";
|
import { useWarnAboutChange } from "./useWarnAboutChange";
|
||||||
|
|
||||||
type UnsavedChangesNotifierProps = {
|
export type UnsavedChangesNotifierProps = {
|
||||||
message?: string;
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
onConfirm?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
type?: "success" | "error" | "warning" | "info";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UnsavedChangesNotifier = ({
|
export const useUnsavedChangesNotifier = ({
|
||||||
message = t("unsaved_changes_prompt"),
|
isDirty = false,
|
||||||
}: UnsavedChangesNotifierProps) => {
|
title = "Se han detectado cambios",
|
||||||
const { pathname } = useLocation();
|
subtitle = "Atención, hay cambios pendientes de guardar en esta página.\nSi continúa, perderá los cambios.",
|
||||||
const { warnWhen, setWarnWhen } = useWarnAboutChange();
|
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<boolean>((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(() => {
|
useEffect(() => {
|
||||||
return () => setWarnWhen?.(false);
|
if (blocker.state === "blocked") {
|
||||||
}, [pathname]);
|
confirm().then((result) => {
|
||||||
|
if (result) blocker.proceed();
|
||||||
|
else blocker.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [blocker, confirm]);
|
||||||
|
|
||||||
const warnMessage = useMemo(() => {
|
useEffect(() => {
|
||||||
return t(message);
|
if (isDirty) {
|
||||||
}, [message]);
|
window.onbeforeunload = () => subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return () => {
|
||||||
<CustomDialog
|
window.onbeforeunload = null;
|
||||||
cancelLabel='Cancelar'
|
};
|
||||||
confirmLabel='Confirmar'
|
}, [isDirty, subtitle]);
|
||||||
onCancel={() => {}}
|
|
||||||
title='titulo'
|
|
||||||
isOpen={warnWhen}
|
|
||||||
onConfirm={() => {
|
|
||||||
setWarnWhen?.(false);
|
|
||||||
}}
|
|
||||||
description={warnMessage}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
/*usePrompt(warnMessage, warnWhen, () => {
|
return {
|
||||||
setWarnWhen?.(false);
|
confirm,
|
||||||
});
|
};
|
||||||
|
|
||||||
return null;*/
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,10 +6,5 @@ export const useWarnAboutChange = () => {
|
|||||||
if (context === null)
|
if (context === null)
|
||||||
throw new Error("useWarnAboutChange must be used within a UnsavedWarnProvider");
|
throw new Error("useWarnAboutChange must be used within a UnsavedWarnProvider");
|
||||||
|
|
||||||
const { warnWhen, setWarnWhen } = context;
|
return context;
|
||||||
|
|
||||||
return {
|
|
||||||
warnWhen: Boolean(warnWhen),
|
|
||||||
setWarnWhen: setWarnWhen ?? (() => undefined),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user