File size: 2,124 Bytes
b24b684 | 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 | import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
import { setUnauthorizedHandler, tokenStore } from "@/api/client";
import { fetchMe, login as loginRequest, logoutRequest } from "@/api/services";
import type { AppUser } from "@/api/types";
interface AuthContextValue {
user: AppUser | null;
loading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AppUser | null>(null);
const [loading, setLoading] = useState(true);
const logout = useCallback(() => {
const refresh = tokenStore.getRefresh();
if (refresh) void logoutRequest(refresh).catch(() => undefined); // revoke server-side
tokenStore.clear();
setUser(null);
}, []);
useEffect(() => {
setUnauthorizedHandler(() => setUser(null));
}, []);
// Restore session on load.
useEffect(() => {
const token = tokenStore.get();
if (!token) {
setLoading(false);
return;
}
fetchMe()
.then((u) => {
if (u.role === "admin") setUser(u);
else logout();
})
.catch(() => logout())
.finally(() => setLoading(false));
}, [logout]);
const login = useCallback(async (email: string, password: string) => {
const token = await loginRequest(email, password);
tokenStore.set(token.access_token, token.refresh_token);
const me = await fetchMe();
setUser(me);
}, []);
const value = useMemo(
() => ({ user, loading, login, logout }),
[user, loading, login, logout],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// eslint-disable-next-line react-refresh/only-export-components
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}
|