ti-pote/apps/frontend/src/context/AuthContext.tsx
2026-04-14 01:40:52 +02:00

105 lines
2.6 KiB
TypeScript

import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react';
import {
api,
hasStoredSession,
type LoginInput,
type Me,
type RegisterInput,
} from '../lib/api';
import { getSocket, disconnectSocket } from '../lib/socket';
type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';
interface AuthContextValue {
status: AuthStatus;
user: Me | null;
login: (input: LoginInput) => Promise<void>;
register: (input: RegisterInput) => Promise<void>;
logout: () => Promise<void>;
refreshMe: () => Promise<void>;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [status, setStatus] = useState<AuthStatus>('loading');
const [user, setUser] = useState<Me | null>(null);
// Bootstrap: if we have a refresh token on disk, try /auth/me
useEffect(() => {
let cancelled = false;
(async () => {
if (!(await hasStoredSession())) {
if (!cancelled) setStatus('unauthenticated');
return;
}
try {
const me = await api.me();
if (cancelled) return;
setUser(me);
setStatus('authenticated');
// Connect WebSocket on session restore
getSocket().catch(() => {});
} catch {
if (cancelled) return;
setUser(null);
setStatus('unauthenticated');
}
})();
return () => {
cancelled = true;
};
}, []);
const login = useCallback(async (input: LoginInput) => {
await api.login(input);
const me = await api.me();
setUser(me);
setStatus('authenticated');
// Connect WebSocket after login
getSocket().catch(() => {});
}, []);
const register = useCallback(async (input: RegisterInput) => {
await api.register(input);
const me = await api.me();
setUser(me);
setStatus('authenticated');
}, []);
const logout = useCallback(async () => {
disconnectSocket();
await api.logout();
setUser(null);
setStatus('unauthenticated');
}, []);
const refreshMe = useCallback(async () => {
const me = await api.me();
setUser(me);
}, []);
const value = useMemo<AuthContextValue>(
() => ({ status, user, login, register, logout, refreshMe }),
[status, user, login, register, logout, refreshMe],
);
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 inside <AuthProvider>');
return ctx;
}