Skip to content

Commit

Permalink
feat(dashboard): Better form protection DX
Browse files Browse the repository at this point in the history
  • Loading branch information
desiprisg committed Feb 13, 2025
1 parent ed827b2 commit 06d70fe
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 41 deletions.
16 changes: 3 additions & 13 deletions apps/dashboard/src/components/subscribers/subscriber-drawer.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,20 +18,11 @@ export const SubscriberDrawer = forwardRef<HTMLDivElement, SubscriberDrawerProps
// Use the forwarded ref if it exists, otherwise use localRef
const ref = (forwardedRef || localRef) as React.RefObject<HTMLDivElement>;

const { showAlert, setShowAlert, isFormDirty } = useFormProtection(ref);
const { protectedOnOpenChange, ProtectionAlert } = useFormProtection({ ref, onOpenChange });

return (
<>
<Sheet
modal={false}
open={open}
onOpenChange={(open) => {
if (isFormDirty) {
return setShowAlert(true);
}
onOpenChange(open);
}}
>
<Sheet modal={false} open={open} onOpenChange={protectedOnOpenChange}>
{/* Custom overlay since SheetOverlay does not work with modal={false} */}
<div
className={cn('fade-in animate-in fixed inset-0 z-50 bg-black/20 transition-opacity duration-300', {
Expand All @@ -48,7 +38,7 @@ export const SubscriberDrawer = forwardRef<HTMLDivElement, SubscriberDrawerProps
</SheetContent>
</Sheet>

<FormProtection onClose={() => onOpenChange(false)} showAlert={showAlert} setShowAlert={setShowAlert} />
<ProtectionAlert />
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -38,16 +37,7 @@ export const StepDrawer = ({ children, title }: { children: React.ReactNode; tit
return (
<>
<PageMeta title={title} />
<Sheet
modal={false}
open={isOpen}
onOpenChange={(open) => {
if (isFormDirty) {
return setShowAlert(true);
}
setIsOpen(open);
}}
>
<Sheet modal={false} open={isOpen} onOpenChange={protectedOnOpenChange}>
<div
className={cn('animate-in fade-in fixed inset-0 z-50 bg-black/20 transition-opacity duration-300', {
'pointer-events-none opacity-0': !isOpen,
Expand All @@ -62,7 +52,7 @@ export const StepDrawer = ({ children, title }: { children: React.ReactNode; tit
</SheetContent>
</Sheet>

<FormProtection onClose={() => setIsOpen(false)} showAlert={showAlert} setShowAlert={setShowAlert} />
<ProtectionAlert />
</>
);
};
Original file line number Diff line number Diff line change
@@ -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<HTMLElement>) {
type UseFormProtectionProps = {
ref: React.RefObject<HTMLElement>;
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);

Expand Down Expand Up @@ -54,5 +61,30 @@ export function useFormProtection(ref: React.RefObject<HTMLElement>) {
return () => observer.disconnect();
}

return { isFormDirty: isDirty, showAlert, setShowAlert };
const protectedOnOpenChange = (open: boolean) => {
if (isDirty) {
setShowAlert(true);
} else {
onOpenChange?.(open);
}
};

const ProtectionAlert = useCallback(
() => (
<FormProtection
onClose={() => {
if (onProceed) {
onProceed();
} else {
onOpenChange?.(false);
}
}}
showAlert={showAlert}
setShowAlert={setShowAlert}
/>
),
[onProceed, onOpenChange, showAlert, setShowAlert]
);

return { isDirty, protectedOnOpenChange, ProtectionAlert };
}
19 changes: 7 additions & 12 deletions apps/dashboard/src/pages/create-subscriber.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand All @@ -33,15 +35,7 @@ export function CreateSubscriberPage() {

return (
<>
<Sheet
open={open}
onOpenChange={(open) => {
if (isFormDirty) {
return setShowAlert(true);
}
setOpen(open);
}}
>
<Sheet open={open} onOpenChange={protectedOnOpenChange}>
{/* Custom overlay since SheetOverlay does not work with modal={false} */}
<div
className={cn('fade-in animate-in fixed inset-0 z-50 bg-black/20 transition-opacity duration-300', {
Expand All @@ -52,7 +46,8 @@ export function CreateSubscriberPage() {
<CreateSubscriberForm onSuccess={() => navigate(-1)} />
</SheetContent>
</Sheet>
<FormProtection onClose={() => setOpen(false)} showAlert={showAlert} setShowAlert={setShowAlert} />

<ProtectionAlert />
</>
);
}

0 comments on commit 06d70fe

Please sign in to comment.