import { createContext, useContext, useState, type ReactNode, useEffect } from 'react'; import { apiClient, setCsrfToken } from '../api/client'; export interface User { id: string; name: string; role: 'supervisor' | 'admin' | 'user'; department?: string; position?: string; phone?: string; last_login?: string; } interface AuthContextType { user: User | null; login: (id: string, password: string) => Promise; logout: () => void; isAuthenticated: boolean; isLoading: boolean; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // Check for existing session on mount and periodically useEffect(() => { let lastActivity = Date.now(); let timeoutMs = 3600000; // Default 1 hour const checkSession = async () => { try { const response = await apiClient.get('/check'); if (response.data.isAuthenticated) { setUser(response.data.user); if (response.data.csrfToken) setCsrfToken(response.data.csrfToken); if (response.data.sessionTimeout) timeoutMs = response.data.sessionTimeout; } else { // Safety: only redirect if we were previously logged in setUser(prev => { if (prev) { setCsrfToken(null); window.location.href = '/login?expired=true'; return null; } return prev; }); } } catch (error) { setUser(prev => { if (prev) { setCsrfToken(null); window.location.href = '/login?expired=true'; return null; } return prev; }); } finally { setIsLoading(false); } }; const updateActivity = () => { lastActivity = Date.now(); }; // Activity Listeners window.addEventListener('mousemove', updateActivity); window.addEventListener('keydown', updateActivity); window.addEventListener('scroll', updateActivity); window.addEventListener('click', updateActivity); checkSession(); // Check activity status every 30 seconds const activityInterval = setInterval(() => { // Functional check to avoid stale user closure setUser(current => { if (current) { const idleTime = Date.now() - lastActivity; if (idleTime >= timeoutMs) { console.log('Idle timeout reached. Checking session...'); checkSession(); } } return current; }); }, 30000); // Fallback polling every 5 minutes (for secondary tabs etc) const pollInterval = setInterval(checkSession, 300000); return () => { window.removeEventListener('mousemove', updateActivity); window.removeEventListener('keydown', updateActivity); window.removeEventListener('scroll', updateActivity); window.removeEventListener('click', updateActivity); clearInterval(activityInterval); clearInterval(pollInterval); }; }, []); // Removed [user] to prevent infinite loop const login = async (id: string, password: string) => { try { const response = await apiClient.post('/login', { id, password }); if (response.data.success) { setUser(response.data.user); if (response.data.csrfToken) { setCsrfToken(response.data.csrfToken); } return true; } return false; } catch (error) { console.error('Login failed:', error); return false; } }; const logout = async () => { try { await apiClient.post('/logout'); } catch (error) { console.error('Logout failed:', error); } finally { setUser(null); setCsrfToken(null); } }; return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }