import { createContext, useContext, useEffect, useState } from "react"; import { jwtDecode } from "jwt-decode"; import { API } from "~/api/api"; export const getApiErrorMessage = (error: any): string => { if (error && error.errors && typeof error.errors === 'object') { // Handles ASP.NET Core validation problem details // e.g., {"errors":{"Email":["The Email field is required."]}} const messages = Object.values(error.errors).flat(); if (messages.length > 0) { return (messages as string[]).join(' '); } } if (error && error.detail) { return error.detail; } if (error && error.title) { return error.title; } if (error && error.message) { return error.message; } if (typeof error === 'string') { return error; } return 'An unexpected error occurred.'; }; export interface User { id: string; email: string; accessToken: string; refreshToken: string; isAdmin: boolean; } interface AuthContextType { user: User | null; isLoading: boolean; login: (accessToken: string, refreshToken: string) => void; logout: () => void; renewSession: () => Promise; } const AuthContext = createContext({ user: null, isLoading: true, login: () => {}, logout: () => {}, renewSession: async () => null, }); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { try { const stored = localStorage.getItem("user"); if (stored) { const storedUser: User = JSON.parse(stored); try { const decoded: { exp: number } = jwtDecode(storedUser.accessToken); if (decoded.exp * 1000 < Date.now()) { // Token is expired, clear user data localStorage.removeItem("user"); localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); setUser(null); } else { setUser(storedUser); } } catch (error) { // If token is invalid, clear user data localStorage.removeItem("user"); localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); setUser(null); } } } finally { setIsLoading(false); } }, []); const login = (accessToken: string, refreshToken: string) => { const decoded: any = jwtDecode(accessToken); const roleField = decoded["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; const isAdmin = Array.isArray(roleField) ? roleField.includes("Admin") : roleField === "Admin"; const newUser: User = { id: decoded.sub, email: decoded.email, accessToken, refreshToken, isAdmin, }; setUser(newUser); localStorage.setItem("user", JSON.stringify(newUser)); localStorage.setItem("accessToken", accessToken); localStorage.setItem("refreshToken", refreshToken); }; const logout = () => { setUser(null); localStorage.removeItem("user"); localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); }; const renewSession = async (): Promise => { const stored = localStorage.getItem("user"); if (!stored) { logout(); return null; } const currentUser: User = JSON.parse(stored); try { const res = await API.renewToken(currentUser.refreshToken); if (!res.ok) { logout(); return null; } const { accessToken, refreshToken: newRefreshToken } = await res.json(); login(accessToken, newRefreshToken); // We need to get the user object that login creates const newStored = localStorage.getItem("user"); return newStored ? JSON.parse(newStored) : null; } catch (error) { logout(); return null; } }; return ( {children} ); } export const useAuth = () => useContext(AuthContext);