'use client' import { createContext, useContext, useState, useCallback, useMemo, type ReactNode, } from 'react' import { createDjangoCSRClient, Auth } from './index' import type { BaseUser, AuthDetails, AuthRoutes } from './types' /** * Auth state provided by AuthContext. */ export interface AuthState { /** Current user (null if not authenticated) */ user: TUser | null /** Whether auth state is loading */ isLoading: boolean /** Refresh user from server */ refresh: () => Promise } const Context = createContext(null) /** * Default routes configuration. */ export const defaultRoutes: AuthRoutes = { login: '/auth/login', authenticated: '/dashboard', } const RoutesContext = createContext(defaultRoutes) export interface AuthContextProps { children: ReactNode /** Initial user from SSR hydration (null if not authenticated) */ user?: TUser | null /** API endpoint to fetch user data (default: '/api/auth/me/') */ userEndpoint?: string /** Route configuration for guards */ routes?: Partial } /** * Base auth context for Django-React apps. * * Provides user state from a simple /me endpoint. * For allauth integration, use AllauthContext instead. */ // Create client once at module level (session auth, no dynamic config needed) const client = createDjangoCSRClient(Auth.SESSION) export function AuthContext({ children, user: initialUser = null, userEndpoint = '/api/auth/me/', routes, }: AuthContextProps) { const [user, setUser] = useState(initialUser) const [isLoading, setIsLoading] = useState(false) const refresh = useCallback(async (): Promise => { setIsLoading(true) try { const resp = await client.request('GET', userEndpoint) if (resp.ok) { const userData = await resp.json() setUser(userData) return userData } else if (resp.status === 401 || resp.status === 403) { setUser(null) return null } throw new Error(`Failed to fetch user: ${resp.status}`) } catch (e) { console.error('[AuthContext] Failed to fetch user:', e) return null } finally { setIsLoading(false) } }, [userEndpoint]) const authState = useMemo>(() => ({ user, isLoading, refresh, }), [user, isLoading, refresh]) const routesValue = useMemo(() => ({ ...defaultRoutes, ...routes, }), [routes]) return ( {children} ) } /** * Hook to access auth state. * Throws if used outside AuthContext. */ export function useAuthState(): AuthState { const ctx = useContext(Context) if (!ctx) throw new Error('useAuthState must be used within AuthContext') return ctx as AuthState } /** * Hook to access current user. * Returns null if not authenticated. */ export function useUser(): TUser | null { return useAuthState().user } /** * Hook to access auth details (isAuthenticated, isStaff, etc.) */ export function useAuth(): AuthDetails { const user = useUser() return { isAuthenticated: user !== null, isStaff: user?.is_staff ?? false, isSuperuser: user?.is_superuser ?? false, } } /** * Hook to access route configuration. */ export function useAuthRoutes(): AuthRoutes { return useContext(RoutesContext) }