diff --git a/src/components/common/useForm.ts b/src/components/common/useForm.ts index 7a89d45..f0fe336 100644 --- a/src/components/common/useForm.ts +++ b/src/components/common/useForm.ts @@ -1,4 +1,4 @@ -import { useState, useCallback, useRef } from "react"; +import { useState, useCallback, useRef, useEffect } from "react"; import Joi from "joi"; import { GeneralIdRef } from "../../utils/GeneralIdRef"; import { @@ -88,11 +88,17 @@ interface UseFormReturn { handleSsoProviderPickerChange: (name: string, value: GeneralIdRef) => void; handleToggleChange: (e: React.ChangeEvent) => void; setState: (updates: Partial) => void; + hasUnsavedChanges: () => boolean; + markAsSaved: () => void; + setupNavigationGuard: (onBlock?: (location: Location) => void) => () => void; } export const useForm = (initialState: FormState): UseFormReturn => { const [state, setStateInternal] = useState(initialState); const schemaRef = useRef({}); + const initialDataRef = useRef( + JSON.parse(JSON.stringify(initialState.data)), + ); const setState = useCallback((updates: Partial) => { setStateInternal((prev) => ({ ...prev, ...updates })); @@ -564,6 +570,42 @@ export const useForm = (initialState: FormState): UseFormReturn => { [state.data, validate, setState], ); + // Unsaved changes detection + const hasUnsavedChanges = useCallback((): boolean => { + return ( + JSON.stringify(state.data) !== JSON.stringify(initialDataRef.current) + ); + }, [state.data]); + + const markAsSaved = useCallback((): void => { + initialDataRef.current = JSON.parse(JSON.stringify(state.data)); + }, [state.data]); + + const setupNavigationGuard = useCallback( + (onBlock?: (location: Location) => void): (() => void) => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (hasUnsavedChanges()) { + e.preventDefault(); + e.returnValue = ""; + } + }; + + window.addEventListener("beforeunload", handleBeforeUnload); + + // Return cleanup function + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + }, + [hasUnsavedChanges], + ); + + // Setup navigation guard on mount/unmount + useEffect(() => { + const cleanup = setupNavigationGuard(); + return cleanup; + }, [setupNavigationGuard]); + const api: any = { state, schema: schemaRef.current, @@ -588,6 +630,9 @@ export const useForm = (initialState: FormState): UseFormReturn => { handleToggleChange, handleTasksChange, setState, + hasUnsavedChanges, + markAsSaved, + setupNavigationGuard, }; Object.defineProperty(api, "schema", { get: () => schemaRef.current,