mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 16:18:57 +00:00
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
|
|
// Auth provider — SHOC shape (mirrors providers/auth-provider.tsx).
|
||
|
|
// Session persistence is delegated to lib/auth/authStorage (sessionStorage, WEB-C1);
|
||
|
|
// this provider only owns the in-memory state. Token acquisition (Cognito code
|
||
|
|
// exchange, dev-login) stays in the auth pages — they hand the resolved AuthUser
|
||
|
|
// to login().
|
||
|
|
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||
|
|
import type { AuthUser } from '@proposal-system/api-contracts';
|
||
|
|
import {
|
||
|
|
AUTH_SESSION_CLEARED_EVENT,
|
||
|
|
clearAuth,
|
||
|
|
getAuthUser,
|
||
|
|
isTokenValid,
|
||
|
|
setAuthUser,
|
||
|
|
} from '../lib/auth/authStorage';
|
||
|
|
import { queryClient } from '../lib/queryClient';
|
||
|
|
import { AuthContext, type AuthContextValue } from './authContext';
|
||
|
|
|
||
|
|
export default function AuthProvider({ children }: { children: ReactNode }) {
|
||
|
|
const [user, setUser] = useState<AuthUser | null>(() => getAuthUser());
|
||
|
|
const [isLoading, setIsLoading] = useState(false);
|
||
|
|
const [error, setErrorState] = useState<string | null>(null);
|
||
|
|
|
||
|
|
const login = useCallback((nextUser: AuthUser) => {
|
||
|
|
setAuthUser(nextUser);
|
||
|
|
setUser(nextUser);
|
||
|
|
setErrorState(null);
|
||
|
|
setIsLoading(false);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const logout = useCallback(() => {
|
||
|
|
clearAuth();
|
||
|
|
// Drop all principal-scoped state: cached queries must not survive into the
|
||
|
|
// next login in the same tab, or query keys shared across users would serve
|
||
|
|
// the previous principal's data without hitting the server.
|
||
|
|
queryClient.clear();
|
||
|
|
setUser(null);
|
||
|
|
setErrorState(null);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// The 401 interceptor clears storage outside React; mirror it here so context
|
||
|
|
// state never outlives the stored session while the redirect commits.
|
||
|
|
useEffect(() => {
|
||
|
|
const onSessionCleared = () => setUser(null);
|
||
|
|
window.addEventListener(AUTH_SESSION_CLEARED_EVENT, onSessionCleared);
|
||
|
|
return () => window.removeEventListener(AUTH_SESSION_CLEARED_EVENT, onSessionCleared);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const setError = useCallback((message: string | null) => {
|
||
|
|
setErrorState(message);
|
||
|
|
setIsLoading(false);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const value = useMemo<AuthContextValue>(
|
||
|
|
() => ({
|
||
|
|
user,
|
||
|
|
isAuthenticated: isTokenValid(user?.token),
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
login,
|
||
|
|
logout,
|
||
|
|
setError,
|
||
|
|
setLoading: setIsLoading,
|
||
|
|
}),
|
||
|
|
[user, isLoading, error, login, logout, setError],
|
||
|
|
);
|
||
|
|
|
||
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||
|
|
}
|