import { CommonModule } from '@angular/common'; import { Component, inject, signal } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { AuthService } from '../../../core/services/auth.service'; import { NotificationService } from '../../../core/services/notification.service'; import { ResetPasswordDto } from '../../../core/models'; /** * Validateur de groupe : vérifie l'égalité entre `nouveauMotDePasse` et * `confirmation`. Pose l'erreur `motsDePasseDifferents` sur le groupe lorsque * les deux valeurs diffèrent. */ function egaliteMotsDePasse(control: AbstractControl): ValidationErrors | null { const nouveau = control.get('nouveauMotDePasse')?.value ?? ''; const confirmation = control.get('confirmation')?.value ?? ''; return nouveau === confirmation ? null : { motsDePasseDifferents: true }; } /** * Écran de réinitialisation du mot de passe (`/reset-password`). * * Lit le paramètre de requête `token` depuis l'URL (via `ActivatedRoute`) et le * conserve dans un signal. Si le token est absent, un message invite à * redemander un lien. Le formulaire saisit le nouveau mot de passe (>= 8 * caractères) et sa confirmation (validateur d'égalité). Sur succès, une * notification est émise puis le composant navigue vers `/login` (Exigence 7.6). * * Contrainte zoneless : tout état async est exposé via des signals * (`soumissionEnCours`, `erreur`, `token`). * * _Requirements: 7.6_ */ @Component({ selector: 'app-reset-password', standalone: true, imports: [CommonModule, ReactiveFormsModule, RouterLink], templateUrl: './reset-password.component.html', styleUrl: './reset-password.component.scss' }) export class ResetPasswordComponent { private readonly fb = inject(FormBuilder); private readonly authService = inject(AuthService); private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly notification = inject(NotificationService); /** Token de réinitialisation lu depuis l'URL (`?token=...`), ou null si absent. */ readonly token = signal( this.route.snapshot.queryParamMap.get('token') ); /** Vrai pendant l'appel de réinitialisation (désactive le bouton). */ readonly soumissionEnCours = signal(false); /** Message d'erreur courant, ou null si aucune erreur. */ readonly erreur = signal(null); readonly form: FormGroup = this.fb.group( { nouveauMotDePasse: ['', [Validators.required, Validators.minLength(8)]], confirmation: ['', [Validators.required]] }, { validators: egaliteMotsDePasse } ); /** Tente la réinitialisation avec le nouveau mot de passe saisi. */ reinitialiser(): void { const token = this.token(); if (this.form.invalid || token === null || this.soumissionEnCours()) { this.form.markAllAsTouched(); return; } const valeur = this.form.getRawValue(); const dto: ResetPasswordDto = { token, nouveauMotDePasse: valeur.nouveauMotDePasse ?? '' }; this.erreur.set(null); this.soumissionEnCours.set(true); this.authService.resetPassword(dto).subscribe({ next: () => { this.soumissionEnCours.set(false); this.notification.success('Votre mot de passe a été réinitialisé.'); this.router.navigate(['/login']); }, error: (err: HttpErrorResponse) => { this.soumissionEnCours.set(false); let message: string; if (err.status === 400) { // ProblemDetails : le backend fournit un message précis // (lien expiré / lien invalide) via `detail` le cas échéant. message = err.error?.detail ?? 'Le lien de réinitialisation est invalide ou a expiré.'; } else { message = 'La réinitialisation a échoué. Veuillez réessayer.'; } this.erreur.set(message); this.notification.error(message); } }); } }