import { useRouter } from 'next/router'; import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { api, ApiError } from './api'; export interface AuthUser { id: string; email: string; name: string; } export interface Membership { role: 'ORG_ADMIN' | 'MEMBER' | 'VIEWER'; org: { id: string; name: string; slug: string }; } interface MeResponse { user: AuthUser; memberships: Membership[]; } interface AuthContextValue { user: AuthUser | null; memberships: Membership[]; activeOrg: Membership | null; loading: boolean; setActiveOrgId: (orgId: string) => void; login: (email: string, password: string) => Promise; register: (data: { email: string; password: string; name: string; orgName: string }) => Promise; logout: () => Promise; } const AuthContext = createContext(null); const ACTIVE_ORG_KEY = 'ulhub_active_org'; export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [memberships, setMemberships] = useState([]); const [activeOrgId, setActiveOrgIdState] = useState(null); const [loading, setLoading] = useState(true); const applySession = useCallback((session: MeResponse) => { setUser(session.user); setMemberships(session.memberships); setActiveOrgIdState((current) => { const stored = current ?? localStorage.getItem(ACTIVE_ORG_KEY); const valid = session.memberships.some((m) => m.org.id === stored); return valid ? stored : (session.memberships[0]?.org.id ?? null); }); }, []); useEffect(() => { api .get('/api/auth/me') .then(applySession) .catch(() => setUser(null)) .finally(() => setLoading(false)); }, [applySession]); const setActiveOrgId = useCallback((orgId: string) => { localStorage.setItem(ACTIVE_ORG_KEY, orgId); setActiveOrgIdState(orgId); }, []); const login = useCallback( async (email: string, password: string) => { applySession(await api.post('/api/auth/login', { email, password })); }, [applySession], ); const register = useCallback( async (data: { email: string; password: string; name: string; orgName: string }) => { applySession(await api.post('/api/auth/register', data)); }, [applySession], ); const logout = useCallback(async () => { await api.post('/api/auth/logout', {}).catch(() => undefined); localStorage.removeItem(ACTIVE_ORG_KEY); setUser(null); setMemberships([]); setActiveOrgIdState(null); }, []); const value = useMemo( () => ({ user, memberships, activeOrg: memberships.find((m) => m.org.id === activeOrgId) ?? null, loading, setActiveOrgId, login, register, logout, }), [user, memberships, activeOrgId, loading, setActiveOrgId, login, register, logout], ); return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error('useAuth must be used inside AuthProvider'); } return ctx; } // Client-side page guard: redirects to /login when unauthenticated. export function useRequireAuth() { const auth = useAuth(); const router = useRouter(); useEffect(() => { if (!auth.loading && !auth.user) { router.replace('/login'); } }, [auth.loading, auth.user, router]); return auth; } export { ApiError };