import React, { createContext, useCallback, useContext, useEffect, useReducer } from 'react'; import { RefreshTokenExpiredError } from '@effigenix/api-client'; import { tokenStorage, getStoredUsername, saveUsername } from '../utils/token-storage.js'; import { client } from '../utils/api-client.js'; export interface AuthUser { username: string; } interface AuthState { isAuthenticated: boolean; user: AuthUser | null; loading: boolean; error: string | null; } type AuthAction = | { type: 'SET_LOADING'; loading: boolean } | { type: 'SET_USER'; user: AuthUser } | { type: 'SET_ERROR'; error: string } | { type: 'CLEAR_ERROR' } | { type: 'LOGOUT' }; function authReducer(state: AuthState, action: AuthAction): AuthState { switch (action.type) { case 'SET_LOADING': return { ...state, loading: action.loading }; case 'SET_USER': return { ...state, isAuthenticated: true, user: action.user, loading: false, error: null }; case 'SET_ERROR': return { ...state, loading: false, error: action.error }; case 'CLEAR_ERROR': return { ...state, error: null }; case 'LOGOUT': return { isAuthenticated: false, user: null, loading: false, error: null }; } } interface AuthContextValue { isAuthenticated: boolean; user: AuthUser | null; loading: boolean; error: string | null; login: (username: string, password: string) => Promise; logout: () => Promise; clearError: () => void; } const AuthContext = createContext(null); interface AuthProviderProps { children: React.ReactNode; onAuthRequired?: () => void; onLogout?: () => void; } export function AuthProvider({ children, onLogout }: AuthProviderProps) { const [state, dispatch] = useReducer(authReducer, { isAuthenticated: false, user: null, loading: true, error: null, }); // Beim Start: prüfen ob bereits gültige Session vorhanden useEffect(() => { void (async () => { try { const token = await tokenStorage.getAccessToken(); const refreshToken = await tokenStorage.getRefreshToken(); if (token && refreshToken) { // Token existiert und ist noch gültig const username = await getStoredUsername() ?? '?'; dispatch({ type: 'SET_USER', user: { username } }); } else if (refreshToken) { // Access Token abgelaufen, aber Refresh Token vorhanden → Refresh versuchen try { const storedUsername = await getStoredUsername(); const refreshed = await client.auth.refresh({ refreshToken }); await tokenStorage.saveTokens( refreshed.accessToken, refreshed.refreshToken, refreshed.expiresAt, ); dispatch({ type: 'SET_USER', user: { username: storedUsername ?? '?' } }); } catch { await tokenStorage.clearTokens(); dispatch({ type: 'LOGOUT' }); } } else { dispatch({ type: 'SET_LOADING', loading: false }); } } catch { dispatch({ type: 'SET_LOADING', loading: false }); } })(); }, []); const login = useCallback(async (username: string, password: string): Promise => { dispatch({ type: 'SET_LOADING', loading: true }); dispatch({ type: 'CLEAR_ERROR' }); try { const response = await client.auth.login({ username, password }); await tokenStorage.saveTokens(response.accessToken, response.refreshToken, response.expiresAt); await saveUsername(username); dispatch({ type: 'SET_USER', user: { username } }); return true; } catch (err) { if (err instanceof RefreshTokenExpiredError) { dispatch({ type: 'SET_ERROR', error: 'Session abgelaufen. Bitte neu anmelden.' }); } else if (err instanceof Error) { const message = err.message.includes('401') ? 'Ungültiger Benutzername oder Passwort.' : err.message; dispatch({ type: 'SET_ERROR', error: message }); } else { dispatch({ type: 'SET_ERROR', error: 'Anmeldung fehlgeschlagen.' }); } return false; } }, []); const logout = useCallback(async (): Promise => { try { await client.auth.logout(); } catch { // Logout-Request kann fehlschlagen wenn Token abgelaufen – trotzdem lokale Session löschen } await tokenStorage.clearTokens(); dispatch({ type: 'LOGOUT' }); onLogout?.(); }, [onLogout]); const clearError = useCallback(() => dispatch({ type: 'CLEAR_ERROR' }), []); const value: AuthContextValue = { ...state, login, logout, clearError, }; return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; }