Files
Stirling-PDF/frontend/src/core/components/shared/NavigationWarningModal.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

191 lines
5.9 KiB
TypeScript
Raw Normal View History

2026-04-02 17:39:45 +01:00
import { useRef, useEffect } from "react";
2025-10-03 17:02:05 +01:00
import { Modal, Text, Button, Group, Stack } from "@mantine/core";
import { useNavigationGuard } from "@app/contexts/NavigationContext";
2025-10-03 17:02:05 +01:00
import { useTranslation } from "react-i18next";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
2026-01-05 15:12:14 +00:00
import { Z_INDEX_TOAST } from "@app/styles/zIndex";
2025-07-16 17:53:50 +01:00
2026-04-02 17:39:45 +01:00
const NavigationWarningModal = () => {
const { t } = useTranslation();
2026-04-02 17:39:45 +01:00
const {
showNavigationWarning,
hasUnsavedChanges,
pendingNavigation,
cancelNavigation,
setHasUnsavedChanges,
navigationWarningHandlersRef,
} = useNavigationGuard();
// Store pendingNavigation in a ref so async handlers always have the latest,
// not a stale closure captured before an await.
const pendingNavigationRef = useRef(pendingNavigation);
useEffect(() => {
pendingNavigationRef.current = pendingNavigation;
}, [pendingNavigation]);
2025-07-16 17:53:50 +01:00
const handleKeepWorking = () => {
cancelNavigation();
};
2026-04-02 17:39:45 +01:00
const finishAndNavigate = () => {
const nav = pendingNavigationRef.current;
setHasUnsavedChanges(false);
cancelNavigation();
if (nav) {
nav();
}
};
2026-01-05 15:12:14 +00:00
const handleDiscardChanges = async () => {
2026-04-02 17:39:45 +01:00
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onDiscardAndContinue) {
await handlers.onDiscardAndContinue();
2026-01-05 15:12:14 +00:00
}
2026-04-02 17:39:45 +01:00
finishAndNavigate();
2025-07-16 17:53:50 +01:00
};
2025-10-02 20:14:35 +01:00
const handleApplyAndContinue = async () => {
2026-04-02 17:39:45 +01:00
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onApplyAndContinue) {
await handlers.onApplyAndContinue();
2025-10-02 20:14:35 +01:00
}
2026-04-02 17:39:45 +01:00
finishAndNavigate();
2025-10-02 20:14:35 +01:00
};
2025-07-16 17:53:50 +01:00
2026-01-05 15:12:14 +00:00
const handleExportAndContinue = async () => {
2026-04-02 17:39:45 +01:00
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onExportAndContinue) {
await handlers.onExportAndContinue();
2025-07-16 17:53:50 +01:00
}
2026-04-02 17:39:45 +01:00
finishAndNavigate();
2025-07-16 17:53:50 +01:00
};
2026-01-05 15:12:14 +00:00
2026-04-02 17:39:45 +01:00
// Read handler availability at render time for button visibility
const handlers = navigationWarningHandlersRef.current;
const hasApply = !!handlers?.onApplyAndContinue;
const hasExport = !!handlers?.onExportAndContinue;
2026-01-05 15:12:14 +00:00
const BUTTON_WIDTH = "12rem";
2025-07-16 17:53:50 +01:00
// Only show modal if there are unsaved changes AND there's an actual pending navigation
if (!hasUnsavedChanges || !pendingNavigation) {
2025-07-16 17:53:50 +01:00
return null;
}
return (
<Modal
opened={showNavigationWarning}
onClose={handleKeepWorking}
title={t("unsavedChangesTitle", "Unsaved Changes")}
2025-07-16 17:53:50 +01:00
centered
2025-10-03 17:02:05 +01:00
size="auto"
closeOnClickOutside={true}
closeOnEscape={true}
2026-01-05 15:12:14 +00:00
zIndex={Z_INDEX_TOAST}
2025-07-16 17:53:50 +01:00
>
2025-10-03 17:02:05 +01:00
<Stack>
<Stack ta="center" p="md">
<Text size="md" fw="300">
{t("unsavedChanges", "You have unsaved changes to your PDF.")}
</Text>
<Text size="lg" fw="500">
{t("areYouSure", "Are you sure you want to leave?")}
</Text>
2025-10-03 17:02:05 +01:00
</Stack>
2025-10-03 17:02:05 +01:00
{/* Desktop layout: 2 groups side by side */}
<Group justify="space-between" gap="xl" visibleFrom="md">
<Group gap="sm">
<Button
variant="light"
color="var(--mantine-color-gray-8)"
onClick={handleKeepWorking}
w={BUTTON_WIDTH}
leftSection={<ArrowBackIcon fontSize="small" />}
>
{t("keepWorking", "Keep Working")}
2025-07-16 17:53:50 +01:00
</Button>
2025-10-02 20:14:35 +01:00
</Group>
2025-10-03 17:02:05 +01:00
<Group gap="sm">
<Button
variant="filled"
color="var(--mantine-color-red-9)"
onClick={handleDiscardChanges}
w={BUTTON_WIDTH}
leftSection={<DeleteOutlineIcon fontSize="small" />}
>
2025-10-03 17:02:05 +01:00
{t("discardChanges", "Discard Changes")}
</Button>
2026-04-02 17:39:45 +01:00
{hasApply && (
<Button
variant="filled"
onClick={handleApplyAndContinue}
w={BUTTON_WIDTH}
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
>
2025-10-03 17:02:05 +01:00
{t("applyAndContinue", "Apply & Leave")}
</Button>
)}
2026-04-02 17:39:45 +01:00
{hasExport && (
<Button
variant="filled"
onClick={handleExportAndContinue}
w={BUTTON_WIDTH}
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
>
2026-01-05 15:12:14 +00:00
{t("exportAndContinue", "Export & Leave")}
</Button>
)}
</Group>
2025-07-16 17:53:50 +01:00
</Group>
2025-10-03 17:02:05 +01:00
{/* Mobile layout: centered stack of 4 buttons */}
<Stack align="center" gap="sm" hiddenFrom="md">
<Button
variant="light"
color="var(--mantine-color-gray-8)"
onClick={handleKeepWorking}
w={BUTTON_WIDTH}
leftSection={<ArrowBackIcon fontSize="small" />}
>
2025-10-03 17:02:05 +01:00
{t("keepWorking", "Keep Working")}
</Button>
<Button
variant="filled"
color="var(--mantine-color-red-9)"
onClick={handleDiscardChanges}
w={BUTTON_WIDTH}
leftSection={<DeleteOutlineIcon fontSize="small" />}
>
2025-10-03 17:02:05 +01:00
{t("discardChanges", "Discard Changes")}
</Button>
2026-04-02 17:39:45 +01:00
{hasApply && (
<Button
variant="filled"
onClick={handleApplyAndContinue}
w={BUTTON_WIDTH}
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
>
2025-10-03 17:02:05 +01:00
{t("applyAndContinue", "Apply & Leave")}
</Button>
)}
2026-04-02 17:39:45 +01:00
{hasExport && (
<Button
variant="filled"
onClick={handleExportAndContinue}
w={BUTTON_WIDTH}
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
>
2026-01-05 15:12:14 +00:00
{t("exportAndContinue", "Export & Leave")}
</Button>
)}
2025-10-03 17:02:05 +01:00
</Stack>
2025-07-16 17:53:50 +01:00
</Stack>
</Modal>
);
};
export default NavigationWarningModal;