71 lines
2.1 KiB
TypeScript
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;
|
|
}
|