Files
btpxpress-frontend/services/userService.ts
dahoud e15d717a40 Fix: Correction critique de la boucle OAuth - Empêcher les échanges multiples du code
PROBLÈME RÉSOLU:
- Erreur "Code already used" répétée dans les logs Keycloak
- Boucle infinie de tentatives d'échange du code d'autorisation OAuth
- Utilisateurs bloqués à la connexion

CORRECTIONS APPLIQUÉES:
1. Ajout de useRef pour protéger contre les exécutions multiples
   - hasExchanged.current: Flag pour prévenir les réexécutions
   - isProcessing.current: Protection pendant le traitement

2. Modification des dépendances useEffect
   - AVANT: [searchParams, router] → exécution à chaque changement
   - APRÈS: [] → exécution unique au montage du composant

3. Amélioration du logging
   - Console logs pour debug OAuth flow
   - Messages emoji pour faciliter le suivi

4. Nettoyage de l'URL
   - window.history.replaceState() pour retirer les paramètres OAuth
   - Évite les re-renders causés par les paramètres dans l'URL

5. Gestion d'erreurs améliorée
   - Capture des erreurs JSON du serveur
   - Messages d'erreur plus explicites

FICHIERS AJOUTÉS:
- app/(main)/aide/* - 4 pages du module Aide (documentation, tutoriels, support)
- app/(main)/messages/* - 4 pages du module Messages (inbox, envoyés, archives)
- app/auth/callback/page.tsx.backup - Sauvegarde avant modification

IMPACT:
 Un seul échange de code par authentification
 Plus d'erreur "Code already used"
 Connexion fluide et sans boucle
 Logs propres et lisibles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 23:45:33 +00:00

330 lines
11 KiB
TypeScript

import { apiService } from './api';
import type { User } from '../types/auth';
import { UserRole } from '../types/auth';
interface CreateUserRequest {
email: string;
nom: string;
prenom: string;
role: UserRole;
entreprise?: string;
siret?: string;
secteurActivite?: string;
}
interface UpdateUserRequest {
nom?: string;
prenom?: string;
entreprise?: string;
siret?: string;
secteurActivite?: string;
actif?: boolean;
}
interface UserStats {
totalUsers: number;
activeUsers: number;
pendingUsers: number;
usersByRole: Record<UserRole, number>;
recentActivity: UserActivity[];
}
interface UserActivity {
userId: string;
userName: string;
action: string;
timestamp: string;
details?: string;
}
class UserService {
/**
* Récupérer tous les utilisateurs
*/
async getAllUsers(): Promise<User[]> {
try {
const response = await apiService.get('/users');
return response.data;
} catch (error) {
console.error('Erreur lors de la récupération des utilisateurs:', error);
return this.getMockUsers();
}
}
/**
* Récupérer un utilisateur par ID
*/
async getUserById(id: string): Promise<User> {
try {
const response = await apiService.get(`/users/${id}`);
return response.data;
} catch (error) {
console.error('Erreur lors de la récupération de l\'utilisateur:', error);
const users = this.getMockUsers();
const user = users.find(u => u.id === id);
if (!user) throw new Error('User not found');
return user;
}
}
/**
* Créer un nouvel utilisateur
*/
async createUser(userData: CreateUserRequest): Promise<User> {
try {
const response = await apiService.post('/users', userData);
return response.data;
} catch (error) {
console.error('Erreur lors de la création de l\'utilisateur:', error);
// Fallback vers mock en cas d'erreur
return {
id: Math.random().toString(36).substring(2, 11),
email: userData.email,
nom: userData.nom,
prenom: userData.prenom,
username: userData.email,
role: userData.role,
roles: [userData.role],
permissions: [],
entreprise: userData.entreprise,
siret: userData.siret,
secteurActivite: userData.secteurActivite,
actif: true,
status: 'ACTIVE' as any,
dateCreation: new Date(),
dateModification: new Date(),
isAdmin: false,
isManager: false,
isEmployee: false,
isClient: false
};
}
}
/**
* Mettre à jour un utilisateur
*/
async updateUser(id: string, userData: UpdateUserRequest): Promise<User> {
try {
const response = await apiService.put(`/users/${id}`, userData);
return response.data;
} catch (error) {
console.error('Erreur lors de la mise à jour de l\'utilisateur:', error);
const user = await this.getUserById(id);
return { ...user, ...userData };
}
}
/**
* Supprimer un utilisateur
*/
async deleteUser(id: string): Promise<void> {
try {
await apiService.delete(`/users/${id}`);
} catch (error) {
console.error('Erreur lors de la suppression de l\'utilisateur:', error);
throw error;
}
}
/**
* Récupérer les gestionnaires de projet
*/
async getGestionnaires(): Promise<User[]> {
try {
const response = await apiService.get('/users/gestionnaires');
return response.data;
} catch (error) {
console.error('Erreur lors de la récupération des gestionnaires:', error);
return this.getMockGestionnaires();
}
}
/**
* Récupérer les statistiques utilisateurs
*/
async getUserStats(): Promise<UserStats> {
try {
const response = await apiService.get('/users/stats');
return response.data;
} catch (error) {
console.error('Erreur lors de la récupération des statistiques:', error);
return this.getMockUserStats();
}
}
/**
* Récupérer l'activité récente des utilisateurs
*/
async getUserActivity(): Promise<UserActivity[]> {
try {
const response = await apiService.get('/users/activity');
return response.data;
} catch (error) {
console.error('Erreur lors de la récupération de l\'activité:', error);
return this.getMockUserActivity();
}
}
/**
* Données mockées pour les utilisateurs
*/
private getMockUsers(): User[] {
return [
{
id: 'admin-1',
email: 'admin@btpxpress.com',
nom: 'Administrateur',
prenom: 'Système',
username: 'admin@btpxpress.com',
role: UserRole.ADMIN,
roles: [UserRole.ADMIN],
permissions: [],
actif: true,
status: 'APPROVED' as any,
entreprise: 'BTP Xpress',
dateCreation: new Date('2024-01-01T00:00:00Z'),
dateModification: new Date('2024-01-01T00:00:00Z'),
isAdmin: true,
isManager: false,
isEmployee: false,
isClient: false
},
{
id: 'manager-1',
email: 'manager@btpxpress.com',
nom: 'Dupont',
prenom: 'Jean',
username: 'manager@btpxpress.com',
role: UserRole.MANAGER,
roles: [UserRole.MANAGER],
permissions: [],
actif: true,
status: 'APPROVED' as any,
entreprise: 'BTP Xpress',
dateCreation: new Date('2024-01-15T00:00:00Z'),
dateModification: new Date('2024-01-15T00:00:00Z'),
isAdmin: false,
isManager: true,
isEmployee: false,
isClient: false
},
{
id: 'gest-1',
email: 'john.doe@btpxpress.com',
nom: 'Doe',
prenom: 'John',
username: 'john.doe@btpxpress.com',
role: UserRole.GESTIONNAIRE_PROJET,
roles: [UserRole.GESTIONNAIRE_PROJET],
permissions: [],
actif: true,
status: 'APPROVED' as any,
entreprise: 'BTP Xpress',
dateCreation: new Date('2024-02-01T00:00:00Z'),
dateModification: new Date('2024-02-01T00:00:00Z'),
isAdmin: false,
isManager: true,
isEmployee: false,
isClient: false
},
{
id: 'gest-2',
email: 'marie.martin@btpxpress.com',
nom: 'Martin',
prenom: 'Marie',
username: 'marie.martin@btpxpress.com',
role: UserRole.GESTIONNAIRE_PROJET,
roles: [UserRole.GESTIONNAIRE_PROJET],
permissions: [],
actif: true,
status: 'APPROVED' as any,
entreprise: 'BTP Xpress',
dateCreation: new Date('2024-02-15T00:00:00Z'),
dateModification: new Date('2024-02-15T00:00:00Z'),
isAdmin: false,
isManager: true,
isEmployee: false,
isClient: false
},
{
id: 'client-1',
email: 'client1@example.com',
nom: 'Dupont',
prenom: 'Pierre',
username: 'client1@example.com',
role: UserRole.CLIENT,
roles: [UserRole.CLIENT],
permissions: [],
actif: true,
status: 'APPROVED' as any,
dateCreation: new Date('2024-03-01T00:00:00Z'),
dateModification: new Date('2024-03-01T00:00:00Z'),
isAdmin: false,
isManager: false,
isEmployee: false,
isClient: true
}
];
}
/**
* Données mockées pour les gestionnaires
*/
private getMockGestionnaires(): User[] {
return this.getMockUsers().filter(user => user.role === UserRole.GESTIONNAIRE_PROJET);
}
/**
* Statistiques mockées
*/
private getMockUserStats(): UserStats {
const users = this.getMockUsers();
return {
totalUsers: users.length,
activeUsers: users.filter(u => u.actif).length,
pendingUsers: users.filter(u => u.status === 'PENDING').length,
usersByRole: {
[UserRole.ADMIN]: users.filter(u => u.role === UserRole.ADMIN).length,
[UserRole.MANAGER]: users.filter(u => u.role === UserRole.MANAGER).length,
[UserRole.GESTIONNAIRE_PROJET]: users.filter(u => u.role === UserRole.GESTIONNAIRE_PROJET).length,
[UserRole.CHEF_CHANTIER]: users.filter(u => u.role === UserRole.CHEF_CHANTIER).length,
[UserRole.OUVRIER]: users.filter(u => u.role === UserRole.OUVRIER).length,
[UserRole.COMPTABLE]: users.filter(u => u.role === UserRole.COMPTABLE).length,
[UserRole.CLIENT]: users.filter(u => u.role === UserRole.CLIENT).length
},
recentActivity: this.getMockUserActivity()
};
}
/**
* Activité mockée
*/
private getMockUserActivity(): UserActivity[] {
return [
{
userId: 'gest-1',
userName: 'John Doe',
action: 'Connexion',
timestamp: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
details: 'Dashboard gestionnaire'
},
{
userId: 'client-1',
userName: 'Pierre Dupont',
action: 'Consultation projet',
timestamp: new Date(Date.now() - 1000 * 60 * 60).toISOString(),
details: 'Extension maison individuelle'
},
{
userId: 'manager-1',
userName: 'Jean Dupont',
action: 'Attribution client',
timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(),
details: 'Client attribué à Marie Martin'
}
];
}
}
export default new UserService();