| 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);
|
| tokenStore.clear();
|
| setUser(null);
|
| }, []);
|
|
|
| useEffect(() => {
|
| setUnauthorizedHandler(() => setUser(null));
|
| }, []);
|
|
|
|
|
| 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>;
|
| }
|
|
|
|
|
| export function useAuth() {
|
| const ctx = useContext(AuthContext);
|
| if (!ctx) throw new Error("useAuth must be used within AuthProvider");
|
| return ctx;
|
| }
|
|
|