Skip to content

Commit

Permalink
Merge pull request #251 from aXenDeveloper/form/before_unload
Browse files Browse the repository at this point in the history
feat(frontend): Add before unload to form when isDirty is true
  • Loading branch information
aXenDeveloper authored Mar 3, 2024
2 parents 7d7ad44 + ce799e2 commit d281c19
Show file tree
Hide file tree
Showing 22 changed files with 106 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const useCreateEditLangAdmin = ({ data }: Args) => {
toast(t(data ? "edit.success" : "create.success"), {
description: values.name
});
setOpen(false);
setOpen?.(false);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const useDownloadLangAdmin = ({
return;
}

setOpen(false);
setOpen?.(false);

window.open(
`${CONFIG.backend_url}/files/${mutation.data.admin__core_languages__download}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const useUpdateLangAdmin = ({
return;
}

setOpen(false);
setOpen?.(false);
toast.success(t("success"), {
description: name
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const useCreatePluginAdmin = () => {
description: values.name
});

setOpen(false);
setOpen?.(false);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const useCreateEditNavAdmin = ({ data }: CreateEditNavAdminArgs) => {
description: convertText(values.name)
});

setOpen(false);
setOpen?.(false);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ContentDeleteActionTableNavAdmin = ({
return (
<form action={onSubmit}>
<AlertDialogHeader>
<AlertDialogTitle>{tCore("are_your_sure")}</AlertDialogTitle>
<AlertDialogTitle>{tCore("are_you_sure")}</AlertDialogTitle>
<AlertDialogDescription>
{t.rich("desc", {
name: () => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const useCreateThemeAdmin = () => {
description: values.name
});

setOpen(false);
setOpen?.(false);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useThemeUpload = () => {
return;
}

setOpen(false);
setOpen?.(false);
toast.success(t("success"));
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const useDownloadThemeAdmin = ({
return;
}

setOpen(false);
setOpen?.(false);

window.open(
`${CONFIG.backend_url}/files/${mutation.data.admin__core_themes__download}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const useEditThemeAdmin = ({
toast.success(t("success"), {
description: values.name
});
setOpen(false);
setOpen?.(false);
};

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const useCreateEditFormGroupsMembersAdmin = ({
description: convertText(values.name)
});

setOpen(false);
setOpen?.(false);
};

return { form, formSchema, onSubmit };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const useFormCreateEditFormGroupsMembersAdmin = () => {
return;
}

setOpen(false);
setOpen?.(false);
toast.success(t("administrators.add.success"), {
description:
values.type === "group"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const useFormCreateEditFormGroupsMembersAdmin = () => {
return;
}

setOpen(false);
setOpen?.(false);
toast.success(t("moderators.add.success"), {
description:
values.type === "group"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const useCreateEditFormForumAdmin = ({
return;
}

setOpen(false);
setOpen?.(false);
};

return { form, onSubmit };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const ContentDeleteActionForumAdmin = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<AlertDialogHeader>
<AlertDialogTitle>{tCore("are_your_sure")}</AlertDialogTitle>
<AlertDialogTitle>{tCore("are_you_sure")}</AlertDialogTitle>
<AlertDialogDescription>
{t.rich("desc", {
name: () => (
Expand Down
84 changes: 60 additions & 24 deletions frontend/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@

import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { useTranslations } from "next-intl";
import {
createContext,
forwardRef,
useContext,
useState,
type ComponentPropsWithoutRef,
type ElementRef,
type HTMLAttributes
type HTMLAttributes,
type MouseEvent
} from "react";

import { cn } from "@/functions/classnames";

interface DialogContextArgs {
open: boolean;
setOpen: (value: boolean) => void;
isDirty?: boolean;
open?: boolean;
setIsDirty?: (value: boolean) => void;
setOpen?: (value: boolean) => void;
}

export const DialogContext = createContext<DialogContextArgs>({
open: false,
setOpen: () => {}
setOpen: () => {},
isDirty: false,
setIsDirty: () => {}
});

export const useDialog = () => useContext(DialogContext);
Expand All @@ -33,10 +39,16 @@ const Dialog = ({
...props
}: DialogPrimitive.DialogProps) => {
const [open, setOpen] = useState(false);
const [isDirty, setIsDirty] = useState(false);

return (
<DialogContext.Provider
value={{ open: openProp ?? open, setOpen: onOpenChange ?? setOpen }}
value={{
open: openProp ?? open,
setOpen: onOpenChange ?? setOpen,
isDirty,
setIsDirty
}}
>
<DialogPrimitive.Root
open={openProp ?? open}
Expand Down Expand Up @@ -71,25 +83,49 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = forwardRef<
ElementRef<typeof DialogPrimitive.Content>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ children, className, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-5 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full overflow-y-auto max-h-[calc(100vh_-_2rem)]",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute w-9 h-9 flex items-center justify-center right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-6 w-6" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
>(({ children, className, ...props }, ref) => {
const t = useTranslations("core");
const { isDirty, setOpen } = useDialog();

const handleBeforeUnload = (
e:
| CustomEvent<{
originalEvent: PointerEvent;
}>
| MouseEvent<HTMLButtonElement>
) => {
if (!isDirty) return;
e.preventDefault();

if (confirm(t("are_you_sure_want_to_leave_form"))) {
setOpen?.(false);
}
};

return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
onPointerDownOutside={handleBeforeUnload}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-5 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full overflow-y-auto max-h-screen",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close
onClick={handleBeforeUnload}
className="absolute w-9 h-9 flex items-center justify-center right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X className="h-6 w-6" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
});
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({
Expand Down
28 changes: 24 additions & 4 deletions frontend/components/ui/form.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { useBeforeUnload } from "react-use";
import {
Controller,
FormProvider,
useFormContext,
type ControllerProps,
type FieldPath,
type FieldValues
type FieldValues,
type FormProviderProps
} from "react-hook-form";
import {
createContext,
Expand All @@ -15,13 +17,31 @@ import {
useId,
type ComponentPropsWithoutRef,
type ElementRef,
type HTMLAttributes
type HTMLAttributes,
useEffect
} from "react";
import { useTranslations } from "next-intl";

import { Label } from "@/components/ui/label";
import { cn } from "@/functions/classnames";

const Form = FormProvider;
import { useDialog } from "./dialog";

function Form<
TFieldValues extends FieldValues,
TContext = unknown,
TTransformedValues extends FieldValues = TFieldValues
>(props: FormProviderProps<TFieldValues, TContext, TTransformedValues>) {
const t = useTranslations("core");
const formIsDirty = props.formState.isDirty;
useBeforeUnload(formIsDirty, t("are_you_sure_want_to_leave_form"));
const { setIsDirty } = useDialog();

useEffect(() => {
setIsDirty?.(formIsDirty);
}, [formIsDirty]);

return <FormProvider {...props} />;
}

type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const useCopperModalChangeAvatar = () => {
toast.success(t("settings.change_avatar.options.upload.title"), {
description: t("settings.change_avatar.options.upload.success")
});
setOpen(false);
setOpen?.(false);
}

setPending(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const useModalChangeAvatar = () => {
toast.success(t("settings.change_avatar.options.delete.title"), {
description: t("settings.change_avatar.options.delete.success")
});
setOpen(false);
setOpen?.(false);
}

setPending(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const useCreateTopic = ({ forumId }: Props) => {
push(`/topic/${convertNameToLink({ id, name: title })}`);
}

setOpen(false);
setOpen?.(false);
};

return { form, onSubmit };
Expand Down
3 changes: 2 additions & 1 deletion frontend/langs/en/core.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"upload_new_version": "Upload New Version",
"more_actions": "More Actions",
"download": "Download",
"are_your_sure": "Are you sure?",
"are_you_sure": "Are you sure?",
"are_you_absolutely_sure": "Are you absolutely sure?",
"are_you_sure_want_to_leave_form": "Are you sure you want to leave the form? Your changes may not be saved.",
"hands_up": "Hands up!",
"cancel": "Cancel",
"confirm": "Confirm",
Expand Down
3 changes: 2 additions & 1 deletion frontend/langs/pl/core.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"upload_new_version": "Prześlij nową wersję",
"more_actions": "Więcej akcji",
"download": "Pobierz",
"are_your_sure": "Czy jesteś pewien?",
"are_you_sure": "Czy jesteś pewien?",
"are_you_sure_want_to_leave_form": "Czy na pewno chcesz opuścić formularz? Twoje zmiany mogą nie zostać zapisane.",
"are_you_absolutely_sure": "Czy jesteś absolutnie pewien?",
"hands_up": "Uwaga!",
"cancel": "Anuluj",
Expand Down

0 comments on commit d281c19

Please sign in to comment.