"use client"; import { createContext, useContext, useEffect, useState, ReactNode, } from "react"; import { useRouter, usePathname } from "next/navigation"; import { api } from "./api"; interface User { id: string; username: string; role: string; onboardingComplete?: boolean; } interface AuthContextType { isAuthenticated: boolean; isLoading: boolean; user: User | null; login: ( username: string, password: string, token?: string ) => Promise; logout: () => void; } const AuthContext = createContext(undefined); const publicPaths = ["/login", "/register", "/onboarding", "/sync"]; export function AuthProvider({ children }: { children: ReactNode }) { const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); const router = useRouter(); const pathname = usePathname(); useEffect(() => { // Check if user has valid session on mount ONLY const checkAuth = async () => { // Check for token in URL (from redirect after login) if (typeof window !== "undefined") { const urlParams = new URLSearchParams(window.location.search); const tokenFromUrl = urlParams.get("token"); if (tokenFromUrl) { // Store the token from URL api.setToken(tokenFromUrl); // Clean up URL (remove token param) const cleanUrl = window.location.pathname; window.history.replaceState({}, "", cleanUrl); } } try { const userData = await api.getCurrentUser(); setUser(userData); setIsAuthenticated(true); // Check onboarding status - redirect if needed if ( userData.onboardingComplete === false && pathname !== "/onboarding" ) { router.push("/onboarding"); } else if ( userData.onboardingComplete && pathname === "/onboarding" ) { router.push("/"); } } catch (error) { setIsAuthenticated(false); setUser(null); // If we're already on onboarding page, allow access if (pathname === "/onboarding") { setIsLoading(false); return; } // If not on a public path, check if we need onboarding if (!publicPaths.includes(pathname)) { // Check if any users exist in the system try { const status = await api.get<{ hasAccount: boolean }>( "/onboarding/status" ); if (!status.hasAccount) { // No users exist - redirect to onboarding router.push("/onboarding"); return; } } catch { // If status check fails, assume users exist } // Users exist but not logged in - redirect to login router.push("/login"); } } finally { setIsLoading(false); } }; checkAuth(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Only run once on mount const login = async ( username: string, password: string, token?: string ) => { try { const userData = await api.login(username, password, token); // Check if 2FA is required if (userData.requires2FA) { // Don't set user or redirect, just throw an error to trigger 2FA UI throw new Error("2FA token required"); } setUser(userData); setIsAuthenticated(true); // Redirect based on onboarding status if (userData.onboardingComplete === false) { router.push("/onboarding"); } else { router.push("/"); } } catch (error: any) { console.error("[AUTH] Login failed:", error.message); // Re-throw the error so the login page can handle it throw error; } }; const logout = async () => { await api.logout(); setIsAuthenticated(false); setUser(null); router.push("/login"); }; return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }