import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react'; import * as Types from 'types/types'; // Assuming you're using React Router import { useLocation, useNavigate } from 'react-router-dom'; import { useAuth } from 'hooks/AuthContext'; import { SetSnackType, SeverityType, Snack } from 'components/Snack'; // ============================ // Storage Keys // ============================ const STORAGE_KEYS = { SELECTED_CANDIDATE_ID: 'selectedCandidateId', SELECTED_JOB_ID: 'selectedJobId', SELECTED_EMPLOYER_ID: 'selectedEmployerId', LAST_ROUTE: 'lastVisitedRoute', ROUTE_STATE: 'routeState', ACTIVE_TAB: 'activeTab', APPLIED_FILTERS: 'appliedFilters', SIDEBAR_COLLAPSED: 'sidebarCollapsed' } as const; // ============================ // Route State Interface // ============================ interface RouteState { lastRoute: string | null; activeTab: string | null; appliedFilters: Record; sidebarCollapsed: boolean; } // ============================ // Enhanced App State Interface // ============================ export interface AppState { selectedCandidate: Types.Candidate | null; selectedJob: Types.Job | null; selectedEmployer: Types.Employer | null; selectedResume: Types.Resume | null; setSelectedResume: (resume: Types.Resume | null) => void; routeState: RouteState; isInitializing: boolean; } export interface AppStateActions { // Snackbar setSnack: SetSnackType; // setSelectedCandidate: (candidate: Types.Candidate | null) => void; setSelectedJob: (job: Types.Job | null) => void; setSelectedEmployer: (employer: Types.Employer | null) => void; clearSelections: () => void; // Route management saveCurrentRoute: () => void; restoreLastRoute: () => void; setActiveTab: (tab: string) => void; setFilters: (filters: Record) => void; setSidebarCollapsed: (collapsed: boolean) => void; clearRouteState: () => void; } export type AppStateContextType = AppState & AppStateActions; // ============================ // Storage Utilities // ============================ function getStoredId(key: string): string | null { try { return localStorage.getItem(key); } catch (error) { console.warn('Failed to read from localStorage:', error); return null; } } function setStoredId(key: string, id: string | null): void { try { if (id) { localStorage.setItem(key, id); } else { localStorage.removeItem(key); } } catch (error) { console.warn('Failed to write to localStorage:', error); } } function getStoredObject(key: string, defaultValue: T): T { try { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : defaultValue; } catch (error) { console.warn(`Failed to read ${key} from localStorage:`, error); return defaultValue; } } function setStoredObject(key: string, value: any): void { try { localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.warn(`Failed to write ${key} to localStorage:`, error); } } // ============================ // Route State Management // ============================ function getInitialRouteState(): RouteState { return { lastRoute: getStoredId(STORAGE_KEYS.LAST_ROUTE), activeTab: getStoredId(STORAGE_KEYS.ACTIVE_TAB), appliedFilters: getStoredObject(STORAGE_KEYS.APPLIED_FILTERS, {}), sidebarCollapsed: getStoredObject(STORAGE_KEYS.SIDEBAR_COLLAPSED, false) }; } function persistRouteState(routeState: RouteState): void { setStoredId(STORAGE_KEYS.LAST_ROUTE, routeState.lastRoute); setStoredId(STORAGE_KEYS.ACTIVE_TAB, routeState.activeTab); setStoredObject(STORAGE_KEYS.APPLIED_FILTERS, routeState.appliedFilters); setStoredObject(STORAGE_KEYS.SIDEBAR_COLLAPSED, routeState.sidebarCollapsed); } // ============================ // Enhanced App State Hook // ============================ export function useAppStateLogic(): AppStateContextType { const location = useLocation(); const navigate = useNavigate(); const { apiClient } = useAuth(); // Entity state const [selectedCandidate, setSelectedCandidateState] = useState(null); const [selectedJob, setSelectedJobState] = useState(null); const [selectedEmployer, setSelectedEmployerState] = useState(null); const [selectedResume, setSelectedResume] = useState(null); const [isInitializing, setIsInitializing] = useState(true); // Route state const [routeState, setRouteStateState] = useState(getInitialRouteState); // ============================ // Initialization Effect // ============================ useEffect(() => { const initializeFromStorage = async () => { setIsInitializing(true); try { // Get stored entity IDs const candidateId = getStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID); const jobId = getStoredId(STORAGE_KEYS.SELECTED_JOB_ID); const employerId = getStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID); // Restore entities in parallel if IDs exist const promises: Promise[] = []; if (candidateId) { promises.push( (async () => { try { const candidate = await apiClient.getCandidate(candidateId); if (candidate) { setSelectedCandidateState(candidate); console.log('Restored candidate from storage:', candidate); } else { setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null); console.log('Candidate not found, cleared from storage'); } } catch (error) { console.warn('Failed to restore candidate:', error); setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null); } })() ); } if (jobId) { promises.push( (async () => { try { const job = await apiClient.getJob(jobId); if (job) { setSelectedJobState(job); console.log('Restored job from storage:', job); } else { setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null); console.log('Job not found, cleared from storage'); } } catch (error) { console.warn('Failed to restore job:', error); setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null); } })() ); } if (employerId) { promises.push( (async () => { try { const employer = await apiClient.getEmployer(employerId); if (employer) { setSelectedEmployerState(employer); console.log('Restored employer from storage:', employer); } else { setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null); console.log('Employer not found, cleared from storage'); } } catch (error) { console.warn('Failed to restore employer:', error); setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null); } })() ); } // Wait for all restoration attempts to complete await Promise.all(promises); } catch (error) { console.error('Error during app state initialization:', error); } finally { setIsInitializing(false); } }; initializeFromStorage(); }, []); // ============================ // Auto-save current route // ============================ useEffect(() => { // Don't save certain routes (login, register, etc.) const excludedRoutes = ['/login', '/register', '/verify-email', '/reset-password']; const shouldSaveRoute = !excludedRoutes.some(route => location.pathname.startsWith(route)); if (shouldSaveRoute && !isInitializing) { setRouteStateState(prev => { const newState = { ...prev, lastRoute: location.pathname }; persistRouteState(newState); return newState; }); } }, [location.pathname, isInitializing]); // ============================ // Entity State Setters with Persistence // ============================ const setSelectedCandidate = useCallback((candidate: Types.Candidate | null) => { setSelectedCandidateState(candidate); setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, candidate?.id || null); if (candidate) { console.log('Selected candidate:', candidate); } else { console.log('Cleared selected candidate'); } }, []); const setSelectedJob = useCallback((job: Types.Job | null) => { setSelectedJobState(job); setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, job?.id || null); if (job) { console.log('Selected job:', job); } else { console.log('Cleared selected job'); } }, []); const setSelectedEmployer = useCallback((employer: Types.Employer | null) => { setSelectedEmployerState(employer); setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, employer?.id || null); if (employer) { console.log('Selected employer:', employer); } else { console.log('Cleared selected employer'); } }, []); const clearSelections = useCallback(() => { setSelectedCandidateState(null); setSelectedJobState(null); setSelectedEmployerState(null); setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null); setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null); setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null); console.log('Cleared all selections'); }, []); // ============================ // Route State Actions // ============================ const saveCurrentRoute = useCallback(() => { setRouteStateState(prev => { const newState = { ...prev, lastRoute: location.pathname }; persistRouteState(newState); return newState; }); }, [location.pathname]); const restoreLastRoute = useCallback(() => { if (routeState.lastRoute && routeState.lastRoute !== location.pathname) { navigate(routeState.lastRoute); } }, [routeState.lastRoute, location.pathname, navigate]); const setActiveTab = useCallback((tab: string) => { setRouteStateState(prev => { const newState = { ...prev, activeTab: tab }; persistRouteState(newState); return newState; }); }, []); const setFilters = useCallback((filters: Record) => { setRouteStateState(prev => { const newState = { ...prev, appliedFilters: filters }; persistRouteState(newState); return newState; }); }, []); const setSidebarCollapsed = useCallback((collapsed: boolean) => { setRouteStateState(prev => { const newState = { ...prev, sidebarCollapsed: collapsed }; persistRouteState(newState); return newState; }); }, []); const clearRouteState = useCallback(() => { const clearedState: RouteState = { lastRoute: null, activeTab: null, appliedFilters: {}, sidebarCollapsed: false }; setRouteStateState(clearedState); // Clear from localStorage localStorage.removeItem(STORAGE_KEYS.LAST_ROUTE); localStorage.removeItem(STORAGE_KEYS.ACTIVE_TAB); localStorage.removeItem(STORAGE_KEYS.APPLIED_FILTERS); localStorage.removeItem(STORAGE_KEYS.SIDEBAR_COLLAPSED); console.log('Cleared all route state'); }, []); const emptySetSnack: SetSnackType = (message: string, severity?: SeverityType) => { return; }; return { setSnack: emptySetSnack, selectedCandidate, selectedJob, selectedEmployer, routeState, selectedResume, setSelectedResume, isInitializing, setSelectedCandidate, setSelectedJob, setSelectedEmployer, clearSelections, saveCurrentRoute, restoreLastRoute, setActiveTab, setFilters, setSidebarCollapsed, clearRouteState }; } // ============================ // Context Provider // ============================ const AppStateContext = createContext(null); export function AppStateProvider({ children }: { children: React.ReactNode }) { const appState = useAppStateLogic(); const snackRef = useRef(null); // Global UI components appState.setSnack = useCallback((message: string, severity?: SeverityType) => { snackRef.current?.setSnack(message, severity); }, [snackRef]); return ( {children} ); } export function useAppState() { const context = useContext(AppStateContext); if (!context) { throw new Error('useAppState must be used within an AppStateProvider'); } return context; } // ============================ // Convenience Hooks // ============================ export function useSelectedCandidate() { const { selectedCandidate, setSelectedCandidate } = useAppState(); return { selectedCandidate, setSelectedCandidate }; } export function useSelectedJob() { const { selectedJob, setSelectedJob } = useAppState(); return { selectedJob, setSelectedJob }; } export function useSelectedEmployer() { const { selectedEmployer, setSelectedEmployer } = useAppState(); return { selectedEmployer, setSelectedEmployer }; } export const useSelectedResume = () => { const { selectedResume, setSelectedResume } = useAppState(); return { selectedResume, setSelectedResume }; }; export function useRouteState() { const { routeState, setActiveTab, setFilters, setSidebarCollapsed, restoreLastRoute, clearRouteState } = useAppState(); return { routeState, setActiveTab, setFilters, setSidebarCollapsed, restoreLastRoute, clearRouteState }; } export function useAppInitializing() { const { isInitializing } = useAppState(); return isInitializing; }