| import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"; |
|
|
| export interface AuthUser { |
| id: number; |
| email: string; |
| displayName?: string | null; |
| isAdmin: boolean; |
| } |
|
|
| interface AuthContextValue { |
| user: AuthUser | null; |
| isLoaded: boolean; |
| isSignedIn: boolean; |
| isAdmin: boolean; |
| signIn: (email: string, password: string) => Promise<void>; |
| signUp: (email: string, password: string, displayName?: string) => Promise<void>; |
| signOut: () => Promise<void>; |
| refetch: () => Promise<void>; |
| } |
|
|
| const AuthContext = createContext<AuthContextValue | null>(null); |
|
|
| const BASE = import.meta.env.BASE_URL.replace(/\/$/, ""); |
| const API_BASE = `${BASE}/api`; |
|
|
| export function AuthProvider({ children }: { children: ReactNode }) { |
| const [user, setUser] = useState<AuthUser | null>(null); |
| const [isLoaded, setIsLoaded] = useState(false); |
|
|
| const fetchMe = useCallback(async () => { |
| try { |
| const res = await fetch(`${API_BASE}/auth/me`, { credentials: "include" }); |
| if (res.ok) { |
| const data = await res.json(); |
| setUser(data); |
| } else { |
| setUser(null); |
| } |
| } catch { |
| setUser(null); |
| } finally { |
| setIsLoaded(true); |
| } |
| }, []); |
|
|
| useEffect(() => { fetchMe(); }, [fetchMe]); |
|
|
| const signIn = async (email: string, password: string) => { |
| const res = await fetch(`${API_BASE}/auth/login`, { |
| method: "POST", |
| credentials: "include", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ email, password }), |
| }); |
| const data = await res.json(); |
| if (!res.ok) throw new Error(data.error || "Login failed"); |
| setUser(data); |
| }; |
|
|
| const signUp = async (email: string, password: string, displayName?: string) => { |
| const res = await fetch(`${API_BASE}/auth/signup`, { |
| method: "POST", |
| credentials: "include", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ email, password, displayName }), |
| }); |
| const data = await res.json(); |
| if (!res.ok) throw new Error(data.error || "Registration failed"); |
| setUser(data); |
| }; |
|
|
| const signOut = async () => { |
| await fetch(`${API_BASE}/auth/logout`, { method: "POST", credentials: "include" }); |
| setUser(null); |
| }; |
|
|
| return ( |
| <AuthContext.Provider value={{ |
| user, |
| isLoaded, |
| isSignedIn: !!user, |
| isAdmin: !!user?.isAdmin, |
| signIn, |
| signUp, |
| signOut, |
| refetch: fetchMe, |
| }}> |
| {children} |
| </AuthContext.Provider> |
| ); |
| } |
|
|
| export function useAuth() { |
| const ctx = useContext(AuthContext); |
| if (!ctx) throw new Error("useAuth must be used within AuthProvider"); |
| return ctx; |
| } |
|
|