Files
dashboard/frontend/src/contexts/AuthContext.tsx
Matthias Hochmeister 215528a521 update
2026-03-16 14:41:08 +01:00

168 lines
4.9 KiB
TypeScript

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<AuthContextType | undefined>(undefined);
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const notification = useNotification();
const [state, setState] = useState<AuthState>({
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<void> => {
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<void> => {
try {
const user = await authService.getCurrentUser();
setUser(user);
setState((prev) => ({ ...prev, user }));
} catch (error: any) {
console.error('Failed to refresh user data:', error);
// Only logout on explicit 401 — network errors / 5xx should not destroy the session
if (error?.response?.status === 401) {
logout();
}
}
}, [logout]);
const value: AuthContextType = {
...state,
login,
logout,
refreshAuth,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};