File size: 2,705 Bytes
5ef6e9d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 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;
}
|