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 { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
@ -26,11 +26,13 @@ function App() {
|
||||
<AuthProvider authActions={createAxiosAuthActions(import.meta.env.VITE_API_URL)}>
|
||||
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Suspense fallback={<LoadingOverlay />}>
|
||||
<Routes />
|
||||
<UnsavedWarnProvider>
|
||||
<Suspense fallback={<LoadingOverlay />}>
|
||||
<Routes />
|
||||
|
||||
<ToastContainer />
|
||||
</Suspense>
|
||||
<ToastContainer />
|
||||
</Suspense>
|
||||
</UnsavedWarnProvider>
|
||||
</TooltipProvider>
|
||||
<TailwindIndicator />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
|
||||
@ -23,7 +23,7 @@ interface CustomDialogProps {
|
||||
|
||||
export const CustomDialog = ({
|
||||
isOpen,
|
||||
onCancel: onClose,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
title,
|
||||
description,
|
||||
@ -39,7 +39,7 @@ export const CustomDialog = ({
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Link to='#' onClick={onClose}>
|
||||
<Link to='#' onClick={onCancel}>
|
||||
{cancelLabel}
|
||||
</Link>
|
||||
</AlertDialogCancel>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { UnsavedChangesNotifier, UnsavedWarnProvider } from "@/lib/hooks";
|
||||
import { UnsavedWarnProvider } from "@/lib/hooks";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const Layout = ({ children }: PropsWithChildren) => (
|
||||
<UnsavedWarnProvider>
|
||||
<div className='flex flex-col w-full min-h-screen'>{children}</div>
|
||||
<UnsavedChangesNotifier />
|
||||
</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 {
|
||||
warnWhen?: boolean;
|
||||
setWarnWhen?: (value: boolean) => void;
|
||||
show: (options: NullOr<UnsavedChangesNotifierProps>) => void;
|
||||
}
|
||||
|
||||
export const UnsavedWarnContext = createContext<IUnsavedWarnContextState>({});
|
||||
export const UnsavedWarnContext = createContext<NullOr<IUnsavedWarnContextState>>(null);
|
||||
|
||||
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 (
|
||||
<UnsavedWarnContext.Provider value={{ warnWhen, setWarnWhen }}>
|
||||
<UnsavedWarnContext.Provider value={value}>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 { 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<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(() => {
|
||||
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 (
|
||||
<CustomDialog
|
||||
cancelLabel='Cancelar'
|
||||
confirmLabel='Confirmar'
|
||||
onCancel={() => {}}
|
||||
title='titulo'
|
||||
isOpen={warnWhen}
|
||||
onConfirm={() => {
|
||||
setWarnWhen?.(false);
|
||||
}}
|
||||
description={warnMessage}
|
||||
/>
|
||||
);
|
||||
return () => {
|
||||
window.onbeforeunload = null;
|
||||
};
|
||||
}, [isDirty, subtitle]);
|
||||
|
||||
/*usePrompt(warnMessage, warnWhen, () => {
|
||||
setWarnWhen?.(false);
|
||||
});
|
||||
|
||||
return null;*/
|
||||
return {
|
||||
confirm,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user