2025-08-03 17:48:34 +02:00
|
|
|
// frontend/src/api/hooks/useAuth.ts
|
|
|
|
|
/**
|
|
|
|
|
* Authentication Hooks
|
|
|
|
|
* React hooks for authentication operations
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
|
import { authService } from '../services';
|
|
|
|
|
import type {
|
|
|
|
|
LoginRequest,
|
|
|
|
|
LoginResponse,
|
|
|
|
|
RegisterRequest,
|
|
|
|
|
UserResponse,
|
|
|
|
|
PasswordResetRequest,
|
|
|
|
|
} from '../types';
|
|
|
|
|
|
|
|
|
|
// Token management
|
|
|
|
|
const TOKEN_KEY = 'auth_token';
|
|
|
|
|
const REFRESH_TOKEN_KEY = 'refresh_token';
|
|
|
|
|
const USER_KEY = 'user_data';
|
|
|
|
|
|
|
|
|
|
export const useAuth = () => {
|
|
|
|
|
const [user, setUser] = useState<UserResponse | null>(null);
|
|
|
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
// Initialize auth state from localStorage
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const initializeAuth = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
|
|
|
const userData = localStorage.getItem(USER_KEY);
|
|
|
|
|
|
|
|
|
|
if (token && userData) {
|
|
|
|
|
setUser(JSON.parse(userData));
|
|
|
|
|
setIsAuthenticated(true);
|
|
|
|
|
|
|
|
|
|
// Verify token is still valid
|
|
|
|
|
try {
|
|
|
|
|
const currentUser = await authService.getCurrentUser();
|
|
|
|
|
setUser(currentUser);
|
|
|
|
|
} catch (error) {
|
2025-08-08 23:06:54 +02:00
|
|
|
// Token might be expired - let interceptors handle refresh
|
|
|
|
|
// Only logout if refresh also fails (handled by ErrorRecoveryInterceptor)
|
|
|
|
|
console.log('Token verification failed, interceptors will handle refresh if possible');
|
|
|
|
|
|
|
|
|
|
// Check if we have a refresh token - if not, logout immediately
|
|
|
|
|
const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
|
|
|
if (!refreshToken) {
|
|
|
|
|
console.log('No refresh token available, logging out');
|
|
|
|
|
logout();
|
|
|
|
|
}
|
2025-08-03 17:48:34 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Auth initialization error:', error);
|
|
|
|
|
logout();
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
initializeAuth();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const login = useCallback(async (credentials: LoginRequest): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
const response = await authService.login(credentials);
|
|
|
|
|
|
|
|
|
|
// Store tokens and user data
|
|
|
|
|
localStorage.setItem(TOKEN_KEY, response.access_token);
|
|
|
|
|
if (response.refresh_token) {
|
|
|
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, response.refresh_token);
|
|
|
|
|
}
|
|
|
|
|
if (response.user) {
|
|
|
|
|
localStorage.setItem(USER_KEY, JSON.stringify(response.user));
|
|
|
|
|
setUser(response.user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsAuthenticated(true);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Login failed';
|
|
|
|
|
setError(message);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const register = useCallback(async (data: RegisterRequest): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
const response = await authService.register(data);
|
|
|
|
|
|
|
|
|
|
// Auto-login after successful registration
|
|
|
|
|
if (response.user) {
|
|
|
|
|
await login({ email: data.email, password: data.password });
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Registration failed';
|
|
|
|
|
setError(message);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, [login]);
|
|
|
|
|
|
|
|
|
|
const logout = useCallback(async (): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
// Call logout endpoint if authenticated
|
|
|
|
|
if (isAuthenticated) {
|
|
|
|
|
await authService.logout();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Logout error:', error);
|
|
|
|
|
} finally {
|
|
|
|
|
// Clear local state regardless of API call success
|
|
|
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
|
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
|
|
|
localStorage.removeItem(USER_KEY);
|
|
|
|
|
setUser(null);
|
|
|
|
|
setIsAuthenticated(false);
|
|
|
|
|
setError(null);
|
|
|
|
|
}
|
|
|
|
|
}, [isAuthenticated]);
|
|
|
|
|
|
|
|
|
|
const updateProfile = useCallback(async (data: Partial<UserResponse>): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
const updatedUser = await authService.updateProfile(data);
|
|
|
|
|
setUser(updatedUser);
|
|
|
|
|
localStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Profile update failed';
|
|
|
|
|
setError(message);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const requestPasswordReset = useCallback(async (data: PasswordResetRequest): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
await authService.requestPasswordReset(data);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Password reset request failed';
|
|
|
|
|
setError(message);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const changePassword = useCallback(async (currentPassword: string, newPassword: string): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
await authService.changePassword(currentPassword, newPassword);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Password change failed';
|
|
|
|
|
setError(message);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
user,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
|
|
|
|
login,
|
|
|
|
|
register,
|
|
|
|
|
logout,
|
|
|
|
|
updateProfile,
|
|
|
|
|
requestPasswordReset,
|
|
|
|
|
changePassword,
|
|
|
|
|
clearError: () => setError(null),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Hook for getting authentication headers
|
|
|
|
|
export const useAuthHeaders = () => {
|
|
|
|
|
const getAuthHeaders = useCallback(() => {
|
|
|
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
|
|
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return { getAuthHeaders };
|
|
|
|
|
};
|