Browse Source

feat(dashboard): Implement navigation confirmation

Michael Bromley 9 months ago
parent
commit
76a53b07f5

+ 39 - 0
packages/dashboard/src/lib/components/shared/navigation-confirmation.tsx

@@ -0,0 +1,39 @@
+import { Trans } from "@/lib/trans.js";
+import { useBlocker } from "@tanstack/react-router";
+import { UseFormReturn } from "react-hook-form";
+
+import { Button } from "../ui/button.js";
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog.js";
+
+export interface NavigationConfirmationProps {
+    form: UseFormReturn<any>;
+}
+
+/**
+ * Navigation confirmation dialog that blocks navigation when the form is dirty.
+ */
+export function NavigationConfirmation(props: NavigationConfirmationProps) {
+    const { proceed, reset, status } = useBlocker({
+        shouldBlockFn: () => props.form.formState.isDirty,
+        withResolver: true,
+        enableBeforeUnload: true,
+    })
+    return (
+        <Dialog open={status === 'blocked'} onOpenChange={reset}>
+            <DialogContent className="sm:max-w-[425px]">
+                <DialogHeader>
+                    <DialogTitle><Trans>Confirm navigation</Trans></DialogTitle>
+                    <DialogDescription>
+                        <Trans>Are you sure you want to navigate away from this page? Any unsaved changes will be lost.</Trans>
+                    </DialogDescription>
+                </DialogHeader>
+                <DialogFooter>
+                    <Button variant="outline" onClick={reset}><Trans>Cancel</Trans></Button>
+                    <Button type="button" onClick={proceed}>
+                        <Trans>Confirm</Trans>
+                    </Button>
+                </DialogFooter>
+            </DialogContent>
+        </Dialog>
+    )
+}

+ 4 - 0
packages/dashboard/src/lib/framework/layout-engine/page-layout.tsx

@@ -5,10 +5,13 @@ import { Form } from '@/components/ui/form.js';
 import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
 import { usePage } from '@/hooks/use-page.js';
 import { cn } from '@/lib/utils.js';
+import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
 import { useMediaQuery } from '@uidotdev/usehooks';
 import React, { ComponentProps, createContext } from 'react';
 import { Control, UseFormReturn } from 'react-hook-form';
+
 import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
+
 import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
 import { LocationWrapper } from './location-wrapper.js';
 
@@ -42,6 +45,7 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
 
     const pageContentWithOptionalForm = form ? (
         <Form {...form}>
+            <NavigationConfirmation form={form} />
             <form onSubmit={submitHandler} className="space-y-4">
                 {pageHeader}
                 {pageContent}