import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { useNavigate } from "react-router-dom"; import Button from "../../../components/Button"; type NavigationAction = () => void; type UnsavedChangesRegistration = { title?: string; message?: string; onSave: () => boolean | Promise; onDiscard?: () => void; }; type UnsavedChangesContextValue = { hasUnsavedChanges: boolean; registerUnsavedChanges: (registration: UnsavedChangesRegistration | null) => () => void; requestNavigation: (action: NavigationAction) => void; }; const UnsavedChangesContext = createContext(null); export function CampaignUnsavedChangesProvider({ children }: { children: ReactNode }) { const navigate = useNavigate(); const [registration, setRegistration] = useState(null); const [pendingAction, setPendingAction] = useState(null); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(""); const registrationRef = useRef(null); useEffect(() => { registrationRef.current = registration; }, [registration]); const hasUnsavedChanges = Boolean(registration); const registerUnsavedChanges = useCallback((next: UnsavedChangesRegistration | null) => { setRegistration(next); return () => { setRegistration((current) => current === next ? null : current); }; }, []); const proceed = useCallback((action: NavigationAction) => { setPendingAction(null); setSaveError(""); action(); }, []); const requestNavigation = useCallback((action: NavigationAction) => { const active = registrationRef.current; if (!active) { action(); return; } setSaveError(""); setPendingAction(() => action); }, []); useEffect(() => { function onBeforeUnload(event: BeforeUnloadEvent) { if (!registrationRef.current) return; event.preventDefault(); event.returnValue = ""; } window.addEventListener("beforeunload", onBeforeUnload); return () => window.removeEventListener("beforeunload", onBeforeUnload); }, []); useEffect(() => { function onDocumentClick(event: MouseEvent) { if (!registrationRef.current) return; if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return; const target = event.target as Element | null; const anchor = target?.closest?.("a[href]") as HTMLAnchorElement | null; if (!anchor) return; if (anchor.target && anchor.target !== "_self") return; if (anchor.hasAttribute("download")) return; if (anchor.getAttribute("href")?.startsWith("#")) return; const destination = new URL(anchor.href, window.location.href); const current = new URL(window.location.href); if (destination.href === current.href) return; event.preventDefault(); event.stopPropagation(); requestNavigation(() => { if (destination.origin === current.origin) { navigate(`${destination.pathname}${destination.search}${destination.hash}`); } else { window.location.assign(destination.href); } }); } document.addEventListener("click", onDocumentClick, true); return () => document.removeEventListener("click", onDocumentClick, true); }, [navigate, requestNavigation]); async function handleSaveAndLeave() { const action = pendingAction; const active = registrationRef.current; if (!action || !active) return; setSaving(true); setSaveError(""); try { const ok = await active.onSave(); if (!ok) { setSaveError("The changes could not be saved. Please review the page message and try again."); return; } proceed(action); } catch (err) { setSaveError(err instanceof Error ? err.message : String(err)); } finally { setSaving(false); } } function handleDiscardAndLeave() { const action = pendingAction; const active = registrationRef.current; if (!action) return; active?.onDiscard?.(); proceed(action); } const value = useMemo(() => ({ hasUnsavedChanges, registerUnsavedChanges, requestNavigation }), [hasUnsavedChanges, registerUnsavedChanges, requestNavigation]); return ( {children} {pendingAction && registration && (

{registration.title ?? "Unsaved campaign changes"}

{registration.message ?? "This campaign page has unsaved changes. Save them before leaving, or discard the changes and continue."}

{saveError &&
{saveError}
}
)}
); } export function useCampaignUnsavedChanges() { const context = useContext(UnsavedChangesContext); if (!context) { throw new Error("useCampaignUnsavedChanges must be used inside CampaignUnsavedChangesProvider"); } return context; } export function useRegisterCampaignUnsavedChanges(registration: UnsavedChangesRegistration | null) { const { registerUnsavedChanges } = useCampaignUnsavedChanges(); useEffect(() => { return registerUnsavedChanges(registration); }, [registerUnsavedChanges, registration]); }