From 3eee0dc7f9e2f5bfe6cfe95fb50c5a369b5efe26 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:41:02 +0200 Subject: [PATCH] fix(dashboard): markdown and form sheet discard (#1472) * fix: markdown * fix: discard changes on close --- apps/dashboard/package.json | 1 + apps/dashboard/src/app/globals.css | 1 + .../data-table/maintenances/columns.tsx | 2 +- .../status-report-updates/data-table.tsx | 2 +- .../src/components/forms/form-sheet.tsx | 144 ++++++++++++++++++ .../src/components/forms/maintenance/form.tsx | 12 +- .../components/forms/maintenance/sheet.tsx | 6 +- .../forms/monitor-tag/form-monitor-tag.tsx | 9 +- .../components/forms/monitor-tag/sheet.tsx | 6 +- .../forms/notifications/form-discord.tsx | 9 +- .../forms/notifications/form-email.tsx | 9 +- .../forms/notifications/form-ntfy.tsx | 9 +- .../forms/notifications/form-pagerduty.tsx | 9 +- .../forms/notifications/form-slack.tsx | 9 +- .../forms/notifications/form-sms.tsx | 9 +- .../forms/notifications/form-webhook.tsx | 9 +- .../components/forms/notifications/sheet.tsx | 6 +- .../forms/private-location/form.tsx | 10 +- .../forms/private-location/sheet.tsx | 6 +- .../forms/status-report-update/form.tsx | 11 +- .../forms/status-report-update/sheet.tsx | 6 +- .../components/forms/status-report/form.tsx | 11 +- .../components/forms/status-report/sheet.tsx | 6 +- pnpm-lock.yaml | 9 +- 24 files changed, 268 insertions(+), 43 deletions(-) diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 987138e7..43b49c31 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -100,6 +100,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "4.1.11", + "@tailwindcss/typography": "0.5.10", "@types/dom-speech-recognition": "0.0.6", "@types/node": "24.0.8", "@types/react": "19.2.2", diff --git a/apps/dashboard/src/app/globals.css b/apps/dashboard/src/app/globals.css index 1a48e76c..1e505e5a 100644 --- a/apps/dashboard/src/app/globals.css +++ b/apps/dashboard/src/app/globals.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "tw-animate-css"; +@plugin "@tailwindcss/typography"; /* safelist */ @source inline("has-data-[slot=slider-range]:bg-red-500"); diff --git a/apps/dashboard/src/components/data-table/maintenances/columns.tsx b/apps/dashboard/src/components/data-table/maintenances/columns.tsx index 42ff5d61..8ac20353 100644 --- a/apps/dashboard/src/components/data-table/maintenances/columns.tsx +++ b/apps/dashboard/src/components/data-table/maintenances/columns.tsx @@ -25,7 +25,7 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { const value = String(row.getValue("message")); return ( -
+
); diff --git a/apps/dashboard/src/components/data-table/status-report-updates/data-table.tsx b/apps/dashboard/src/components/data-table/status-report-updates/data-table.tsx index 12a14258..267a443e 100644 --- a/apps/dashboard/src/components/data-table/status-report-updates/data-table.tsx +++ b/apps/dashboard/src/components/data-table/status-report-updates/data-table.tsx @@ -111,7 +111,7 @@ export function DataTable({
-
+
diff --git a/apps/dashboard/src/components/forms/form-sheet.tsx b/apps/dashboard/src/components/forms/form-sheet.tsx index 7514f5ea..5f34cd7c 100644 --- a/apps/dashboard/src/components/forms/form-sheet.tsx +++ b/apps/dashboard/src/components/forms/form-sheet.tsx @@ -1,5 +1,15 @@ "use client"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Sheet, SheetContent, @@ -10,6 +20,13 @@ import { SheetTrigger, } from "@/components/ui/sheet"; import { cn } from "@/lib/utils"; +import React, { + createContext, + useContext, + useEffect, + useRef, + useState, +} from "react"; export function FormSheetContent({ children, @@ -86,6 +103,133 @@ export function FormSheetTrigger({ ); } +export function FormSheetAlertDialog({ + onConfirm, + ...props +}: React.ComponentProps & { + onConfirm: () => void; +}) { + return ( + + + + Discard changes? + + You have unsaved changes. Are you sure you want to discard them? + + + + Continue editing + + Discard changes + + + + + ); +} + +const FormSheetDirtyContext = createContext<{ + isDirty: boolean; + setIsDirty: (dirty: boolean) => void; +} | null>(null); + +export function useFormSheetDirty() { + const context = useContext(FormSheetDirtyContext); + if (!context) { + throw new Error( + "useFormSheetDirty must be used within FormSheetWithDirtyProtection", + ); + } + return context; +} + +export function FormSheetWithDirtyProtection({ + children, + open: controlledOpen, + onOpenChange: controlledOnOpenChange, +}: { + children: React.ReactNode; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}) { + const [internalOpen, setInternalOpen] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const [showAlert, setShowAlert] = useState(false); + const shouldBypassAlert = useRef(false); + + const open = controlledOpen ?? internalOpen; + const setOpen = controlledOnOpenChange ?? setInternalOpen; + + // Reset states when sheet closes + useEffect(() => { + if (!open) { + setIsDirty(false); + shouldBypassAlert.current = false; + } + }, [open]); + + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen && isDirty && !shouldBypassAlert.current) { + // User is trying to close with unsaved changes + setShowAlert(true); + } else { + setOpen(newOpen); + } + }; + + const handleDiscardChanges = () => { + shouldBypassAlert.current = true; + setShowAlert(false); + setOpen(false); + }; + + const handleInteractOutside = (e: Event) => { + if (isDirty) { + e.preventDefault(); + setShowAlert(true); + } + }; + + const handleEscapeKeyDown = (e: KeyboardEvent) => { + if (isDirty) { + e.preventDefault(); + setShowAlert(true); + } + }; + + return ( + + + {/* Clone children and inject event handlers if it's SheetContent */} + {React.Children.map(children, (child) => { + if ( + React.isValidElement(child) && + (child.type === FormSheetContent || child.type === SheetContent) + ) { + return React.cloneElement( + child as React.ReactElement<{ + onInteractOutside?: (e: Event) => void; + onEscapeKeyDown?: (e: KeyboardEvent) => void; + }>, + { + onInteractOutside: handleInteractOutside, + onEscapeKeyDown: handleEscapeKeyDown, + }, + ); + } + return child; + })} + + + + ); +} + export { SheetTitle as FormSheetTitle, SheetDescription as FormSheetDescription, diff --git a/apps/dashboard/src/components/forms/maintenance/form.tsx b/apps/dashboard/src/components/forms/maintenance/form.tsx index cb7b23d6..ba3a021f 100644 --- a/apps/dashboard/src/components/forms/maintenance/form.tsx +++ b/apps/dashboard/src/components/forms/maintenance/form.tsx @@ -9,6 +9,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Checkbox } from "@/components/ui/checkbox"; @@ -37,8 +38,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; import { addDays, format } from "date-fns"; import { CalendarIcon, ClockIcon } from "lucide-react"; -import type React from "react"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -81,6 +81,12 @@ export function FormMaintenance({ }); const watchMessage = form.watch("message"); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; @@ -362,7 +368,7 @@ export function FormMaintenance({
-
+
diff --git a/apps/dashboard/src/components/forms/maintenance/sheet.tsx b/apps/dashboard/src/components/forms/maintenance/sheet.tsx index bdb38902..660853fe 100644 --- a/apps/dashboard/src/components/forms/maintenance/sheet.tsx +++ b/apps/dashboard/src/components/forms/maintenance/sheet.tsx @@ -2,7 +2,6 @@ import { FormCard, FormCardGroup } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, @@ -10,6 +9,7 @@ import { FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { FormMaintenance, @@ -32,7 +32,7 @@ export function FormSheetMaintenance({ const [open, setOpen] = useState(false); return ( - + {children} @@ -69,6 +69,6 @@ export function FormSheetMaintenance({ - + ); } diff --git a/apps/dashboard/src/components/forms/monitor-tag/form-monitor-tag.tsx b/apps/dashboard/src/components/forms/monitor-tag/form-monitor-tag.tsx index 3b86c4ab..0894111d 100644 --- a/apps/dashboard/src/components/forms/monitor-tag/form-monitor-tag.tsx +++ b/apps/dashboard/src/components/forms/monitor-tag/form-monitor-tag.tsx @@ -1,5 +1,6 @@ "use client"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form, @@ -15,7 +16,7 @@ import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; import { Plus, Trash2 } from "lucide-react"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -58,6 +59,12 @@ export function FormMonitorTag({ }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/monitor-tag/sheet.tsx b/apps/dashboard/src/components/forms/monitor-tag/sheet.tsx index 36f5dd59..345d5164 100644 --- a/apps/dashboard/src/components/forms/monitor-tag/sheet.tsx +++ b/apps/dashboard/src/components/forms/monitor-tag/sheet.tsx @@ -6,13 +6,13 @@ import { FormCardGroup, } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { FormMonitorTag, @@ -33,7 +33,7 @@ export function FormSheetMonitorTag({ const [open, setOpen] = useState(false); return ( - + {children} @@ -62,6 +62,6 @@ export function FormSheetMonitorTag({ - + ); } diff --git a/apps/dashboard/src/components/forms/notifications/form-discord.tsx b/apps/dashboard/src/components/forms/notifications/form-discord.tsx index 239d0b21..78489319 100644 --- a/apps/dashboard/src/components/forms/notifications/form-discord.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-discord.tsx @@ -15,6 +15,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; @@ -23,7 +24,7 @@ import { config } from "@/data/notifications.client"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -58,6 +59,12 @@ export function FormDiscord({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/form-email.tsx b/apps/dashboard/src/components/forms/notifications/form-email.tsx index 8887c568..10ff9b50 100644 --- a/apps/dashboard/src/components/forms/notifications/form-email.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-email.tsx @@ -14,13 +14,14 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -55,6 +56,12 @@ export function FormEmail({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/form-ntfy.tsx b/apps/dashboard/src/components/forms/notifications/form-ntfy.tsx index 5a553c18..e5d61a0c 100644 --- a/apps/dashboard/src/components/forms/notifications/form-ntfy.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-ntfy.tsx @@ -14,6 +14,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; @@ -22,7 +23,7 @@ import { config } from "@/data/notifications.client"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -61,6 +62,12 @@ export function FormNtfy({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/form-pagerduty.tsx b/apps/dashboard/src/components/forms/notifications/form-pagerduty.tsx index 3f55d82a..b7167671 100644 --- a/apps/dashboard/src/components/forms/notifications/form-pagerduty.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-pagerduty.tsx @@ -14,6 +14,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; @@ -24,7 +25,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { PagerDutySchema } from "@openstatus/notification-pagerduty"; import { isTRPCClientError } from "@trpc/client"; import { parseAsString, useQueryState } from "nuqs"; -import { useEffect, useTransition } from "react"; +import React, { useEffect, useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -61,6 +62,12 @@ export function FormPagerDuty({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); useEffect(() => { if (searchConfig) { diff --git a/apps/dashboard/src/components/forms/notifications/form-slack.tsx b/apps/dashboard/src/components/forms/notifications/form-slack.tsx index 173ea107..9c37ff74 100644 --- a/apps/dashboard/src/components/forms/notifications/form-slack.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-slack.tsx @@ -15,6 +15,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; @@ -23,7 +24,7 @@ import { config } from "@/data/notifications.client"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -58,6 +59,12 @@ export function FormSlack({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/form-sms.tsx b/apps/dashboard/src/components/forms/notifications/form-sms.tsx index 15a43749..52cc5e58 100644 --- a/apps/dashboard/src/components/forms/notifications/form-sms.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-sms.tsx @@ -14,12 +14,13 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -54,6 +55,12 @@ export function FormSms({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/form-webhook.tsx b/apps/dashboard/src/components/forms/notifications/form-webhook.tsx index 8d1d103d..817745d9 100644 --- a/apps/dashboard/src/components/forms/notifications/form-webhook.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-webhook.tsx @@ -15,6 +15,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; @@ -23,7 +24,7 @@ import { config } from "@/data/notifications.client"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -61,6 +62,12 @@ export function FormWebhook({ }, }); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/notifications/sheet.tsx b/apps/dashboard/src/components/forms/notifications/sheet.tsx index 293f6eaf..baa7c5dc 100644 --- a/apps/dashboard/src/components/forms/notifications/sheet.tsx +++ b/apps/dashboard/src/components/forms/notifications/sheet.tsx @@ -2,13 +2,13 @@ import { FormCard, FormCardGroup } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { config } from "@/data/notifications.client"; @@ -34,7 +34,7 @@ export function FormSheetNotifier({ const Form = provider ? config[provider].form : undefined; return ( - + {children} @@ -78,6 +78,6 @@ export function FormSheetNotifier({ - + ); } diff --git a/apps/dashboard/src/components/forms/private-location/form.tsx b/apps/dashboard/src/components/forms/private-location/form.tsx index 1d5dea55..a87ef103 100644 --- a/apps/dashboard/src/components/forms/private-location/form.tsx +++ b/apps/dashboard/src/components/forms/private-location/form.tsx @@ -8,6 +8,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Checkbox } from "@/components/ui/checkbox"; import { Form, @@ -31,8 +32,7 @@ import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { isTRPCClientError } from "@trpc/client"; import { Check, Copy } from "lucide-react"; -import type React from "react"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -66,6 +66,12 @@ export function FormPrivateLocation({ }); const [isPending, startTransition] = useTransition(); const { copy, isCopied } = useCopyToClipboard(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; diff --git a/apps/dashboard/src/components/forms/private-location/sheet.tsx b/apps/dashboard/src/components/forms/private-location/sheet.tsx index c1f3f7cb..a3f5d252 100644 --- a/apps/dashboard/src/components/forms/private-location/sheet.tsx +++ b/apps/dashboard/src/components/forms/private-location/sheet.tsx @@ -2,13 +2,13 @@ import { FormCard, FormCardGroup } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { FormPrivateLocation, @@ -31,7 +31,7 @@ export function FormSheetPrivateLocation({ const [open, setOpen] = useState(false); return ( - + {children} @@ -62,6 +62,6 @@ export function FormSheetPrivateLocation({ - + ); } diff --git a/apps/dashboard/src/components/forms/status-report-update/form.tsx b/apps/dashboard/src/components/forms/status-report-update/form.tsx index 6e400f55..b6b62509 100644 --- a/apps/dashboard/src/components/forms/status-report-update/form.tsx +++ b/apps/dashboard/src/components/forms/status-report-update/form.tsx @@ -5,6 +5,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { @@ -38,7 +39,7 @@ import { statusReportStatus } from "@openstatus/db/src/schema"; import { isTRPCClientError } from "@trpc/client"; import { format } from "date-fns"; import { CalendarIcon, ClockIcon } from "lucide-react"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -70,6 +71,12 @@ export function FormStatusReportUpdate({ }); const watchMessage = form.watch("message"); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; @@ -269,7 +276,7 @@ export function FormStatusReportUpdate({
-
+
diff --git a/apps/dashboard/src/components/forms/status-report-update/sheet.tsx b/apps/dashboard/src/components/forms/status-report-update/sheet.tsx index c3d73f35..aeada2d9 100644 --- a/apps/dashboard/src/components/forms/status-report-update/sheet.tsx +++ b/apps/dashboard/src/components/forms/status-report-update/sheet.tsx @@ -2,13 +2,13 @@ import { FormCard, FormCardGroup } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { FormStatusReportUpdate, @@ -27,7 +27,7 @@ export function FormSheetStatusReportUpdate({ }) { const [open, setOpen] = useState(false); return ( - + {children} @@ -55,6 +55,6 @@ export function FormSheetStatusReportUpdate({ - + ); } diff --git a/apps/dashboard/src/components/forms/status-report/form.tsx b/apps/dashboard/src/components/forms/status-report/form.tsx index 1c8b04aa..8bc85ee4 100644 --- a/apps/dashboard/src/components/forms/status-report/form.tsx +++ b/apps/dashboard/src/components/forms/status-report/form.tsx @@ -9,6 +9,7 @@ import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; +import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Checkbox } from "@/components/ui/checkbox"; @@ -46,7 +47,7 @@ import { statusReportStatus } from "@openstatus/db/src/schema"; import { isTRPCClientError } from "@trpc/client"; import { format } from "date-fns"; import { CalendarIcon, ClockIcon } from "lucide-react"; -import { useTransition } from "react"; +import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -89,6 +90,12 @@ export function FormStatusReport({ }); const watchMessage = form.watch("message"); const [isPending, startTransition] = useTransition(); + const { setIsDirty } = useFormSheetDirty(); + + const formIsDirty = form.formState.isDirty; + React.useEffect(() => { + setIsDirty(formIsDirty); + }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; @@ -308,7 +315,7 @@ export function FormStatusReport({
-
+
diff --git a/apps/dashboard/src/components/forms/status-report/sheet.tsx b/apps/dashboard/src/components/forms/status-report/sheet.tsx index 0d29463c..55054bfa 100644 --- a/apps/dashboard/src/components/forms/status-report/sheet.tsx +++ b/apps/dashboard/src/components/forms/status-report/sheet.tsx @@ -2,13 +2,13 @@ import { FormCard, FormCardGroup } from "@/components/forms/form-card"; import { - FormSheet, FormSheetContent, FormSheetDescription, FormSheetFooter, FormSheetHeader, FormSheetTitle, FormSheetTrigger, + FormSheetWithDirtyProtection, } from "@/components/forms/form-sheet"; import { FormStatusReport, @@ -33,7 +33,7 @@ export function FormSheetStatusReport({ const [open, setOpen] = useState(false); return ( - + {children} @@ -68,6 +68,6 @@ export function FormSheetStatusReport({ - + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4740d1c..afb873f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,12 +27,6 @@ importers: specifier: 5.7.2 version: 5.7.2 - apps/build-docker: - dependencies: - '@libsql/client': - specifier: 0.15.15 - version: 0.15.15(bufferutil@4.0.8)(utf-8-validate@6.0.5) - apps/dashboard: dependencies: '@auth/core': @@ -300,6 +294,9 @@ importers: '@tailwindcss/postcss': specifier: 4.1.11 version: 4.1.11 + '@tailwindcss/typography': + specifier: 0.5.10 + version: 0.5.10(tailwindcss@4.1.11) '@types/dom-speech-recognition': specifier: 0.0.6 version: 0.0.6 -- 2.51.2