diff --git a/frontend/admin/core/views/core/langs/create-edit/hooks/use-create-edit-lang-admin.tsx b/frontend/admin/core/views/core/langs/create-edit/hooks/use-create-edit-lang-admin.tsx index d09ddadad..5fab6cc23 100644 --- a/frontend/admin/core/views/core/langs/create-edit/hooks/use-create-edit-lang-admin.tsx +++ b/frontend/admin/core/views/core/langs/create-edit/hooks/use-create-edit-lang-admin.tsx @@ -66,7 +66,7 @@ export const useCreateEditLangAdmin = ({ data }: Args) => { toast(t(data ? "edit.success" : "create.success"), { description: values.name }); - setOpen(false); + setOpen?.(false); }; return { diff --git a/frontend/admin/core/views/core/langs/table/actions/download/hooks/use-download-lang-admin.ts b/frontend/admin/core/views/core/langs/table/actions/download/hooks/use-download-lang-admin.ts index a2ab021ef..3db8a7ee5 100644 --- a/frontend/admin/core/views/core/langs/table/actions/download/hooks/use-download-lang-admin.ts +++ b/frontend/admin/core/views/core/langs/table/actions/download/hooks/use-download-lang-admin.ts @@ -49,7 +49,7 @@ export const useDownloadLangAdmin = ({ return; } - setOpen(false); + setOpen?.(false); window.open( `${CONFIG.backend_url}/files/${mutation.data.admin__core_languages__download}`, diff --git a/frontend/admin/core/views/core/langs/table/actions/update/hooks/use-update-lang-admin.ts b/frontend/admin/core/views/core/langs/table/actions/update/hooks/use-update-lang-admin.ts index b25d70040..84f85156b 100644 --- a/frontend/admin/core/views/core/langs/table/actions/update/hooks/use-update-lang-admin.ts +++ b/frontend/admin/core/views/core/langs/table/actions/update/hooks/use-update-lang-admin.ts @@ -40,7 +40,7 @@ export const useUpdateLangAdmin = ({ return; } - setOpen(false); + setOpen?.(false); toast.success(t("success"), { description: name }); diff --git a/frontend/admin/core/views/core/plugins/actions/create/hooks/use-create-plugin-admin.ts b/frontend/admin/core/views/core/plugins/actions/create/hooks/use-create-plugin-admin.ts index e52b3db65..c2b619e53 100644 --- a/frontend/admin/core/views/core/plugins/actions/create/hooks/use-create-plugin-admin.ts +++ b/frontend/admin/core/views/core/plugins/actions/create/hooks/use-create-plugin-admin.ts @@ -69,7 +69,7 @@ export const useCreatePluginAdmin = () => { description: values.name }); - setOpen(false); + setOpen?.(false); }; return { diff --git a/frontend/admin/core/views/core/styles/nav/create-edit/hooks/use-create-edit-nav-admin.ts b/frontend/admin/core/views/core/styles/nav/create-edit/hooks/use-create-edit-nav-admin.ts index 087119614..52ae0ab28 100644 --- a/frontend/admin/core/views/core/styles/nav/create-edit/hooks/use-create-edit-nav-admin.ts +++ b/frontend/admin/core/views/core/styles/nav/create-edit/hooks/use-create-edit-nav-admin.ts @@ -71,7 +71,7 @@ export const useCreateEditNavAdmin = ({ data }: CreateEditNavAdminArgs) => { description: convertText(values.name) }); - setOpen(false); + setOpen?.(false); }; return { diff --git a/frontend/admin/core/views/core/styles/nav/table/actions/delete/content.tsx b/frontend/admin/core/views/core/styles/nav/table/actions/delete/content.tsx index 4622f2df7..b226bc52b 100644 --- a/frontend/admin/core/views/core/styles/nav/table/actions/delete/content.tsx +++ b/frontend/admin/core/views/core/styles/nav/table/actions/delete/content.tsx @@ -28,7 +28,7 @@ export const ContentDeleteActionTableNavAdmin = ({ return (
- {tCore("are_your_sure")} + {tCore("are_you_sure")} {t.rich("desc", { name: () => ( diff --git a/frontend/admin/core/views/core/styles/themes/actions/create/hooks/use-create-plugin-admin.ts b/frontend/admin/core/views/core/styles/themes/actions/create/hooks/use-create-plugin-admin.ts index 81c93ae7c..2f5645e71 100644 --- a/frontend/admin/core/views/core/styles/themes/actions/create/hooks/use-create-plugin-admin.ts +++ b/frontend/admin/core/views/core/styles/themes/actions/create/hooks/use-create-plugin-admin.ts @@ -58,7 +58,7 @@ export const useCreateThemeAdmin = () => { description: values.name }); - setOpen(false); + setOpen?.(false); }; return { diff --git a/frontend/admin/core/views/core/styles/themes/actions/upload/hooks/use-upload-theme.ts b/frontend/admin/core/views/core/styles/themes/actions/upload/hooks/use-upload-theme.ts index 1cc101246..690d9dd5b 100644 --- a/frontend/admin/core/views/core/styles/themes/actions/upload/hooks/use-upload-theme.ts +++ b/frontend/admin/core/views/core/styles/themes/actions/upload/hooks/use-upload-theme.ts @@ -35,7 +35,7 @@ export const useThemeUpload = () => { return; } - setOpen(false); + setOpen?.(false); toast.success(t("success")); }; diff --git a/frontend/admin/core/views/core/styles/themes/table/actions/download/hooks/use-download-theme-admin.ts b/frontend/admin/core/views/core/styles/themes/table/actions/download/hooks/use-download-theme-admin.ts index 4e14c3a02..c2e957a18 100644 --- a/frontend/admin/core/views/core/styles/themes/table/actions/download/hooks/use-download-theme-admin.ts +++ b/frontend/admin/core/views/core/styles/themes/table/actions/download/hooks/use-download-theme-admin.ts @@ -48,7 +48,7 @@ export const useDownloadThemeAdmin = ({ return; } - setOpen(false); + setOpen?.(false); window.open( `${CONFIG.backend_url}/files/${mutation.data.admin__core_themes__download}`, diff --git a/frontend/admin/core/views/core/styles/themes/table/actions/edit/hooks/use-edit-theme-admin.ts b/frontend/admin/core/views/core/styles/themes/table/actions/edit/hooks/use-edit-theme-admin.ts index 10b764d17..83b42aebc 100644 --- a/frontend/admin/core/views/core/styles/themes/table/actions/edit/hooks/use-edit-theme-admin.ts +++ b/frontend/admin/core/views/core/styles/themes/table/actions/edit/hooks/use-edit-theme-admin.ts @@ -62,7 +62,7 @@ export const useEditThemeAdmin = ({ toast.success(t("success"), { description: values.name }); - setOpen(false); + setOpen?.(false); }; return { diff --git a/frontend/admin/core/views/members/groups/create-edit-form/hooks/use-create-edit-form-groups-members-admin.ts b/frontend/admin/core/views/members/groups/create-edit-form/hooks/use-create-edit-form-groups-members-admin.ts index 0ae8a0704..d6a4d3b66 100644 --- a/frontend/admin/core/views/members/groups/create-edit-form/hooks/use-create-edit-form-groups-members-admin.ts +++ b/frontend/admin/core/views/members/groups/create-edit-form/hooks/use-create-edit-form-groups-members-admin.ts @@ -76,7 +76,7 @@ export const useCreateEditFormGroupsMembersAdmin = ({ description: convertText(values.name) }); - setOpen(false); + setOpen?.(false); }; return { form, formSchema, onSubmit }; diff --git a/frontend/admin/core/views/members/staff/views/administrators/create-edit-form/hooks/use-form.ts b/frontend/admin/core/views/members/staff/views/administrators/create-edit-form/hooks/use-form.ts index f4f0f2694..2e7621dbb 100644 --- a/frontend/admin/core/views/members/staff/views/administrators/create-edit-form/hooks/use-form.ts +++ b/frontend/admin/core/views/members/staff/views/administrators/create-edit-form/hooks/use-form.ts @@ -69,7 +69,7 @@ export const useFormCreateEditFormGroupsMembersAdmin = () => { return; } - setOpen(false); + setOpen?.(false); toast.success(t("administrators.add.success"), { description: values.type === "group" diff --git a/frontend/admin/core/views/members/staff/views/moderators/create-edit-form/hooks/use-form.ts b/frontend/admin/core/views/members/staff/views/moderators/create-edit-form/hooks/use-form.ts index 14e8aa41e..a82ebae32 100644 --- a/frontend/admin/core/views/members/staff/views/moderators/create-edit-form/hooks/use-form.ts +++ b/frontend/admin/core/views/members/staff/views/moderators/create-edit-form/hooks/use-form.ts @@ -69,7 +69,7 @@ export const useFormCreateEditFormGroupsMembersAdmin = () => { return; } - setOpen(false); + setOpen?.(false); toast.success(t("moderators.add.success"), { description: values.type === "group" diff --git a/frontend/admin/forum/views/forums/create-edit/hooks/use-create-edit-form-forum-admin.ts b/frontend/admin/forum/views/forums/create-edit/hooks/use-create-edit-form-forum-admin.ts index 67241b2b9..81ad4bb45 100644 --- a/frontend/admin/forum/views/forums/create-edit/hooks/use-create-edit-form-forum-admin.ts +++ b/frontend/admin/forum/views/forums/create-edit/hooks/use-create-edit-form-forum-admin.ts @@ -91,7 +91,7 @@ export const useCreateEditFormForumAdmin = ({ return; } - setOpen(false); + setOpen?.(false); }; return { form, onSubmit }; diff --git a/frontend/admin/forum/views/forums/table/item/actions/delete/content.tsx b/frontend/admin/forum/views/forums/table/item/actions/delete/content.tsx index 3f5321c3b..795913abd 100644 --- a/frontend/admin/forum/views/forums/table/item/actions/delete/content.tsx +++ b/frontend/admin/forum/views/forums/table/item/actions/delete/content.tsx @@ -35,7 +35,7 @@ export const ContentDeleteActionForumAdmin = ({ - {tCore("are_your_sure")} + {tCore("are_you_sure")} {t.rich("desc", { name: () => ( diff --git a/frontend/components/ui/dialog.tsx b/frontend/components/ui/dialog.tsx index 265eff82d..922d88fbe 100644 --- a/frontend/components/ui/dialog.tsx +++ b/frontend/components/ui/dialog.tsx @@ -2,6 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; +import { useTranslations } from "next-intl"; import { createContext, forwardRef, @@ -9,19 +10,24 @@ import { 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({ open: false, - setOpen: () => {} + setOpen: () => {}, + isDirty: false, + setIsDirty: () => {} }); export const useDialog = () => useContext(DialogContext); @@ -33,10 +39,16 @@ const Dialog = ({ ...props }: DialogPrimitive.DialogProps) => { const [open, setOpen] = useState(false); + const [isDirty, setIsDirty] = useState(false); return ( , ComponentPropsWithoutRef ->(({ children, className, ...props }, ref) => ( - - - - {children} - - - Close - - - -)); +>(({ children, className, ...props }, ref) => { + const t = useTranslations("core"); + const { isDirty, setOpen } = useDialog(); + + const handleBeforeUnload = ( + e: + | CustomEvent<{ + originalEvent: PointerEvent; + }> + | MouseEvent + ) => { + if (!isDirty) return; + e.preventDefault(); + + if (confirm(t("are_you_sure_want_to_leave_form"))) { + setOpen?.(false); + } + }; + + return ( + + + + {children} + + + Close + + + + ); +}); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ diff --git a/frontend/components/ui/form.tsx b/frontend/components/ui/form.tsx index 37d3d921b..231f42bfc 100644 --- a/frontend/components/ui/form.tsx +++ b/frontend/components/ui/form.tsx @@ -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, @@ -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) { + 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 ; +} type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, diff --git a/frontend/hooks/core/settings/avatar/use-copper-modal-change-avatar.ts b/frontend/hooks/core/settings/avatar/use-copper-modal-change-avatar.ts index 13945db48..d5a4d9c8a 100644 --- a/frontend/hooks/core/settings/avatar/use-copper-modal-change-avatar.ts +++ b/frontend/hooks/core/settings/avatar/use-copper-modal-change-avatar.ts @@ -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); diff --git a/frontend/hooks/core/settings/avatar/use-modal-change-avatar.ts b/frontend/hooks/core/settings/avatar/use-modal-change-avatar.ts index bff9d6635..3f630dc08 100644 --- a/frontend/hooks/core/settings/avatar/use-modal-change-avatar.ts +++ b/frontend/hooks/core/settings/avatar/use-modal-change-avatar.ts @@ -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); diff --git a/frontend/hooks/forums/forum/topics/create/use-create-topic.ts b/frontend/hooks/forums/forum/topics/create/use-create-topic.ts index 7eaddc557..666991bd4 100644 --- a/frontend/hooks/forums/forum/topics/create/use-create-topic.ts +++ b/frontend/hooks/forums/forum/topics/create/use-create-topic.ts @@ -61,7 +61,7 @@ export const useCreateTopic = ({ forumId }: Props) => { push(`/topic/${convertNameToLink({ id, name: title })}`); } - setOpen(false); + setOpen?.(false); }; return { form, onSubmit }; diff --git a/frontend/langs/en/core.json b/frontend/langs/en/core.json index c471b786d..648886722 100644 --- a/frontend/langs/en/core.json +++ b/frontend/langs/en/core.json @@ -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", diff --git a/frontend/langs/pl/core.json b/frontend/langs/pl/core.json index f781aaf74..abf84632c 100644 --- a/frontend/langs/pl/core.json +++ b/frontend/langs/pl/core.json @@ -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",