import { useState, useEffect, useCallback, useRef } from 'react'; import { useUser, useCurrentUser } from './useUsers'; import { useApiRequest } from './useApi'; import { GenericDataHook } from '../core/PageManager/pageInterface'; import type { SettingsFieldConfig } from '../core/PageManager/pageInterface'; // Interface for unified settings data export interface SettingsData { // User data (from API) username?: string; fullName?: string; email?: string; language?: string; privilege?: string; enabled?: boolean; authenticationAuthority?: string; // Phone name (localStorage) phoneName?: string; // Theme (localStorage) theme?: string; // Speech data (localStorage) speechData?: any; // Nested speech fields mandate_general?: { company_name?: string; industry?: string; contact_info?: { email?: string; phone?: string; street?: string; postal_code?: string; city?: string; country?: string; }; business_hours?: string; timezone?: string; }; setup_contacts?: boolean; } // Create settings hook factory export function createSettingsHook(): () => GenericDataHook { return function useSettings() { const { user: currentUser } = useCurrentUser(); const { getUser, updateUser } = useUser(); const { request } = useApiRequest(); const [settingsData, setSettingsData] = useState({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [settingsFields, setSettingsFields] = useState>({}); const [settingsLoading, setSettingsLoading] = useState>({}); const [settingsErrors, setSettingsErrors] = useState>({}); // Track if we've loaded data initially to prevent infinite loops const hasLoadedRef = useRef(false); const currentUserIdRef = useRef(currentUser?.id); // Load phone name from localStorage const _loadPhoneName = useCallback((): string => { try { return localStorage.getItem('userPhoneName') || ''; } catch (error) { console.error('Failed to load phone name from localStorage:', error); return ''; } }, []); void _loadPhoneName; // Intentionally unused, reserved for future use // Load theme from localStorage const _loadTheme = useCallback((): string => { try { const savedTheme = localStorage.getItem('theme'); if (savedTheme) { return savedTheme; } // Default to system preference return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } catch (error) { console.error('Failed to load theme from localStorage:', error); return 'light'; } }, []); void _loadTheme; // Intentionally unused, reserved for future use // Load speech data from localStorage const _loadSpeechData = useCallback((): any | null => { try { const savedData = localStorage.getItem('speechSignUpData'); const timestamp = localStorage.getItem('speechSignUpTimestamp'); if (savedData && timestamp) { const parsedData = JSON.parse(savedData); const savedTime = parseInt(timestamp); const now = Date.now(); const twentyFourHours = 24 * 60 * 60 * 1000; // Check if data is still valid (within 24 hours) if (now - savedTime < twentyFourHours) { return parsedData; } else { // Data expired, clear it localStorage.removeItem('speechSignUpData'); localStorage.removeItem('speechSignUpTimestamp'); return null; } } return null; } catch (error) { console.error('Error loading speech data:', error); return null; } }, []); void _loadSpeechData; // Intentionally unused, reserved for future use // Fetch user data from API const _fetchUserData = useCallback(async () => { if (!currentUser?.id) return null; try { const userData = await getUser(currentUser.id); return userData; } catch (error) { console.error('Failed to fetch user data:', error); throw error; } }, [currentUser?.id, getUser]); void _fetchUserData; // Intentionally unused, reserved for future use // Fetch field definitions from backend const _fetchFieldsForSection = useCallback(async (sectionId: string): Promise => { try { setSettingsLoading(prev => ({ ...prev, [sectionId]: true })); setSettingsErrors(prev => ({ ...prev, [sectionId]: null })); // TODO: Replace with actual backend endpoint // For now, return empty array - fields will come from backend later const response = await request({ url: `/api/settings/fields?section=${sectionId}`, method: 'get' }); const fields = response?.fields || []; setSettingsFields(prev => ({ ...prev, [sectionId]: fields })); return fields; } catch (error: any) { const errorMessage = error.message || `Failed to load fields for ${sectionId}`; console.error(`Error fetching fields for section ${sectionId}:`, error); setSettingsErrors(prev => ({ ...prev, [sectionId]: errorMessage })); return []; } finally { setSettingsLoading(prev => ({ ...prev, [sectionId]: false })); } }, [request]); void _fetchFieldsForSection; // Intentionally unused, reserved for future use // Load all settings data const loadSettingsData = useCallback(async () => { // Prevent multiple simultaneous loads if (loading && hasLoadedRef.current) return; try { setLoading(true); setError(null); // Load from different sources - call functions directly to avoid dependency issues const userData = currentUser?.id ? await getUser(currentUser.id) : null; const phoneName = localStorage.getItem('userPhoneName') || ''; const savedTheme = localStorage.getItem('theme'); const theme = savedTheme || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); // Load speech data let speechData: any | null = null; try { const savedData = localStorage.getItem('speechSignUpData'); const timestamp = localStorage.getItem('speechSignUpTimestamp'); if (savedData && timestamp) { const parsedData = JSON.parse(savedData); const savedTime = parseInt(timestamp); const now = Date.now(); const twentyFourHours = 24 * 60 * 60 * 1000; if (now - savedTime < twentyFourHours) { speechData = parsedData; } else { localStorage.removeItem('speechSignUpData'); localStorage.removeItem('speechSignUpTimestamp'); } } } catch (error) { console.error('Error loading speech data:', error); } // Merge all data into unified object const unifiedData: SettingsData = { ...(userData || {}), phoneName, theme, speechData, // Flatten speech data if it exists ...(speechData?.mandate_general && { mandate_general: speechData.mandate_general }), ...(speechData?.setup_contacts !== undefined && { setup_contacts: speechData.setup_contacts }) }; setSettingsData(unifiedData); hasLoadedRef.current = true; } catch (error: any) { console.error('Error loading settings data:', error); setError(error.message || 'Failed to load settings'); } finally { setLoading(false); } }, [currentUser?.id, getUser]); // Only depend on currentUser?.id and getUser // Save section data const saveSection = useCallback(async (sectionId: string, data: any) => { try { setSettingsLoading(prev => ({ ...prev, [sectionId]: true })); setSettingsErrors(prev => ({ ...prev, [sectionId]: null })); if (sectionId === 'user-info') { // Separate user API data from localStorage data const { phoneName, id, mandateId, ...userApiData } = data; // Build clean update object with only allowed fields (explicitly exclude id and mandateId) // Allowed fields: username, email, fullName, language, enabled, privilege, authenticationAuthority const userUpdateData: Record = {}; const allowedFields = ['username', 'email', 'fullName', 'language', 'enabled', 'privilege', 'authenticationAuthority']; allowedFields.forEach(field => { if (userApiData.hasOwnProperty(field)) { userUpdateData[field] = userApiData[field]; } }); // Save user data via API (only allowed fields, no id or mandateId) if (currentUser?.id && Object.keys(userUpdateData).length > 0) { const updatedUser = await updateUser(currentUser.id, userUpdateData); // Update local state with API data setSettingsData(prev => ({ ...prev, ...updatedUser })); // Update localStorage cache localStorage.setItem('currentUser', JSON.stringify(updatedUser)); // Dispatch event to notify other components window.dispatchEvent(new CustomEvent('userInfoUpdated')); } // Save phone name to localStorage separately if (phoneName !== undefined) { if (phoneName?.trim()) { localStorage.setItem('userPhoneName', phoneName.trim()); } else { localStorage.removeItem('userPhoneName'); } // Update local state with phone name setSettingsData(prev => ({ ...prev, phoneName })); } } else if (sectionId === 'theme') { // Save theme to localStorage localStorage.setItem('theme', data.theme); // Apply theme immediately document.documentElement.setAttribute('data-theme', data.theme); document.documentElement.classList.remove('light-theme', 'dark-theme'); document.documentElement.classList.add(`${data.theme}-theme`); // Update local state setSettingsData(prev => ({ ...prev, theme: data.theme })); } else if (sectionId === 'speech-settings') { // Save speech data to localStorage localStorage.setItem('speechSignUpData', JSON.stringify(data)); localStorage.setItem('speechSignUpTimestamp', Date.now().toString()); // Update local state setSettingsData(prev => ({ ...prev, speechData: data, ...(data.mandate_general && { mandate_general: data.mandate_general }), ...(data.setup_contacts !== undefined && { setup_contacts: data.setup_contacts }) })); // Dispatch event to notify other components window.dispatchEvent(new CustomEvent('speechSignUpChanged')); } else if (sectionId === 'phone-name') { // Save phone name to localStorage if (data.phoneName?.trim()) { localStorage.setItem('userPhoneName', data.phoneName.trim()); } else { localStorage.removeItem('userPhoneName'); } // Update local state setSettingsData(prev => ({ ...prev, phoneName: data.phoneName })); } } catch (error: any) { const errorMessage = error.message || `Failed to save ${sectionId}`; console.error(`Error saving section ${sectionId}:`, error); setSettingsErrors(prev => ({ ...prev, [sectionId]: errorMessage })); throw error; } finally { setSettingsLoading(prev => ({ ...prev, [sectionId]: false })); } }, [currentUser?.id, updateUser]); // Initial load - only when currentUser?.id changes or on first mount useEffect(() => { // Only load if: // 1. We haven't loaded yet, OR // 2. The user ID changed if (!hasLoadedRef.current || currentUserIdRef.current !== currentUser?.id) { currentUserIdRef.current = currentUser?.id; // Load data directly to avoid dependency issues const loadData = async () => { if (loading && hasLoadedRef.current) return; try { setLoading(true); setError(null); // Load from different sources const userData = currentUser?.id ? await getUser(currentUser.id) : null; const phoneName = localStorage.getItem('userPhoneName') || ''; const savedTheme = localStorage.getItem('theme'); const theme = savedTheme || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); // Load speech data let speechData: any | null = null; try { const savedData = localStorage.getItem('speechSignUpData'); const timestamp = localStorage.getItem('speechSignUpTimestamp'); if (savedData && timestamp) { const parsedData = JSON.parse(savedData); const savedTime = parseInt(timestamp); const now = Date.now(); const twentyFourHours = 24 * 60 * 60 * 1000; if (now - savedTime < twentyFourHours) { speechData = parsedData; } else { localStorage.removeItem('speechSignUpData'); localStorage.removeItem('speechSignUpTimestamp'); } } } catch (error) { console.error('Error loading speech data:', error); } // Merge all data into unified object const unifiedData: SettingsData = { ...(userData || {}), phoneName, theme, speechData, ...(speechData?.mandate_general && { mandate_general: speechData.mandate_general }), ...(speechData?.setup_contacts !== undefined && { setup_contacts: speechData.setup_contacts }) }; setSettingsData(unifiedData); hasLoadedRef.current = true; } catch (error: any) { console.error('Error loading settings data:', error); setError(error.message || 'Failed to load settings'); } finally { setLoading(false); } }; loadData(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentUser?.id]); // Only depend on currentUser?.id return { data: [], // Not used for settings loading, error, settingsData, settingsFields, settingsLoading, settingsErrors, saveSection, refetch: loadSettingsData } as GenericDataHook; }; }