From 35f0bcd135c7c17d1bd208801c3ad0706ea15899 Mon Sep 17 00:00:00 2001 From: diallolatoile Date: Mon, 3 Nov 2025 21:07:19 +0000 Subject: [PATCH] feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature --- src/app/app.routes.ts | 2 +- src/app/app.ts | 25 ++++++--- src/app/core/guards/auth.guard.ts | 37 +++----------- src/app/core/guards/public.guard.ts | 6 --- src/app/core/guards/role.guard.ts | 16 +----- src/app/core/interceptors/auth.interceptor.ts | 2 +- src/app/core/services/auth.service.ts | 24 +++------ src/app/core/services/menu.service.ts | 28 +++++----- src/app/core/services/permissions.service.ts | 12 +++++ .../core/services/role-management.service.ts | 9 ++++ src/app/layouts/components/data.ts | 13 +---- .../components/user-profile/user-profile.html | 17 +------ .../components/user-profile/user-profile.ts | 10 ---- src/app/modules/auth/auth.routes.ts | 14 +++-- src/app/modules/auth/{sign-in.ts => login.ts} | 16 +++--- src/app/modules/auth/logout.ts | 51 +++++++++++++++++++ src/app/modules/auth/new-password.ts | 2 +- src/app/modules/auth/reset-password.ts | 2 +- src/app/modules/auth/two-factor.ts | 2 +- src/app/modules/modules.routes.ts | 6 +-- src/app/modules/profile/profile.html | 38 ++++++++++---- src/app/modules/profile/profile.ts | 11 ++-- .../modules/users/services/users.service.ts | 5 +- src/app/types/layout.ts | 2 +- src/main.ts | 1 - 25 files changed, 183 insertions(+), 168 deletions(-) rename src/app/modules/auth/{sign-in.ts => login.ts} (96%) create mode 100644 src/app/modules/auth/logout.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 9433e32..c3bd036 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -34,7 +34,7 @@ export const routes: Routes = [ // ===== REDIRECTIONS POUR LES ERREURS ===== { path: '404', redirectTo: '/error/404' }, { path: '403', redirectTo: '/error/403' }, - { path: '401', redirectTo: '/auth/sign-in' }, + { path: '401', redirectTo: '/auth/login' }, { path: 'unauthorized', redirectTo: '/error/403' }, // ===== CATCH-ALL ===== diff --git a/src/app/app.ts b/src/app/app.ts index b81456c..c7df4f5 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -25,23 +25,32 @@ export class App implements OnInit { async ngOnInit(): Promise { try { - // Initialiser l'authentification avec gestion d'erreur const isAuthenticated = await this.authService.initialize(); - - console.log('Authentication initialized:', isAuthenticated); - - if (!isAuthenticated) { - console.log('👤 User not authenticated, may redirect to login'); - // Note: Votre AuthService gère déjà la redirection dans logout() + + if (!isAuthenticated && this.router.url === '/') { + this.router.navigate(['/auth/login']); + } else if (isAuthenticated && this.router.url === '/') { + this.router.navigate(['/dcb-dashboard']); } + } catch (error) { console.error('Error during authentication initialization:', error); + this.router.navigate(['/auth/login']); } - // Configurer le titre de la page this.setupTitleListener(); } + private checkPublicRouteRedirection(): void { + const currentUrl = this.router.url; + const publicRoutes = ['/auth/login', '/auth/reset-password', '/auth/forgot-password']; + + // Si l'utilisateur est authentifié et sur une route publique, rediriger vers la page d'accueil + if (publicRoutes.includes(currentUrl)) { + this.router.navigate(['/dcb-dashboard']); + } + } + private setupTitleListener(): void { this.router.events .pipe( diff --git a/src/app/core/guards/auth.guard.ts b/src/app/core/guards/auth.guard.ts index ca2c97c..95f40da 100644 --- a/src/app/core/guards/auth.guard.ts +++ b/src/app/core/guards/auth.guard.ts @@ -10,14 +10,11 @@ export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: R const roleService = inject(RoleService); const router = inject(Router); - console.log('🔐 AuthGuard check for:', state.url); - // Attendre que l'initialisation soit terminée return authService.getInitializedState().pipe( switchMap(initialized => { if (!initialized) { - console.log('⏳ AuthService pas encore initialisé, attente...'); - return of(false); // Bloquer en attendant l'initialisation + return of(false); } // Vérifier l'authentification @@ -28,29 +25,24 @@ export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: R // Tentative de rafraîchissement du token const refreshToken = authService.getRefreshToken(); if (refreshToken) { - console.log('🔄 Token expiré, tentative de rafraîchissement...'); return authService.refreshToken().pipe( tap(() => { - console.log('✅ Token rafraîchi avec succès'); roleService.refreshRoles(); }), map(() => checkRoleAccess(route, roleService, router, state.url)), catchError((error) => { - console.error('❌ Échec du rafraîchissement du token:', error); authService.logout().subscribe(); - return of(redirectToLogin(router, state.url, 'session_expired')); + return of(redirectToLogin(router, state.url)); }) ); } // Redirection vers login - console.log('🔒 Redirection vers login depuis:', state.url); - return of(redirectToLogin(router, state.url, 'not_authenticated')); + return of(redirectToLogin(router, state.url)); }), catchError(error => { - console.error('❌ Erreur dans le guard d\'auth:', error); - return of(redirectToLogin(router, state.url, 'not_authenticated')); + return of(redirectToLogin(router, state.url)); }) ); } @@ -67,7 +59,6 @@ function checkRoleAccess( const requiredRoles = route.data?.['roles'] as string[]; if (!requiredRoles || requiredRoles.length === 0) { - console.log('✅ Accès autorisé (aucun rôle requis):', currentUrl); return true; } @@ -75,17 +66,9 @@ function checkRoleAccess( const currentUserRoles = roleService.getCurrentUserRoles(); if (hasRequiredRole) { - console.log('✅ Accès autorisé avec rôles:', currentUrl); - console.log(' Rôles requis:', requiredRoles); - console.log(' Rôles actuels:', currentUserRoles); return true; } - console.warn('❌ Accès refusé: rôles insuffisants'); - console.warn(' URL demandée:', currentUrl); - console.warn(' Rôles requis:', requiredRoles); - console.warn(' Rôles actuels:', currentUserRoles); - // Rediriger vers la page non autorisée router.navigate(['/unauthorized'], { queryParams: { @@ -104,20 +87,14 @@ function checkRoleAccess( */ function redirectToLogin( router: Router, - returnUrl: string, - reason: 'not_authenticated' | 'session_expired' + returnUrl: string, ): boolean { const queryParams: any = { - returnUrl: returnUrl, - reason: reason + returnUrl: returnUrl }; // Message spécifique selon la raison - if (reason === 'session_expired') { - queryParams.message = 'Votre session a expiré. Veuillez vous reconnecter.'; - } - - router.navigate(['/auth/sign-in'], { + router.navigate(['/auth/login'], { queryParams, replaceUrl: true }); diff --git a/src/app/core/guards/public.guard.ts b/src/app/core/guards/public.guard.ts index 3095a54..edece89 100644 --- a/src/app/core/guards/public.guard.ts +++ b/src/app/core/guards/public.guard.ts @@ -10,7 +10,6 @@ export const publicGuard: CanActivateFn = () => { // Si l'utilisateur est déjà authentifié, le rediriger vers le dashboard if (authService.isAuthenticated()) { - console.log('🔄 Utilisateur déjà authentifié, redirection vers le dashboard'); router.navigate(['/dcb-dashboard'], { replaceUrl: true }); return false; } @@ -18,16 +17,12 @@ export const publicGuard: CanActivateFn = () => { // Vérifier si un refresh token est disponible const refreshToken = authService.getRefreshToken(); if (refreshToken) { - console.log('🔄 Token de rafraîchissement détecté, tentative de reconnexion...'); - return authService.refreshToken().pipe( map(() => { - console.log('✅ Reconnexion automatique réussie'); router.navigate(['/dcb-dashboard'], { replaceUrl: true }); return false; }), catchError((error) => { - console.error('❌ Échec de la reconnexion automatique:', error); // En cas d'erreur, autoriser l'accès à la page publique return of(true); }) @@ -35,6 +30,5 @@ export const publicGuard: CanActivateFn = () => { } // L'utilisateur n'est pas connecté, autoriser l'accès à la page publique - console.log('🌐 Accès public autorisé'); return true; }; \ No newline at end of file diff --git a/src/app/core/guards/role.guard.ts b/src/app/core/guards/role.guard.ts index 0b7c523..bbf0a14 100644 --- a/src/app/core/guards/role.guard.ts +++ b/src/app/core/guards/role.guard.ts @@ -10,7 +10,6 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) = // Vérifier d'abord l'authentification if (!authService.isAuthenticated()) { - console.log('RoleGuard: User not authenticated, redirecting to login'); router.navigate(['/auth/login'], { queryParams: { returnUrl: state.url, @@ -24,26 +23,16 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) = const userRoles = authService.getCurrentUserRoles(); if (!userRoles || userRoles.length === 0) { - console.warn('RoleGuard: User has no roles'); - router.navigate(['/unauthorized'], { - queryParams: { reason: 'no_roles' } - }); + router.navigate(['/unauthorized']); return false; } const modulePath = getModulePath(route); - console.log('🔐 RoleGuard check:', { - module: modulePath, - userRoles: userRoles, - url: state.url - }); - // Vérifier les permissions const hasAccess = permissionsService.canAccessModule(modulePath, userRoles); if (!hasAccess) { - console.warn('❌ RoleGuard: Access denied for', modulePath, 'User roles:', userRoles); router.navigate(['/unauthorized'], { queryParams: { module: modulePath, @@ -54,7 +43,6 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) = return false; } - console.log('✅ RoleGuard: Access granted for', modulePath); return true; }; @@ -92,8 +80,6 @@ function buildPathFromUrl(route: ActivatedRouteSnapshot): string { } function getRequiredRolesForModule(permissionsService: PermissionsService, modulePath: string): string[] { - // Cette fonction récupère les rôles requis pour un module donné - // Vous devrez peut-être l'adapter selon votre implémentation PermissionsService const [mainModule] = modulePath.split('/'); const permission = (permissionsService as any).findPermission?.(mainModule); return permission?.roles || []; diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index 043b233..b5665f3 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -55,7 +55,7 @@ function handle401Error( }), catchError((refreshError) => { authService.logout().subscribe(); - router.navigate(['/auth/sign-in']); + router.navigate(['/auth/login']); return throwError(() => refreshError); }) ); diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index e5b370d..8390064 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -38,7 +38,14 @@ export interface UserProfileDto { firstName: string; lastName: string; roles: string[]; + enabled: boolean; emailVerified: boolean; + merchantPartnerId: string; + createdBy: string; + createdByUsername: string; + createdTimestamp: number; + lastLogin?: number; + userType: string; } export interface TokenValidationResponseDto { @@ -74,36 +81,29 @@ export class AuthService { * Initialise l'authentification au démarrage de l'application */ async initialize(): Promise { - console.log('🔄 Initialisation du service d\'authentification...'); - try { const token = this.getAccessToken(); if (!token) { - console.log('🔍 Aucun token trouvé, utilisateur non authentifié'); this.initialized$.next(true); return false; } if (this.isTokenExpired(token)) { - console.log('⚠️ Token expiré, tentative de rafraîchissement...'); const refreshSuccess = await this.tryRefreshToken(); this.initialized$.next(true); return refreshSuccess; } // Token valide, vérifier le profil utilisateur - console.log('✅ Token valide détecté, vérification du profil...'); await this.loadUserProfile().toPromise(); this.authState$.next(true); this.initialized$.next(true); - console.log('🎯 Authentification initialisée avec succès'); return true; } catch (error) { - console.error('❌ Erreur lors de l\'initialisation de l\'auth:', error); this.clearAuthData(); this.initialized$.next(true); return false; @@ -117,17 +117,14 @@ export class AuthService { const refreshToken = this.getRefreshToken(); if (!refreshToken) { - console.log('🔍 Aucun refresh token disponible'); return false; } try { // Convertir l'Observable en Promise pour l'initialisation const response = await this.refreshToken().toPromise(); - console.log('✅ Token rafraîchi avec succès lors de l\'initialisation'); return true; } catch (error) { - console.error('❌ Échec du rafraîchissement du token:', error); this.clearAuthData(); return false; } @@ -174,10 +171,8 @@ export class AuthService { ).pipe( tap(response => { this.handleLoginSuccess(response); - console.log('🔄 Token rafraîchi avec succès'); }), catchError(error => { - console.error('❌ Échec du rafraîchissement du token:', error); this.clearAuthData(); return throwError(() => error); }) @@ -194,7 +189,6 @@ export class AuthService { ).pipe( tap(() => { this.clearAuthData(); - console.log('👋 Déconnexion réussie'); }), catchError(error => { this.clearAuthData(); // Nettoyer même en cas d'erreur @@ -212,10 +206,8 @@ export class AuthService { ).pipe( tap(profile => { this.userProfile$.next(profile); - console.log('👤 Profil utilisateur chargé:', profile.username); }), catchError(error => { - console.error('❌ Erreur lors du chargement du profil:', error); return throwError(() => error); }) ); @@ -233,7 +225,6 @@ export class AuthService { } this.authState$.next(true); - console.log('✅ Connexion réussie'); } } @@ -245,7 +236,6 @@ export class AuthService { localStorage.removeItem(this.refreshTokenKey); this.authState$.next(false); this.userProfile$.next(null); - console.log('🧹 Données d\'authentification nettoyées'); } /** diff --git a/src/app/core/services/menu.service.ts b/src/app/core/services/menu.service.ts index f8d3a84..c93252e 100644 --- a/src/app/core/services/menu.service.ts +++ b/src/app/core/services/menu.service.ts @@ -124,19 +124,19 @@ export class MenuService { ]; } + // Mettez à jour votre méthode private getFullUserDropdown(): UserDropdownItemType[] { - return [ - { label: 'Welcome back!', isHeader: true }, - { label: 'Profile', icon: 'tablerUserCircle', url: '/profile' }, - { label: 'Account Settings', icon: 'tablerSettings2', url: '/settings' }, - { label: 'Support Center', icon: 'tablerHeadset', url: '/support' }, - { isDivider: true }, - { - label: 'Log Out', - icon: 'tablerLogout2', - url: '/auth/logout', - class: 'fw-semibold text-danger' - }, - ]; -} + return [ + { label: 'Welcome back!', isHeader: true }, + { label: 'Profile', icon: 'tablerUserCircle', url: '/profile' }, + { label: 'Account Settings', icon: 'tablerSettings2', url: '/settings' }, + { label: 'Support Center', icon: 'tablerHeadset', url: '/support' }, + { isDivider: true }, + { + label: 'Déconnexion', + icon: 'tablerLogout2', + class: 'fw-semibold text-danger' + }, + ]; + } } \ No newline at end of file diff --git a/src/app/core/services/permissions.service.ts b/src/app/core/services/permissions.service.ts index 6540041..53708be 100644 --- a/src/app/core/services/permissions.service.ts +++ b/src/app/core/services/permissions.service.ts @@ -22,6 +22,18 @@ export class PermissionsService { ], }, + { + module: 'auth', + roles: [ + 'dcb-admin', + 'dcb-partner', + 'dcb-support', + 'dcb-partner-admin', + 'dcb-partner-manager', + 'dcb-partner-support' + ], + }, + // Transactions { module: 'transactions', diff --git a/src/app/core/services/role-management.service.ts b/src/app/core/services/role-management.service.ts index d743216..42ee966 100644 --- a/src/app/core/services/role-management.service.ts +++ b/src/app/core/services/role-management.service.ts @@ -303,6 +303,9 @@ export class RoleManagementService { return 'bg-info'; case UserRole.DCB_PARTNER: return 'bg-success'; + case UserRole.DCB_PARTNER_ADMIN: return 'bg-danger'; + case UserRole.DCB_PARTNER_MANAGER: return 'bg-success'; + case UserRole.DCB_PARTNER_SUPPORT: return 'bg-info'; default: return 'bg-secondary'; } @@ -319,6 +322,12 @@ export class RoleManagementService { return 'lucideHeadphones'; case UserRole.DCB_PARTNER: return 'lucideBuilding'; + case UserRole.DCB_PARTNER_ADMIN: + return 'lucideShield'; + case UserRole.DCB_PARTNER_MANAGER: + return 'lucideUserCog'; + case UserRole.DCB_PARTNER_SUPPORT: + return 'lucideHeadphones'; default: return 'lucideUser'; } diff --git a/src/app/layouts/components/data.ts b/src/app/layouts/components/data.ts index 8c80321..183e322 100644 --- a/src/app/layouts/components/data.ts +++ b/src/app/layouts/components/data.ts @@ -33,10 +33,9 @@ export const userDropdownItems: UserDropdownItemType[] = [ isDivider: true, }, { - label: 'Log Out', + label: 'Déconnexion', icon: 'tablerLogout2', url: '/auth/logout', - class: 'fw-semibold', }, ] @@ -112,16 +111,6 @@ export const menuItems: MenuItemType[] = [ { label: 'Liste des Utilisateurs', url: '/users' }, ], }, - { - label: 'Authentification', - icon: 'lucideFingerprint', - isCollapsed: true, - children: [ - { label: 'Login / Logout', url: '/auth/login' }, - { label: 'Réinitialisation Mot de Passe', url: '/auth/reset-password' }, - { label: 'Gestion des Sessions', url: '/auth/sessions' }, - ], - }, // --------------------------- // Paramètres & Intégrations diff --git a/src/app/layouts/components/topbar/components/user-profile/user-profile.html b/src/app/layouts/components/topbar/components/user-profile/user-profile.html index 1b7f80d..383530b 100644 --- a/src/app/layouts/components/topbar/components/user-profile/user-profile.html +++ b/src/app/layouts/components/topbar/components/user-profile/user-profile.html @@ -28,21 +28,7 @@ @if (!item.isHeader && !item.isDivider) { - @if (item.label === 'Log Out') { - - - } @else { - - {{ item.label }} - } } } diff --git a/src/app/layouts/components/topbar/components/user-profile/user-profile.ts b/src/app/layouts/components/topbar/components/user-profile/user-profile.ts index 2c7b6d9..30126ce 100644 --- a/src/app/layouts/components/topbar/components/user-profile/user-profile.ts +++ b/src/app/layouts/components/topbar/components/user-profile/user-profile.ts @@ -45,14 +45,4 @@ export class UserProfile implements OnInit, OnDestroy { private loadDropdownItems() { this.menuItems = this.menuService.getUserDropdownItems() } - - logout() { - this.authService.logout() - } - - handleItemClick(item: UserDropdownItemType) { - if (item.label === 'Log Out') { - this.logout() - } - } } \ No newline at end of file diff --git a/src/app/modules/auth/auth.routes.ts b/src/app/modules/auth/auth.routes.ts index d858eda..3b30d7a 100644 --- a/src/app/modules/auth/auth.routes.ts +++ b/src/app/modules/auth/auth.routes.ts @@ -1,13 +1,14 @@ import { Routes } from '@angular/router' -import { SignIn } from '@/app/modules/auth/sign-in' +import { Login } from '@/app/modules/auth/login' +import { Logout } from '@/app/modules/auth/logout' import { ResetPassword } from '@/app/modules/auth/reset-password' import { NewPassword } from '@/app/modules/auth/new-password' import { publicGuard } from '@core/guards/public.guard'; export const AUTH_ROUTES: Routes = [ { - path: 'sign-in', - component: SignIn, + path: 'login', + component: Login, canActivate: [publicGuard], data: { title: 'Connexion - DCB', @@ -35,7 +36,10 @@ export const AUTH_ROUTES: Routes = [ }, { path: 'logout', - redirectTo: 'sign-in', - data: { module: 'auth' } + component: Logout, + data: { + title: 'Déconnexion - DCB', + module: 'auth' + } } ]; \ No newline at end of file diff --git a/src/app/modules/auth/sign-in.ts b/src/app/modules/auth/login.ts similarity index 96% rename from src/app/modules/auth/sign-in.ts rename to src/app/modules/auth/login.ts index 163a3c1..01e942e 100644 --- a/src/app/modules/auth/sign-in.ts +++ b/src/app/modules/auth/login.ts @@ -1,4 +1,4 @@ -// src/app/modules/auth/sign-in/sign-in.ts +// src/app/modules/auth/login/sign-in.ts import { Component, inject, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, NgForm } from '@angular/forms'; @@ -11,7 +11,7 @@ import { PasswordStrengthBar } from '@app/components/password-strength-bar'; import { appName, credits, currentYear } from '@/app/constants'; @Component({ - selector: 'app-sign-in', + selector: 'app-login', standalone: true, imports: [FormsModule, CommonModule, RouterLink, AppLogo, PasswordStrengthBar], template: ` @@ -149,7 +149,7 @@ import { appName, credits, currentYear } from '@/app/constants'; `, styles: [] }) -export class SignIn implements OnInit, OnDestroy { +export class Login implements OnInit, OnDestroy { protected readonly appName = appName; protected readonly currentYear = currentYear; protected readonly credits = credits; @@ -258,16 +258,18 @@ export class SignIn implements OnInit, OnDestroy { const userRoles = this.roleService.getCurrentUserRoles(); // Logique de redirection selon les rôles - if (userRoles.includes('dcb-admin')) { - this.router.navigate(['/admin/dcb-dashboard']); + /*if (userRoles.includes('dcb-admin')) { + this.router.navigate(['/dcb-dashboard']); } else if (userRoles.includes('dcb-partner-admin')) { - this.router.navigate(['/partner/dcb-dashboard']); + this.router.navigate(['/dcb-dashboard']); } else if (userRoles.includes('dcb-support')) { this.router.navigate(['/support/dcb-dashboard']); } else { // Route par défaut this.router.navigate(['/dcb-dashboard']); - } + }*/ + // Route par défaut + this.router.navigate(['/dcb-dashboard']); } /** diff --git a/src/app/modules/auth/logout.ts b/src/app/modules/auth/logout.ts new file mode 100644 index 0000000..24871e1 --- /dev/null +++ b/src/app/modules/auth/logout.ts @@ -0,0 +1,51 @@ +// app/modules/auth/logout/logout.component.ts +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from '@core/services/auth.service'; + +@Component({ + template: ` +
+
+ Déconnexion en cours... +
+

Déconnexion en cours...

+
+ `, + styles: [` + .logout-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + } + `] +}) +export class Logout implements OnInit { + + constructor( + private authService: AuthService, + private router: Router + ) {} + + ngOnInit() { + this.logout(); + } + + private logout(): void { + this.authService.logout().subscribe({ + next: () => { + // Redirection vers la page de login après déconnexion + this.router.navigate(['/auth/login'], { + queryParams: { logout: 'success' } + }); + }, + error: (error) => { + console.error('Erreur lors de la déconnexion:', error); + // Rediriger même en cas d'erreur + this.router.navigate(['/auth/login']); + } + }); + } +} \ No newline at end of file diff --git a/src/app/modules/auth/new-password.ts b/src/app/modules/auth/new-password.ts index b19588c..ae75307 100644 --- a/src/app/modules/auth/new-password.ts +++ b/src/app/modules/auth/new-password.ts @@ -136,7 +136,7 @@ import { NgOtpInputModule } from 'ng-otp-input'

Return to Sign in diff --git a/src/app/modules/auth/reset-password.ts b/src/app/modules/auth/reset-password.ts index 8b7fa37..901bcc3 100644 --- a/src/app/modules/auth/reset-password.ts +++ b/src/app/modules/auth/reset-password.ts @@ -63,7 +63,7 @@ import { AppLogo } from '@app/components/app-logo'

Return to Sign in diff --git a/src/app/modules/auth/two-factor.ts b/src/app/modules/auth/two-factor.ts index 10e9b25..6de8714 100644 --- a/src/app/modules/auth/two-factor.ts +++ b/src/app/modules/auth/two-factor.ts @@ -66,7 +66,7 @@ import { credits, currentYear } from '@/app/constants'

Return to Sign in diff --git a/src/app/modules/modules.routes.ts b/src/app/modules/modules.routes.ts index b439dad..20c3abb 100644 --- a/src/app/modules/modules.routes.ts +++ b/src/app/modules/modules.routes.ts @@ -251,8 +251,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class ModulesRoutes { - constructor() { - console.log('Modules routes loaded:', routes); - } -} \ No newline at end of file +export class ModulesRoutes {} \ No newline at end of file diff --git a/src/app/modules/profile/profile.html b/src/app/modules/profile/profile.html index 09c1cd1..0eafdd1 100644 --- a/src/app/modules/profile/profile.html +++ b/src/app/modules/profile/profile.html @@ -22,7 +22,7 @@ @if (user) { {{ getUserDisplayName() }} } @else { - Profil + Profile } @@ -178,13 +178,29 @@

- - - {{ getRoleLabel(user.role) }} - - - {{ getRoleDescription(user.role) }} - + + @if (user.roles && user.roles.length > 0) { + @for (role of user.roles; track role; let first = $first) { + @if (first) { + + + {{ getRoleLabel(role) }} + + + {{ getRoleDescription(role) }} + + } + } + + + @if (user.roles.length > 1) { + + + {{ user.roles.length - 1 }} autre(s) rôle(s) + + } + } @else { + Aucun rôle + }
@@ -193,7 +209,7 @@