From 06d70feca2a5b4c7b0def9adc1711dfad9c8058a Mon Sep 17 00:00:00 2001 From: desiprisg Date: Thu, 13 Feb 2025 12:09:58 +0200 Subject: [PATCH] feat(dashboard): Better form protection DX --- .../subscribers/subscriber-drawer.tsx | 16 ++------ .../workflow-editor/steps/step-drawer.tsx | 16 ++------ ...-protection.ts => use-form-protection.tsx} | 38 +++++++++++++++++-- .../dashboard/src/pages/create-subscriber.tsx | 19 ++++------ 4 files changed, 48 insertions(+), 41 deletions(-) rename apps/dashboard/src/hooks/{use-form-protection.ts => use-form-protection.tsx} (55%) diff --git a/apps/dashboard/src/components/subscribers/subscriber-drawer.tsx b/apps/dashboard/src/components/subscribers/subscriber-drawer.tsx index 3eae9fdff60..90883cfba4a 100644 --- a/apps/dashboard/src/components/subscribers/subscriber-drawer.tsx +++ b/apps/dashboard/src/components/subscribers/subscriber-drawer.tsx @@ -1,4 +1,3 @@ -import { FormProtection } from '@/components/form-protection'; import { Sheet, SheetContent, SheetDescription, SheetTitle } from '@/components/primitives/sheet'; import { VisuallyHidden } from '@/components/primitives/visually-hidden'; import { SubscriberTabs } from '@/components/subscribers/subscriber-tabs'; @@ -19,20 +18,11 @@ export const SubscriberDrawer = forwardRef; - const { showAlert, setShowAlert, isFormDirty } = useFormProtection(ref); + const { protectedOnOpenChange, ProtectionAlert } = useFormProtection({ ref, onOpenChange }); return ( <> - { - if (isFormDirty) { - return setShowAlert(true); - } - onOpenChange(open); - }} - > + {/* Custom overlay since SheetOverlay does not work with modal={false} */}
- onOpenChange(false)} showAlert={showAlert} setShowAlert={setShowAlert} /> + ); }); diff --git a/apps/dashboard/src/components/workflow-editor/steps/step-drawer.tsx b/apps/dashboard/src/components/workflow-editor/steps/step-drawer.tsx index 4c8ad0706c4..81b00496201 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/step-drawer.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/step-drawer.tsx @@ -1,6 +1,5 @@ import { useNavigate } from 'react-router-dom'; -import { FormProtection } from '@/components/form-protection'; import { PageMeta } from '@/components/page-meta'; import { Sheet, SheetContent, SheetDescription, SheetTitle } from '@/components/primitives/sheet'; import { VisuallyHidden } from '@/components/primitives/visually-hidden'; @@ -29,7 +28,7 @@ export const StepDrawer = ({ children, title }: { children: React.ReactNode; tit }, }); - const { isFormDirty, setShowAlert, showAlert } = useFormProtection(sheetRef); + const { protectedOnOpenChange, ProtectionAlert } = useFormProtection({ ref: sheetRef, onOpenChange: setIsOpen }); if (!workflow || !step) { return null; @@ -38,16 +37,7 @@ export const StepDrawer = ({ children, title }: { children: React.ReactNode; tit return ( <> - { - if (isFormDirty) { - return setShowAlert(true); - } - setIsOpen(open); - }} - > +
- setIsOpen(false)} showAlert={showAlert} setShowAlert={setShowAlert} /> + ); }; diff --git a/apps/dashboard/src/hooks/use-form-protection.ts b/apps/dashboard/src/hooks/use-form-protection.tsx similarity index 55% rename from apps/dashboard/src/hooks/use-form-protection.ts rename to apps/dashboard/src/hooks/use-form-protection.tsx index b44d965c690..eaec8ececb3 100644 --- a/apps/dashboard/src/hooks/use-form-protection.ts +++ b/apps/dashboard/src/hooks/use-form-protection.tsx @@ -1,7 +1,14 @@ +import { FormProtection } from '@/components/form-protection'; import { useBeforeUnload } from '@/hooks/use-before-unload'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; -export function useFormProtection(ref: React.RefObject) { +type UseFormProtectionProps = { + ref: React.RefObject; + onOpenChange?: (open: boolean) => void; // for drawers/sheets + onProceed?: () => void; // for nav, i.e. navigate(-1) when closing the alert +}; +export function useFormProtection(props: UseFormProtectionProps) { + const { ref, onOpenChange, onProceed } = props; const [isDirty, setIsDirty] = useState(false); const [showAlert, setShowAlert] = useState(false); @@ -54,5 +61,30 @@ export function useFormProtection(ref: React.RefObject) { return () => observer.disconnect(); } - return { isFormDirty: isDirty, showAlert, setShowAlert }; + const protectedOnOpenChange = (open: boolean) => { + if (isDirty) { + setShowAlert(true); + } else { + onOpenChange?.(open); + } + }; + + const ProtectionAlert = useCallback( + () => ( + { + if (onProceed) { + onProceed(); + } else { + onOpenChange?.(false); + } + }} + showAlert={showAlert} + setShowAlert={setShowAlert} + /> + ), + [onProceed, onOpenChange, showAlert, setShowAlert] + ); + + return { isDirty, protectedOnOpenChange, ProtectionAlert }; } diff --git a/apps/dashboard/src/pages/create-subscriber.tsx b/apps/dashboard/src/pages/create-subscriber.tsx index db2e86e3d2e..ec56a5823be 100644 --- a/apps/dashboard/src/pages/create-subscriber.tsx +++ b/apps/dashboard/src/pages/create-subscriber.tsx @@ -1,4 +1,3 @@ -import { FormProtection } from '@/components/form-protection'; import { Sheet, SheetContent } from '@/components/primitives/sheet'; import { CreateSubscriberForm } from '@/components/subscribers/create-subscriber-form'; import { useFormProtection } from '@/hooks/use-form-protection'; @@ -22,7 +21,10 @@ export function CreateSubscriberPage() { ); }; - const { isFormDirty, setShowAlert, showAlert } = useFormProtection(sheetRef); + const { protectedOnOpenChange, ProtectionAlert } = useFormProtection({ + ref: sheetRef, + onOpenChange: setOpen, + }); useOnElementUnmount({ element: sheetRef.current, @@ -33,15 +35,7 @@ export function CreateSubscriberPage() { return ( <> - { - if (isFormDirty) { - return setShowAlert(true); - } - setOpen(open); - }} - > + {/* Custom overlay since SheetOverlay does not work with modal={false} */}
navigate(-1)} /> - setOpen(false)} showAlert={showAlert} setShowAlert={setShowAlert} /> + + ); }