import React, { createContext, useCallback, useContext, useState, useEffect, ReactNode } from 'react'; import { AuthContextType, AuthState } from '../types/auth.types'; import { authService } from '../services/auth'; import { getToken, setToken, removeToken, getUser, setUser, removeUser, setRefreshToken, removeRefreshToken } from '../utils/storage'; import { useNotification } from './NotificationContext'; import { setAuthInitialized } from '../services/api'; const AuthContext = createContext(undefined); interface AuthProviderProps { children: ReactNode; } export const AuthProvider: React.FC = ({ children }) => { const notification = useNotification(); const [state, setState] = useState({ user: null, token: null, isAuthenticated: false, isLoading: true, }); // Check for existing token on mount useEffect(() => { const initializeAuth = async () => { const token = getToken(); const user = getUser(); if (token && user) { setState({ user, token, isAuthenticated: true, isLoading: false, }); // Optionally verify token is still valid try { await authService.getCurrentUser(); } catch (error: any) { console.error('Token validation failed:', error); // Only clear auth for explicit 401 Unauthorized // Network errors or server errors (5xx) should not log out the user if (error?.status === 401) { removeToken(); removeUser(); setState({ user: null, token: null, isAuthenticated: false, isLoading: false }); } else { // Keep existing auth state on non-auth errors (network issues, server down) // The user may still be authenticated, just the server is temporarily unavailable setState({ user, token, isAuthenticated: true, isLoading: false }); } } finally { setAuthInitialized(true); } } else { setState({ user: null, token: null, isAuthenticated: false, isLoading: false, }); setAuthInitialized(true); } }; initializeAuth(); }, []); const login = useCallback(async (code: string): Promise => { try { setState((prev) => ({ ...prev, isLoading: true })); const { token, refreshToken, user } = await authService.handleCallback(code); // Save to localStorage setToken(token); setRefreshToken(refreshToken); setUser(user); // Update state setState({ user, token, isAuthenticated: true, isLoading: false, }); // Show success notification notification.showSuccess('Anmeldung erfolgreich'); } catch (error) { console.error('Login failed:', error); setState({ user: null, token: null, isAuthenticated: false, isLoading: false, }); // Show error notification const is429 = error && typeof error === 'object' && 'status' in error && (error as any).status === 429; notification.showError( is429 ? 'Zu viele Anmeldeversuche. Bitte warten Sie einige Minuten und versuchen Sie es erneut.' : 'Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.' ); throw error; } }, [notification]); const logout = useCallback((): void => { // Call backend logout (fire and forget) authService.logout().catch((error) => { console.error('Backend logout failed:', error); }); // Clear local state removeToken(); removeRefreshToken(); removeUser(); setState({ user: null, token: null, isAuthenticated: false, isLoading: false, }); // Show logout notification notification.showSuccess('Abmeldung erfolgreich'); // Redirect to login after a short delay to show notification setTimeout(() => { window.location.href = '/login'; }, 1000); }, [notification]); const refreshAuth = useCallback(async (): Promise => { try { const user = await authService.getCurrentUser(); setUser(user); setState((prev) => ({ ...prev, user })); } catch (error) { console.error('Failed to refresh user data:', error); logout(); } }, [logout]); const value: AuthContextType = { ...state, login, logout, refreshAuth, }; return {children}; }; export const useAuth = (): AuthContextType => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };