import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { toast } from "sonner"; import type { DashboardData } from '../types'; export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error' | 'unsaved'; export interface ModuleData { options?: Record; i18n?: Record; optionNames?: Record; [key: string]: unknown; } interface UseSettingsManagerOptions { rootData: DashboardData; moduleData: ModuleData; initialSettings: T; settingsPayloadKey?: string; autoSaveDelay?: number; onBeforeSave?: (settings: T) => T; onAfterSave?: (result: unknown) => void; } export function useSettingsManager>({ rootData, moduleData, initialSettings, settingsPayloadKey, autoSaveDelay = 1500, onBeforeSave, onAfterSave }: UseSettingsManagerOptions) { const [settings, setSettings] = useState(() => ({ ...initialSettings, ...(moduleData?.options || {}) })); const [saveStatus, setSaveStatus] = useState('idle'); const [isSaving, setIsSaving] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const t = useMemo(() => (moduleData?.i18n || {}) as Record, [moduleData?.i18n]); const isInitialMount = useRef(true); // Adjust state if moduleData changes (Using the "Adjusting state during render" pattern) const [prevModuleData, setPrevModuleData] = useState(moduleData); if (moduleData !== prevModuleData) { setPrevModuleData(moduleData); if (moduleData?.options) { setSettings(prev => ({ ...prev, ...moduleData.options }) as T); } } const updateSetting = useCallback((key: string, value: unknown) => { setSettings((prev) => { const next = { ...prev, [key]: value } as T; const optionNames = moduleData?.optionNames || {}; const dbKey = optionNames[key]; if (dbKey) { (next as Record)[dbKey] = value; } else { const prettyKey = Object.keys(optionNames).find(k => optionNames[k] === key); if (prettyKey) { (next as Record)[prettyKey] = value; } } return next; }); setHasUnsavedChanges(true); }, [moduleData]); const handleSave = useCallback(async (overrides?: T | boolean) => { const silent = typeof overrides === 'boolean' ? overrides : false; const settingsToUse = typeof overrides === 'object' ? overrides : settings; if (!silent) setIsSaving(true); setSaveStatus('saving'); try { const settingsToSave: Record = {}; const sourceSettings = onBeforeSave ? onBeforeSave(settingsToUse) : settingsToUse; Object.keys(sourceSettings).forEach(key => { const optionNames = moduleData?.optionNames || {}; const dbKey = optionNames[key] || key; let value = sourceSettings[key]; if (typeof value === 'boolean') { value = value ? 1 : 0; } settingsToSave[dbKey] = value; }); const payload = settingsPayloadKey ? { settings: { [settingsPayloadKey]: settingsToSave } } : { settings: settingsToSave }; const response = await fetch(`${rootData?.global?.settingsRestUrl}${rootData?.global?.settingsRestUrl?.includes('?') ? '&' : '?'}_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': rootData?.global?.wpRestNonce || '', }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`Server responded with ${response.status}`); } const result = await response.json(); setHasUnsavedChanges(false); setSaveStatus('saved'); window.dispatchEvent(new CustomEvent('wawp-refresh-data')); if (!silent) { toast.success(t.settingsSaved || 'Settings saved successfully'); } if (onAfterSave) onAfterSave(result); setTimeout(() => setSaveStatus('idle'), 3000); } catch (error: unknown) { setSaveStatus('error'); const errorMessage = error instanceof Error ? error.message : String(error); if (!silent) { toast.error(errorMessage || t.errorOccurred || 'An error occurred'); } console.error('Save error:', error); } finally { if (!silent) setIsSaving(false); } }, [rootData, moduleData, settings, settingsPayloadKey, t, onBeforeSave, onAfterSave]); useEffect(() => { if (isInitialMount.current) { isInitialMount.current = false; return; } if (!hasUnsavedChanges) return; const timer = setTimeout(() => { handleSave(true); }, autoSaveDelay); return () => clearTimeout(timer); }, [settings, hasUnsavedChanges, handleSave, autoSaveDelay]); // Before unload warning useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges]); return { settings, setSettings, updateSetting, handleSave, saveStatus, hasUnsavedChanges, setHasUnsavedChanges, isSaving, t }; }