import React, { createContext, useContext, useEffect, useState } from "react"; import { SetSnackType } from '../components/Snack'; import { User, Guest, Candidate } from 'types/types'; import { ApiClient } from "services/api-client"; import { debugConversion } from "types/conversion"; type UserContextType = { apiClient: ApiClient; user: User | null; guest: Guest; candidate: Candidate | null; setUser: (user: User | null) => void; setCandidate: (candidate: Candidate | null) => void; }; const UserContext = createContext(undefined); const useUser = () => { const ctx = useContext(UserContext); if (!ctx) throw new Error("useUser must be used within a UserProvider"); return ctx; }; interface UserProviderProps { children: React.ReactNode; setSnack: SetSnackType; }; const UserProvider: React.FC = (props: UserProviderProps) => { const { children, setSnack } = props; const [apiClient, setApiClient] = useState(new ApiClient()); const [candidate, setCandidate] = useState(null); const [guest, setGuest] = useState(null); const [user, setUser] = useState(null); const [activeUser, setActiveUser] = useState(null); useEffect(() => { console.log("Candidate =>", candidate); }, [candidate]); useEffect(() => { console.log("Guest =>", guest); }, [guest]); /* If the user changes to a non-null value, create a new * apiClient with the access token */ useEffect(() => { console.log("User => ", user); if (user === null) { return; } /* This apiClient will persist until the user is changed * or logged out */ const accessToken = localStorage.getItem('accessToken'); if (!accessToken) { throw Error("accessToken is not set for user!"); } setApiClient(new ApiClient(accessToken)); }, [user]); /* Handle logout if any consumers of UserProvider setUser to NULL */ useEffect(() => { /* If there is an active user and it is the same as the * new user, do nothing */ if (activeUser && activeUser.email === user?.email) { return; } const logout = async () => { if (!activeUser) { return; } console.log(`Logging out ${activeUser.email}`); try { const accessToken = localStorage.getItem('accessToken'); const refreshToken = localStorage.getItem('refreshToken'); if (!accessToken || !refreshToken) { setSnack("Authentication tokens are invalid.", "error"); return; } const results = await apiClient.logout(accessToken, refreshToken); if (results.error) { console.error(results.error); setSnack(results.error.message, "error") } } catch (e) { console.error(e); setSnack(`Unable to logout: ${e}`, "error") } localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); localStorage.removeItem('userData'); createGuestSession(); setUser(null); }; setActiveUser(user); if (!user) { logout(); } }, [user, apiClient, activeUser]); const createGuestSession = () => { console.log("TODO: Convert this to query the server for the session instead of generating it."); const sessionId = `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const guest: Guest = { sessionId, createdAt: new Date(), lastActivity: new Date(), ipAddress: 'unknown', userAgent: navigator.userAgent }; setGuest(guest); debugConversion(guest, 'Guest Session'); }; const checkExistingAuth = () => { const accessToken = localStorage.getItem('accessToken'); const userData = localStorage.getItem('userData'); if (accessToken && userData) { try { const user = JSON.parse(userData); // Convert dates back to Date objects if they're stored as strings if (user.createdAt && typeof user.createdAt === 'string') { user.createdAt = new Date(user.createdAt); } if (user.updatedAt && typeof user.updatedAt === 'string') { user.updatedAt = new Date(user.updatedAt); } if (user.lastLogin && typeof user.lastLogin === 'string') { user.lastLogin = new Date(user.lastLogin); } setApiClient(new ApiClient(accessToken)); setUser(user); } catch (e) { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); localStorage.removeItem('userData'); } } }; // Create guest session on component mount useEffect(() => { createGuestSession(); checkExistingAuth(); }, []); if (guest === null) { return <>; } return ( {children} ); }; export { UserProvider, useUser };