diff --git a/src/app/routes/authentification/authentification-routing.module.ts b/src/app/routes/authentification/authentification-routing.module.ts
index 45b9c03..607e3f2 100644
--- a/src/app/routes/authentification/authentification-routing.module.ts
+++ b/src/app/routes/authentification/authentification-routing.module.ts
@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { RegisterComponent } from '@app/shared/features/register/register.component';
import { AuthComponent } from '@app/routes/authentification/auth/auth.component';
import { LoginComponent } from '@app/shared/features/login/login.component';
+import { ForgotPasswordComponent } from '@app/shared/features/forgot-password/forgot-password.component';
const routes: Routes = [
{
@@ -11,6 +12,7 @@ const routes: Routes = [
children: [
{ path: '', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
+ { path: 'forgot-password', component: ForgotPasswordComponent },
],
},
{
diff --git a/src/app/shared/features/forgot-password/forgot-password.component.html b/src/app/shared/features/forgot-password/forgot-password.component.html
new file mode 100644
index 0000000..ce81e26
--- /dev/null
+++ b/src/app/shared/features/forgot-password/forgot-password.component.html
@@ -0,0 +1,58 @@
+
diff --git a/src/app/shared/features/forgot-password/forgot-password.component.scss b/src/app/shared/features/forgot-password/forgot-password.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/features/forgot-password/forgot-password.component.spec.ts b/src/app/shared/features/forgot-password/forgot-password.component.spec.ts
new file mode 100644
index 0000000..879f103
--- /dev/null
+++ b/src/app/shared/features/forgot-password/forgot-password.component.spec.ts
@@ -0,0 +1,119 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ForgotPasswordComponent } from './forgot-password.component';
+import { ToastrService } from 'ngx-toastr';
+import { signal, WritableSignal } from '@angular/core';
+import { ActionType } from '@app/domain/action-type.util';
+import { AuthFacade } from '@app/ui/authentification/auth.facade';
+import { ActivatedRoute, provideRouter, Router } from '@angular/router';
+import { Subject } from 'rxjs';
+
+describe('ForgotPasswordComponent', () => {
+ let component: ForgotPasswordComponent;
+ let fixture: ComponentFixture;
+
+ let mockToastrService: Partial;
+ let mockRouter: any;
+ let mockActivatedRoute: any;
+ let mockAuthFacade: {
+ sendRequestPasswordReset: jest.Mock;
+ isRequestPasswordSent: jest.Mock; // <--- AJOUT
+ loading: WritableSignal<{ isLoading: boolean; action: ActionType }>;
+ error: WritableSignal<{ hasError: boolean }>;
+ };
+
+ beforeEach(async () => {
+ mockToastrService = {
+ success: jest.fn(),
+ error: jest.fn(),
+ warning: jest.fn(),
+ info: jest.fn(),
+ };
+
+ // 1. CORRECTION DU MOCK ROUTER
+ mockRouter = {
+ navigate: jest.fn().mockResolvedValue(true),
+ createUrlTree: jest.fn(),
+ serializeUrl: jest.fn(),
+ routerState: { root: '' },
+ events: new Subject(), // <--- AJOUT CRUCIAL : RouterLink s'abonne à ceci
+ };
+
+ mockActivatedRoute = {
+ snapshot: { paramMap: { get: () => null } },
+ };
+
+ mockAuthFacade = {
+ sendRequestPasswordReset: jest.fn(),
+ isRequestPasswordSent: jest.fn().mockReturnValue(true),
+ loading: signal({ isLoading: false, action: ActionType.NONE }),
+ error: signal({ hasError: false }),
+ };
+
+ await TestBed.configureTestingModule({
+ imports: [ForgotPasswordComponent],
+ providers: [
+ // provideRouter([]) est inutile si on écrase Router juste en dessous,
+ // mais on peut le laisser par sécurité si d'autres deps en ont besoin.
+ provideRouter([]),
+ { provide: ToastrService, useValue: mockToastrService },
+ { provide: AuthFacade, useValue: mockAuthFacade },
+ { provide: Router, useValue: mockRouter }, // On remplace le vrai routeur par notre mock
+ { provide: ActivatedRoute, useValue: mockActivatedRoute },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ForgotPasswordComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should NOT call sendRequestPasswordReset if form is invalid', () => {
+ component.fpForm.setValue({ email: 'bad-email' });
+ component.onSubmit();
+ expect(component.fpForm.invalid).toBe(true);
+ expect(mockAuthFacade.sendRequestPasswordReset).not.toHaveBeenCalled();
+ });
+
+ it('should call sendRequestPasswordReset if form is valid', () => {
+ const validEmail = 'test@example.com';
+ component.fpForm.setValue({ email: validEmail });
+ component.onSubmit();
+ expect(mockAuthFacade.sendRequestPasswordReset).toHaveBeenCalledWith(validEmail);
+ });
+
+ it('should show success toast and navigate to /auth when action finishes successfully', () => {
+ component.fpForm.setValue({ email: 'success@test.com' });
+ // On s'assure que la condition isRequestPasswordSent() renvoie bien TRUE
+ mockAuthFacade.isRequestPasswordSent.mockReturnValue(true);
+
+ mockAuthFacade.error.set({ hasError: false });
+ mockAuthFacade.loading.set({ isLoading: false, action: ActionType.CREATE });
+
+ fixture.detectChanges();
+
+ expect(mockToastrService.success).toHaveBeenCalledWith(
+ expect.stringContaining('success@test.com'),
+ expect.anything(),
+ expect.anything()
+ );
+ expect(mockRouter.navigate).toHaveBeenCalledWith(['/auth']);
+ });
+
+ it('should show error toast when action finishes with error', () => {
+ component.fpForm.setValue({ email: 'error@test.com' });
+
+ mockAuthFacade.error.set({ hasError: true });
+ mockAuthFacade.loading.set({ isLoading: false, action: ActionType.CREATE });
+
+ fixture.detectChanges();
+
+ expect(mockToastrService.error).toHaveBeenCalled();
+ expect(mockRouter.navigate).not.toHaveBeenCalled();
+ expect(mockToastrService.success).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/app/shared/features/forgot-password/forgot-password.component.ts b/src/app/shared/features/forgot-password/forgot-password.component.ts
new file mode 100644
index 0000000..0079804
--- /dev/null
+++ b/src/app/shared/features/forgot-password/forgot-password.component.ts
@@ -0,0 +1,80 @@
+import { Component, effect, inject } from '@angular/core';
+import { BtnLoadingComponent } from '@app/shared/components/btn-loading/btn-loading.component';
+import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
+import { Router, RouterLink } from '@angular/router';
+import { AuthFacade } from '@app/ui/authentification/auth.facade';
+import { ToastrService } from 'ngx-toastr';
+import { ActionType } from '@app/domain/action-type.util';
+
+@Component({
+ selector: 'app-forgot-password',
+ standalone: true,
+ imports: [BtnLoadingComponent, FormsModule, ReactiveFormsModule, RouterLink],
+ templateUrl: './forgot-password.component.html',
+ styleUrl: './forgot-password.component.scss',
+})
+export class ForgotPasswordComponent {
+ private readonly fb = inject(FormBuilder);
+ private readonly facade = inject(AuthFacade);
+ private readonly toastrService = inject(ToastrService);
+ protected readonly router = inject(Router);
+
+ fpForm = this.fb.group({
+ email: ['', [Validators.required, Validators.email]],
+ });
+
+ protected readonly loading = this.facade.loading;
+ protected readonly error = this.facade.error;
+
+ constructor() {
+ let message = '';
+
+ effect(() => {
+ if (!this.loading().isLoading) {
+ switch (this.loading().action) {
+ case ActionType.CREATE:
+ message = `Un mail de réinitialisation vous a été envoyé à cette adresse mail : ${this.fpForm.getRawValue().email!}`;
+ this.customToast(ActionType.CREATE, message);
+ if (!this.error().hasError && this.facade.isRequestPasswordSent()) {
+ this.router.navigate(['/auth']);
+ }
+ break;
+ }
+ }
+ });
+ }
+
+ onSubmit() {
+ if (this.fpForm.invalid) {
+ this.fpForm.setErrors({ invalidForm: true });
+ return;
+ }
+ this.facade.sendRequestPasswordReset(this.fpForm.getRawValue().email!);
+ }
+
+ private customToast(action: ActionType, message: string): void {
+ if (this.error().hasError) {
+ this.toastrService.error(
+ `Une erreur s'est produite, veuillez réessayer ulterieurement`,
+ `Erreur`,
+ {
+ closeButton: true,
+ progressAnimation: 'decreasing',
+ progressBar: true,
+ }
+ );
+ return;
+ }
+
+ this.toastrService.success(
+ `${message}`,
+ `${action === ActionType.CREATE ? 'Mise à jour' : ''}`,
+ {
+ closeButton: true,
+ progressAnimation: 'decreasing',
+ progressBar: true,
+ disableTimeOut: true,
+ }
+ );
+ }
+}
diff --git a/src/app/shared/features/login/login.component.html b/src/app/shared/features/login/login.component.html
index 1988cd7..ada382f 100644
--- a/src/app/shared/features/login/login.component.html
+++ b/src/app/shared/features/login/login.component.html
@@ -106,7 +106,7 @@