Files
dashboard-cpsp/context/AuthContext.tsx
T
2026-08-26 13:46:05 +07:00

71 lines
2.1 KiB
TypeScript

import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { api, ApiError, AUTH_UNAUTHORIZED_EVENT } from '../services/apiClient.ts';
import type { User } from '../types/api.ts';
type AuthContextValue = {
user: User | null;
loading: boolean;
login: (userName: string, password: string) => Promise<void>;
logout: () => Promise<void>;
};
const AuthContext = createContext<AuthContextValue | null>(null);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const onUnauthorized = () => setUser(null);
window.addEventListener(AUTH_UNAUTHORIZED_EVENT, onUnauthorized);
return () => window.removeEventListener(AUTH_UNAUTHORIZED_EVENT, onUnauthorized);
}, []);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const me = await api.auth.me();
if (!cancelled) setUser(me);
} catch (err) {
if (
!cancelled &&
!(err instanceof ApiError && (err.status === 401 || err.status === 403))
) {
console.error(err);
}
if (!cancelled) setUser(null);
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, []);
const login = useCallback(async (userName: string, password: string) => {
await api.auth.csrf();
const next = await api.auth.login(userName, password);
setUser(next);
}, []);
const logout = useCallback(async () => {
try {
await api.auth.logout();
} finally {
setUser(null);
}
}, []);
const value = useMemo(() => ({ user, loading, login, logout }), [user, loading, login, logout]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}