This commit is contained in:
Garret Patti
2026-06-29 10:41:29 -04:00
parent d48c1e973e
commit 2b0b19eb91
23 changed files with 969 additions and 44 deletions

View File

@@ -0,0 +1,86 @@
import { useState, useEffect, useCallback, type ReactNode } from "react";
import { AuthContext, type AuthUser } from "./AuthTypes";
const BASE = "/api";
async function fetchMe(token: string) {
try {
const res = await fetch(`${BASE}/auth/me`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (!res.ok) return null;
return res.json();
} catch {
return null;
}
}
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
const [token, setToken] = useState<string | null>(() => localStorage.getItem("token"));
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function validateToken() {
if (!token) return;
const u = await fetchMe(token);
if (cancelled) return;
if (u) {
setUser(u);
} else {
localStorage.removeItem("token");
setToken(null);
}
}
validateToken().finally(() => {
if (!cancelled) setIsLoading(false);
});
return () => {
cancelled = true;
};
}, [token]);
const login = useCallback(async (username: string, password: string) => {
const res = await fetch(`${BASE}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`${res.status}: ${text}`);
}
const data = await res.json();
const newToken = data.access_token;
localStorage.setItem("token", newToken);
setToken(newToken);
}, []);
const logout = useCallback(() => {
localStorage.removeItem("token");
setToken(null);
setUser(null);
}, []);
return (
<AuthContext.Provider
value={{
user,
token,
isAuthenticated: !!user,
isLoading,
login,
logout,
}}
>
{children}
</AuthContext.Provider>
);
}