refacto : refactoring des classes facades

This commit is contained in:
styve Lioumba
2025-12-21 09:14:39 +01:00
parent a4fb20dd68
commit dfda229a03
52 changed files with 543 additions and 531 deletions

View File

@@ -18,7 +18,8 @@
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"pocketbase" "pocketbase",
"leaflet"
], ],
"outputPath": "dist/", "outputPath": "dist/",
"index": "src/index.html", "index": "src/index.html",

View File

@@ -1,6 +1,6 @@
{ {
"name": "trouve-ton-profile", "name": "trouve-ton-profile",
"version": "1.3.0", "version": "1.3.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "bash replace-prod-env.sh src/environments/environment.development.ts $ENV_URL && ng serve", "start": "bash replace-prod-env.sh src/environments/environment.development.ts $ENV_URL && ng serve",

View File

@@ -8,6 +8,8 @@ import { ProfileRepository } from '@app/domain/profiles/profile.repository';
import { SettingRepository } from '@app/domain/settings/setting.repository'; import { SettingRepository } from '@app/domain/settings/setting.repository';
import { mockSettingRepo } from '@app/testing/setting.mock'; import { mockSettingRepo } from '@app/testing/setting.mock';
import { SETTING_REPOSITORY_TOKEN } from '@app/infrastructure/settings/setting-repository.token'; import { SETTING_REPOSITORY_TOKEN } from '@app/infrastructure/settings/setting-repository.token';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('AppComponent', () => { describe('AppComponent', () => {
let component: AppComponent; let component: AppComponent;
@@ -41,6 +43,7 @@ describe('AppComponent', () => {
imports: [AppComponent], imports: [AppComponent],
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: ToastrService, useValue: mockToastR },
{ provide: AUTH_REPOSITORY_TOKEN, useValue: mockAuthRepository }, { provide: AUTH_REPOSITORY_TOKEN, useValue: mockAuthRepository },
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo },
{ provide: SETTING_REPOSITORY_TOKEN, useValue: mockSettingRepository }, { provide: SETTING_REPOSITORY_TOKEN, useValue: mockSettingRepository },

View File

@@ -348,7 +348,7 @@
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"
/> />
</svg> </svg>
Localisation <span class="hidden sm:inline">Localisation</span>
</button> </button>
<button <button

View File

@@ -95,9 +95,7 @@
<img <img
alt="{{ user().name }}" alt="{{ user().name }}"
class="object-cover w-full h-full" class="object-cover w-full h-full"
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{ src="{{ profile().avatarUrl }}"
user().avatar
}}"
loading="lazy" loading="lazy"
/> />
} @else { } @else {

View File

@@ -8,6 +8,8 @@ import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repo
import { SectorRepository } from '@app/domain/sectors/sector.repository'; import { SectorRepository } from '@app/domain/sectors/sector.repository';
import { mockProfileRepo } from '@app/testing/profile.mock'; import { mockProfileRepo } from '@app/testing/profile.mock';
import { mockSectorRepo } from '@app/testing/sector.mock'; import { mockSectorRepo } from '@app/testing/sector.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('ProfileListComponent', () => { describe('ProfileListComponent', () => {
let component: ProfileListComponent; let component: ProfileListComponent;
@@ -22,6 +24,7 @@ describe('ProfileListComponent', () => {
provideRouter([]), provideRouter([]),
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository }, { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository },
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();

View File

@@ -8,7 +8,6 @@ import { Router } from '@angular/router';
import { SearchFilters } from '@app/domain/search/search-filters'; import { SearchFilters } from '@app/domain/search/search-filters';
import { FilterComponent } from '@app/shared/features/filter/filter.component'; import { FilterComponent } from '@app/shared/features/filter/filter.component';
import { PaginationComponent } from '@app/shared/features/pagination/pagination.component'; import { PaginationComponent } from '@app/shared/features/pagination/pagination.component';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ProfileListMapComponent } from '@app/shared/features/profile-list-map/profile-list-map.component'; import { ProfileListMapComponent } from '@app/shared/features/profile-list-map/profile-list-map.component';
type ViewMode = 'list' | 'map'; type ViewMode = 'list' | 'map';
@@ -52,18 +51,11 @@ export class ProfileListComponent {
this.facade.load(filters); this.facade.load(filters);
} }
// Nouvelles méthodes
switchToListView(): void { switchToListView(): void {
this.viewMode.set('list'); this.viewMode.set('list');
} }
switchToMapView(): void { switchToMapView(): void {
this.viewMode.set('map'); this.viewMode.set('map');
// Recharger les profils avec localisation si nécessaire
}
onProfileClickFromMap(profile: ProfileViewModel): void {
// Navigation vers le profil
this.router.navigate(['/profiles', profile.id]);
} }
} }

View File

@@ -5,6 +5,8 @@ import { provideRouter } from '@angular/router';
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token'; import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
import { SectorRepository } from '@app/domain/sectors/sector.repository'; import { SectorRepository } from '@app/domain/sectors/sector.repository';
import { mockSectorRepo } from '@app/testing/sector.mock'; import { mockSectorRepo } from '@app/testing/sector.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('ChipsComponent', () => { describe('ChipsComponent', () => {
let component: ChipsComponent; let component: ChipsComponent;
@@ -18,6 +20,7 @@ describe('ChipsComponent', () => {
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();

View File

@@ -1,4 +1,4 @@
import { Component, inject, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Component, inject, input, OnChanges, SimpleChanges } from '@angular/core';
import { TitleCasePipe } from '@angular/common'; import { TitleCasePipe } from '@angular/common';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { SectorFacade } from '@app/ui/sectors/sector.facade'; import { SectorFacade } from '@app/ui/sectors/sector.facade';
@@ -13,7 +13,7 @@ import { SectorFacade } from '@app/ui/sectors/sector.facade';
}) })
@UntilDestroy() @UntilDestroy()
export class ChipsComponent implements OnChanges { export class ChipsComponent implements OnChanges {
@Input({ required: true }) sectorId: string | null = null; sectorId = input.required<string>();
private readonly sectorFacade = inject(SectorFacade); private readonly sectorFacade = inject(SectorFacade);
protected sector = this.sectorFacade.sector; protected sector = this.sectorFacade.sector;
@@ -21,8 +21,8 @@ export class ChipsComponent implements OnChanges {
protected readonly error = this.sectorFacade.error; protected readonly error = this.sectorFacade.error;
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (this.sectorId && !this.loading().isLoading) { if (this.sectorId() && !this.loading().isLoading) {
this.sectorFacade.loadOne(this.sectorId); this.sectorFacade.loadOne(this.sectorId());
} }
} }
} }

View File

@@ -1,12 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyProfileProjectItemComponent } from './my-profile-project-item.component'; import { MyProfileProjectItemComponent } from './my-profile-project-item.component';
import { of } from 'rxjs';
import { Project } from '@app/domain/projects/project.model';
import { ProjectRepository } from '@app/domain/projects/project.repository'; import { ProjectRepository } from '@app/domain/projects/project.repository';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token'; import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
import { mockProjectRepo } from '@app/testing/project.mock'; import { mockProjectRepo } from '@app/testing/project.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('MyProfileProjectItemComponent', () => { describe('MyProfileProjectItemComponent', () => {
let component: MyProfileProjectItemComponent; let component: MyProfileProjectItemComponent;
@@ -20,11 +20,15 @@ describe('MyProfileProjectItemComponent', () => {
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository }, { provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(MyProfileProjectItemComponent); fixture = TestBed.createComponent(MyProfileProjectItemComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.componentRef.setInput('projectId', 'fakeId');
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, input, OnInit } from '@angular/core';
import { environment } from '@env/environment'; import { environment } from '@env/environment';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
@@ -12,12 +12,12 @@ import { ProjectFacade } from '@app/ui/projects/project.facade';
}) })
export class MyProfileProjectItemComponent implements OnInit { export class MyProfileProjectItemComponent implements OnInit {
protected readonly environment = environment; protected readonly environment = environment;
@Input({ required: true }) projectId = ''; projectId = input.required<string>();
private readonly projectFacade = new ProjectFacade(); private readonly projectFacade = new ProjectFacade();
protected project = this.projectFacade.project; protected project = this.projectFacade.project;
ngOnInit(): void { ngOnInit(): void {
this.projectFacade.loadOne(this.projectId); this.projectFacade.loadOne(this.projectId());
} }
} }

View File

@@ -5,6 +5,8 @@ import { ProjectRepository } from '@app/domain/projects/project.repository';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token'; import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
import { mockProfileRepo } from '@app/testing/profile.mock'; import { mockProfileRepo } from '@app/testing/profile.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('MyProfileProjectListComponent', () => { describe('MyProfileProjectListComponent', () => {
let component: MyProfileProjectListComponent; let component: MyProfileProjectListComponent;
@@ -17,6 +19,7 @@ describe('MyProfileProjectListComponent', () => {
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository }, { provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit, signal } from '@angular/core'; import { Component, input, OnInit, signal } from '@angular/core';
import { PaginatorModule } from 'primeng/paginator'; import { PaginatorModule } from 'primeng/paginator';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
@@ -14,8 +14,8 @@ import { ProjectFacade } from '@app/ui/projects/project.facade';
}) })
@UntilDestroy() @UntilDestroy()
export class MyProfileProjectListComponent implements OnInit { export class MyProfileProjectListComponent implements OnInit {
@Input({ required: true }) projectIds: string[] = []; projectIds = input.required<string[]>();
@Input({ required: true }) userId = ''; userId = input<string>('');
protected projectIdSelected = signal<string | null>(null); protected projectIdSelected = signal<string | null>(null);
@@ -23,7 +23,7 @@ export class MyProfileProjectListComponent implements OnInit {
protected projects = this.projectFacade.projects; protected projects = this.projectFacade.projects;
ngOnInit(): void { ngOnInit(): void {
this.projectFacade.load(this.userId); this.projectFacade.load(this.userId());
} }
onProjectFormSubmitted($event: string | null) { onProjectFormSubmitted($event: string | null) {

View File

@@ -27,6 +27,7 @@ describe('MyProfileUpdateCvFormComponent', () => {
fixture = TestBed.createComponent(MyProfileUpdateCvFormComponent); fixture = TestBed.createComponent(MyProfileUpdateCvFormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();

View File

@@ -1,6 +1,5 @@
import { Component, effect, inject, Input } from '@angular/core'; import { Component, effect, input } from '@angular/core';
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { ToastrService } from 'ngx-toastr';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { ProfileFacade } from '@app/ui/profiles/profile.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -14,9 +13,7 @@ import { Profile } from '@app/domain/profiles/profile.model';
styleUrl: './my-profile-update-cv-form.component.scss', styleUrl: './my-profile-update-cv-form.component.scss',
}) })
export class MyProfileUpdateCvFormComponent { export class MyProfileUpdateCvFormComponent {
@Input({ required: true }) profile: ProfileViewModel | undefined = undefined; profile = input.required<ProfileViewModel>();
private readonly toastrService = inject(ToastrService);
file: File | null = null; // Variable to store file file: File | null = null; // Variable to store file
@@ -29,13 +26,6 @@ export class MyProfileUpdateCvFormComponent {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
if (!this.loading() && !this.error().hasError) { if (!this.loading() && !this.error().hasError) {
//this.authService.updateUser();
this.toastrService.success(` Votre CV a bien été modifier !`, `Mise à jour`, {
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
});
} }
} }
}); });
@@ -45,7 +35,7 @@ export class MyProfileUpdateCvFormComponent {
if (this.file != null) { if (this.file != null) {
const formData = new FormData(); const formData = new FormData();
formData.append('cv', this.file); // "avatar" est le nom du champ dans PocketBase formData.append('cv', this.file); // "avatar" est le nom du champ dans PocketBase
this.profileFacade.update(this.profile?.id!, formData as Partial<Profile>); this.profileFacade.update(this.profile()?.id!, formData as Partial<Profile>);
} }
} }

View File

@@ -8,7 +8,6 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component'; import { MyProfileUpdateCvFormComponent } from '@app/shared/components/my-profile-update-cv-form/my-profile-update-cv-form.component';
import { ToastrService } from 'ngx-toastr';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { ProfileFacade } from '@app/ui/profiles/profile.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -32,7 +31,6 @@ import { LoadingComponent } from '@app/shared/components/loading/loading.compone
}) })
@UntilDestroy() @UntilDestroy()
export class MyProfileUpdateFormComponent implements OnInit { export class MyProfileUpdateFormComponent implements OnInit {
private readonly toastrService = inject(ToastrService);
protected readonly ActionType = ActionType; protected readonly ActionType = ActionType;
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel; @Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel;
@@ -50,14 +48,10 @@ export class MyProfileUpdateFormComponent implements OnInit {
protected readonly sectorError = this.sectorFacade.error; protected readonly sectorError = this.sectorFacade.error;
constructor() { constructor() {
let message = '';
effect(() => { effect(() => {
if (!this.loading().isLoading) { if (!this.loading().isLoading) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
message = `Vos informations personnelles ont bien été modifier !`;
this.customToast(ActionType.UPDATE, message);
break; break;
} }
} }
@@ -127,29 +121,4 @@ export class MyProfileUpdateFormComponent implements OnInit {
this.profileFacade.update(this.profile.id, data); this.profileFacade.update(this.profile.id, data);
} }
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.UPDATE ? 'Mise à jour' : ''}`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
}
} }

View File

@@ -1,4 +1,4 @@
@if (projectId) { @if (projectId()) {
<div class="space-y-6"> <div class="space-y-6">
<!-- Section Image du projet --> <!-- Section Image du projet -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in"> <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in">
@@ -22,7 +22,7 @@
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Image du projet</h3> <h3 class="text-xl font-bold text-gray-900 dark:text-white">Image du projet</h3>
</div> </div>
@if (projectId === 'add'.toLowerCase()) { @if (projectId() === 'add'.toLowerCase()) {
<app-project-picture-form [project]="undefined" /> <app-project-picture-form [project]="undefined" />
} @else { } @else {
<app-project-picture-form [project]="project()" /> <app-project-picture-form [project]="project()" />

View File

@@ -5,8 +5,6 @@ import { ToastrService } from 'ngx-toastr';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token'; import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
import { ProjectRepository } from '@app/domain/projects/project.repository'; import { ProjectRepository } from '@app/domain/projects/project.repository';
import { of } from 'rxjs';
import { Project } from '@app/domain/projects/project.model';
import { AUTH_REPOSITORY_TOKEN } from '@app/infrastructure/authentification/auth-repository.token'; import { AUTH_REPOSITORY_TOKEN } from '@app/infrastructure/authentification/auth-repository.token';
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
import { AuthRepository } from '@app/domain/authentification/auth.repository'; import { AuthRepository } from '@app/domain/authentification/auth.repository';
@@ -40,6 +38,8 @@ describe('MyProfileUpdateProjectFormComponent', () => {
fixture = TestBed.createComponent(MyProfileUpdateProjectFormComponent); fixture = TestBed.createComponent(MyProfileUpdateProjectFormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.componentRef.setInput('projectId', 'fakeId');
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,17 +1,7 @@
import { import { Component, effect, inject, input, output } from '@angular/core';
Component,
effect,
inject,
Input,
OnChanges,
OnInit,
output,
SimpleChanges,
} from '@angular/core';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { PaginatorModule } from 'primeng/paginator'; import { PaginatorModule } from 'primeng/paginator';
import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component'; import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component';
import { ToastrService } from 'ngx-toastr';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto'; import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -25,10 +15,8 @@ import { AuthFacade } from '@app/ui/authentification/auth.facade';
templateUrl: './my-profile-update-project-form.component.html', templateUrl: './my-profile-update-project-form.component.html',
styleUrl: './my-profile-update-project-form.component.scss', styleUrl: './my-profile-update-project-form.component.scss',
}) })
export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges { export class MyProfileUpdateProjectFormComponent {
@Input({ required: true }) projectId: string | null = null; projectId = input.required<string | null>();
private readonly toastrService = inject(ToastrService);
private readonly projectFacade = new ProjectFacade(); private readonly projectFacade = new ProjectFacade();
protected readonly ActionType = ActionType; protected readonly ActionType = ActionType;
@@ -50,17 +38,12 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
formIsUpdated = output<string | null>(); formIsUpdated = output<string | null>();
constructor() { constructor() {
let message = '';
effect(() => { effect(() => {
if (!this.loading().isLoading) { if (!this.loading().isLoading) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.CREATE: case ActionType.CREATE:
message = `Le projet ${this.projectForm.getRawValue().nom} a bien été créer !`;
this.customToast(ActionType.CREATE, message);
break; break;
case ActionType.UPDATE: case ActionType.UPDATE:
message = `Les informations du projet ${this.projectForm.getRawValue().nom} ont bien été modifier !`;
this.customToast(ActionType.UPDATE, message);
break; break;
} }
@@ -73,20 +56,26 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
} }
} }
}); });
}
ngOnInit(): void { effect(
if (this.projectId == 'add'.toLowerCase()) { () => {
this.projectForm.setValue({ const id = this.projectId();
nom: '', if (id) {
description: '', if (id.toLowerCase() === 'add') {
lien: '', // Mode Création : On vide le formulaire
}); this.projectForm.reset({
} nom: '',
description: '',
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) { lien: '',
this.projectFacade.loadOne(this.projectId); });
} } else {
// Mode Édition : On charge les données
this.projectFacade.loadOne(id);
}
}
},
{ allowSignalWrites: true }
);
} }
onSubmit() { onSubmit() {
@@ -94,7 +83,7 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
return; return;
} }
if (this.projectId != null && this.projectId != 'add'.toLowerCase()) { if (this.projectId() != null && this.projectId() != 'add'.toLowerCase()) {
// Update // Update
this.projectFacade.update(this.project()!.id, this.projectForm.getRawValue()); this.projectFacade.update(this.project()!.id, this.projectForm.getRawValue());
} else { } else {
@@ -107,35 +96,4 @@ export class MyProfileUpdateProjectFormComponent implements OnInit, OnChanges {
this.projectFacade.create(projectDto); this.projectFacade.create(projectDto);
} }
} }
ngOnChanges(changes: SimpleChanges): void {
this.projectId = changes['projectId'].currentValue;
this.ngOnInit();
}
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.formIsUpdated.emit(this.project()!.id);
this.toastrService.success(
`${message}`,
`${action === ActionType.UPDATE ? 'Mise à jour' : 'Nouveau projet'}`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
}
} }

View File

@@ -11,6 +11,8 @@ import { ProfileRepository } from '@app/domain/profiles/profile.repository';
import { mockProfileRepo } from '@app/testing/profile.mock'; import { mockProfileRepo } from '@app/testing/profile.mock';
import { mockAuthRepo } from '@app/testing/auth.mock'; import { mockAuthRepo } from '@app/testing/auth.mock';
import { mockThemeService } from '@app/testing/theme.mock'; import { mockThemeService } from '@app/testing/theme.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('NavBarComponent', () => { describe('NavBarComponent', () => {
let component: NavBarComponent; let component: NavBarComponent;
@@ -38,6 +40,7 @@ describe('NavBarComponent', () => {
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: ThemeService, useValue: mockTheme }, { provide: ThemeService, useValue: mockTheme },
{ provide: ToastrService, useValue: mockToastR },
{ provide: AUTH_REPOSITORY_TOKEN, useValue: mockAuthRepository }, { provide: AUTH_REPOSITORY_TOKEN, useValue: mockAuthRepository },
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository }, { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository },
], ],

View File

@@ -5,6 +5,8 @@ import { ProjectRepository } from '@app/domain/projects/project.repository';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token'; import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
import { mockProjectRepo } from '@app/testing/project.mock'; import { mockProjectRepo } from '@app/testing/project.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('ProjectListComponent', () => { describe('ProjectListComponent', () => {
let component: ProjectListComponent; let component: ProjectListComponent;
@@ -18,6 +20,7 @@ describe('ProjectListComponent', () => {
providers: [ providers: [
provideRouter([]), provideRouter([]),
{ provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository }, { provide: PROJECT_REPOSITORY_TOKEN, useValue: mockProjectRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Component, input, OnChanges, SimpleChanges } from '@angular/core';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { ProjectItemComponent } from '@app/shared/components/project-item/project-item.component'; import { ProjectItemComponent } from '@app/shared/components/project-item/project-item.component';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
@@ -12,13 +12,13 @@ import { ProjectFacade } from '@app/ui/projects/project.facade';
}) })
@UntilDestroy() @UntilDestroy()
export class ProjectListComponent implements OnChanges { export class ProjectListComponent implements OnChanges {
@Input({ required: true }) userProjectId = ''; userProjectId = input.required<string>({});
private readonly projectFacade = new ProjectFacade(); private readonly projectFacade = new ProjectFacade();
protected projects = this.projectFacade.projects; protected projects = this.projectFacade.projects;
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
this.projectFacade.load(this.userProjectId); this.projectFacade.load(this.userProjectId());
} }
} }

View File

@@ -1,7 +1,6 @@
import { Component, effect, inject, Input, output, signal } from '@angular/core'; import { Component, effect, Input, output, signal } from '@angular/core';
import { NgClass, NgTemplateOutlet } from '@angular/common'; import { NgClass, NgTemplateOutlet } from '@angular/common';
import { environment } from '@env/environment'; import { environment } from '@env/environment';
import { ToastrService } from 'ngx-toastr';
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model'; import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -20,7 +19,6 @@ export class ProjectPictureFormComponent {
protected readonly ActionType = ActionType; protected readonly ActionType = ActionType;
onFormSubmitted = output<any>(); onFormSubmitted = output<any>();
private readonly toastrService = inject(ToastrService);
private readonly projectFacade = new ProjectFacade(); private readonly projectFacade = new ProjectFacade();
protected readonly loading = this.projectFacade.loading; protected readonly loading = this.projectFacade.loading;
@@ -36,8 +34,6 @@ export class ProjectPictureFormComponent {
if (!this.loading().isLoading && this.onSubmitted()) { if (!this.loading().isLoading && this.onSubmitted()) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
message = `L'aperçu du projet ${this.project!.nom} ont bien été modifier !`;
this.customToast(ActionType.UPDATE, message);
this.onSubmitted.set(false); this.onSubmitted.set(false);
this.onFormSubmitted.emit(''); this.onFormSubmitted.emit('');
break; break;
@@ -71,29 +67,4 @@ export class ProjectPictureFormComponent {
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
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.UPDATE ? 'Mise à jour' : ''}`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
}
} }

View File

@@ -40,7 +40,6 @@ export class UserAvatarFormComponent {
if (!this.loading().isLoading) { if (!this.loading().isLoading) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
message = `Votre photo de profile a bien été modifier !`;
if (this.onSubmitted()) { if (this.onSubmitted()) {
this.customToast(ActionType.UPDATE, message); this.customToast(ActionType.UPDATE, message);
} }

View File

@@ -1,11 +1,15 @@
@if (user() !== undefined) { @if (user() !== undefined) {
<a [routerLink]="[slug()]" [state]="{ user: user(), profile }" class="block group"> <a
[routerLink]="[profile()!.slug!]"
[state]="{ user: user(), profile: profile() }"
class="block group"
>
<!-- Card du profil --> <!-- Card du profil -->
<div <div
class="relative bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-2xl hover:-translate-y-2" class="relative bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-2xl hover:-translate-y-2"
> >
<!-- Badge vérifié (position absolue en haut à droite) --> <!-- Badge vérifié (position absolue en haut à droite) -->
@if (profile.estVerifier) { @if (profile().estVerifier) {
<div class="absolute top-3 right-3 z-10 animate-pulse-slow"> <div class="absolute top-3 right-3 z-10 animate-pulse-slow">
<div class="bg-purple-500/20 backdrop-blur-md p-2 rounded-full"> <div class="bg-purple-500/20 backdrop-blur-md p-2 rounded-full">
<svg <svg
@@ -29,10 +33,10 @@
<!-- Avatar avec bordure gradient --> <!-- Avatar avec bordure gradient -->
<div class="relative inline-block mb-4"> <div class="relative inline-block mb-4">
<div class="w-32 h-32 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-1"> <div class="w-32 h-32 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-1">
@if (user().avatar) { @if (profile().avatarUrl) {
<img <img
class="w-full h-full rounded-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500 group-hover:scale-105" class="w-full h-full rounded-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500 group-hover:scale-105"
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{ user().avatar }}" src="{{ profile().avatarUrl }}"
alt="{{ user().username }}" alt="{{ user().username }}"
loading="lazy" loading="lazy"
/> />
@@ -61,12 +65,12 @@
{{ user().username }} {{ user().username }}
</h3> </h3>
} @else { } @else {
<h3 class="text-lg font-bold text-gray-500 dark:text-gray-400 mb-2">Non mention</h3> <h3 class="text-lg font-bold text-gray-500 dark:text-gray-400 mb-2">Non renseig</h3>
} }
<!-- Profession --> <!-- Profession -->
<p class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2"> <p class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">
{{ profile.profession || 'Profession non renseignée' }} {{ profile().profession || 'Profession non renseignée' }}
</p> </p>
</div> </div>

View File

@@ -4,13 +4,13 @@ import { VerticalProfileItemComponent } from './vertical-profile-item.component'
import { UserRepository } from '@app/domain/users/user.repository'; import { UserRepository } from '@app/domain/users/user.repository';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token';
import { User } from '@app/domain/users/user.model';
import { of } from 'rxjs';
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token'; import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
import { SectorRepository } from '@app/domain/sectors/sector.repository'; import { SectorRepository } from '@app/domain/sectors/sector.repository';
import { Sector } from '@app/domain/sectors/sector.model';
import { mockUserRepo } from '@app/testing/user.mock'; import { mockUserRepo } from '@app/testing/user.mock';
import { mockSectorRepo } from '@app/testing/sector.mock'; import { mockSectorRepo } from '@app/testing/sector.mock';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('VerticalProfileItemComponent', () => { describe('VerticalProfileItemComponent', () => {
let component: VerticalProfileItemComponent; let component: VerticalProfileItemComponent;
@@ -19,6 +19,11 @@ describe('VerticalProfileItemComponent', () => {
let mockUserRepository: jest.Mocked<Partial<UserRepository>> = mockUserRepo; let mockUserRepository: jest.Mocked<Partial<UserRepository>> = mockUserRepo;
let mockSectorRepository: jest.Mocked<Partial<SectorRepository>> = mockSectorRepo; let mockSectorRepository: jest.Mocked<Partial<SectorRepository>> = mockSectorRepo;
const mockProfile: Partial<ProfileViewModel> = {
id: 'fakeId',
utilisateur: 'fakeUtilisateur',
};
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [VerticalProfileItemComponent], imports: [VerticalProfileItemComponent],
@@ -26,11 +31,15 @@ describe('VerticalProfileItemComponent', () => {
provideRouter([]), provideRouter([]),
{ provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepository }, { provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepository },
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(VerticalProfileItemComponent); fixture = TestBed.createComponent(VerticalProfileItemComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.componentRef.setInput('profile', mockProfile);
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,4 +1,4 @@
import { Component, computed, inject, Input, OnInit } from '@angular/core'; import { Component, computed, inject, input, OnInit } from '@angular/core';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { environment } from '@env/environment'; import { environment } from '@env/environment';
@@ -15,9 +15,10 @@ import { UserFacade } from '@app/ui/users/user.facade';
}) })
@UntilDestroy() @UntilDestroy()
export class VerticalProfileItemComponent implements OnInit { export class VerticalProfileItemComponent implements OnInit {
@Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel; profile = input.required<ProfileViewModel>();
protected router = inject(Router); protected readonly router = inject(Router);
private readonly facade = inject(UserFacade); private readonly facade = inject(UserFacade);
protected readonly environment = environment;
protected user = this.facade.user; protected user = this.facade.user;
protected readonly loading = this.facade.loading; protected readonly loading = this.facade.loading;
@@ -25,13 +26,13 @@ export class VerticalProfileItemComponent implements OnInit {
protected slug = computed(() => { protected slug = computed(() => {
const slug = this.user().slug ?? ''; const slug = this.user().slug ?? '';
const profileId = this.profile.id ? this.profile.id : ''; const profileId = this.profile().id ? this.profile().id : '';
return slug === '' ? profileId : slug.concat('-', profileId); return slug === '' ? profileId : slug.concat('-', profileId);
}); });
ngOnInit(): void { ngOnInit(): void {
this.facade.loadOne(this.profile.utilisateur); if (this.profile()) {
this.facade.loadOne(this.profile().utilisateur);
}
} }
protected readonly environment = environment;
} }

View File

@@ -5,38 +5,40 @@
@for (profile of profiles; track profile.id) { @for (profile of profiles; track profile.id) {
<app-vertical-profile-item [profile]="profile" /> <app-vertical-profile-item [profile]="profile" />
} @empty { } @empty {
<!-- Message si aucun profil --> <ng-container *ngTemplateOutlet="emptyMessage" />
<div class="col-span-full">
<div
class="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-700 rounded-xl p-12 text-center"
>
<div
class="inline-flex w-20 h-20 bg-white dark:bg-gray-600 rounded-full items-center justify-center mb-6 shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-gray-400 dark:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Aucun profil trouvé
</h3>
<p class="text-gray-600 dark:text-gray-300 max-w-md mx-auto">
Aucun profil ne correspond à votre recherche. Essayez de modifier vos critères.
</p>
</div>
</div>
} }
</div> </div>
</div> </div>
</section> </section>
<ng-template #emptyMessage>
<!-- Message si aucun profil -->
<div class="col-span-full">
<div
class="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-700 rounded-xl p-12 text-center"
>
<div
class="inline-flex w-20 h-20 bg-white dark:bg-gray-600 rounded-full items-center justify-center mb-6 shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-gray-400 dark:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">Aucun profil trouvé</h3>
<p class="text-gray-600 dark:text-gray-300 max-w-md mx-auto">
Aucun profil ne correspond à votre recherche. Essayez de modifier vos critères.
</p>
</div>
</div>
</ng-template>

View File

@@ -1,11 +1,12 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { VerticalProfileItemComponent } from '@app/shared/components/vertical-profile-item/vertical-profile-item.component'; import { VerticalProfileItemComponent } from '@app/shared/components/vertical-profile-item/vertical-profile-item.component';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { NgTemplateOutlet } from '@angular/common';
@Component({ @Component({
selector: 'app-vertical-profile-list', selector: 'app-vertical-profile-list',
standalone: true, standalone: true,
imports: [VerticalProfileItemComponent], imports: [VerticalProfileItemComponent, NgTemplateOutlet],
templateUrl: './vertical-profile-list.component.html', templateUrl: './vertical-profile-list.component.html',
styleUrl: './vertical-profile-list.component.scss', styleUrl: './vertical-profile-list.component.scss',
}) })

View File

@@ -5,12 +5,11 @@ import { SectorRepository } from '@app/domain/sectors/sector.repository';
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token'; import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
import { of } from 'rxjs';
import { Profile } from '@app/domain/profiles/profile.model';
import { ProfileRepository } from '@app/domain/profiles/profile.repository'; import { ProfileRepository } from '@app/domain/profiles/profile.repository';
import { Sector } from '@app/domain/sectors/sector.model';
import { mockProfileRepo } from '@app/testing/profile.mock'; import { mockProfileRepo } from '@app/testing/profile.mock';
import { mockSectorRepo } from '@app/testing/sector.mock'; import { mockSectorRepo } from '@app/testing/sector.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('FilterComponent', () => { describe('FilterComponent', () => {
let component: FilterComponent; let component: FilterComponent;
@@ -25,6 +24,7 @@ describe('FilterComponent', () => {
provideRouter([]), provideRouter([]),
{ provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository }, { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepository },
{ provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}).compileComponents(); }).compileComponents();

View File

@@ -8,6 +8,7 @@ import { AuthFacade } from '@app/ui/authentification/auth.facade';
import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { ActivatedRoute, provideRouter, Router } from '@angular/router';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
// TODO : Model mock de la Facade
describe('ForgotPasswordComponent', () => { describe('ForgotPasswordComponent', () => {
let component: ForgotPasswordComponent; let component: ForgotPasswordComponent;
let fixture: ComponentFixture<ForgotPasswordComponent>; let fixture: ComponentFixture<ForgotPasswordComponent>;
@@ -96,11 +97,11 @@ describe('ForgotPasswordComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(mockToastrService.success).toHaveBeenCalledWith( /*expect(mockToastrService.success).toHaveBeenCalledWith(
expect.stringContaining('success@test.com'), expect.stringContaining('success@test.com'),
expect.anything(), expect.anything(),
expect.anything() expect.anything()
); );*/
expect(mockRouter.navigate).toHaveBeenCalledWith(['/auth']); expect(mockRouter.navigate).toHaveBeenCalledWith(['/auth']);
}); });
@@ -112,7 +113,7 @@ describe('ForgotPasswordComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(mockToastrService.error).toHaveBeenCalled(); //expect(mockToastrService.error).toHaveBeenCalled();
expect(mockRouter.navigate).not.toHaveBeenCalled(); expect(mockRouter.navigate).not.toHaveBeenCalled();
expect(mockToastrService.success).not.toHaveBeenCalled(); expect(mockToastrService.success).not.toHaveBeenCalled();
}); });

View File

@@ -3,7 +3,6 @@ import { BtnLoadingComponent } from '@app/shared/components/btn-loading/btn-load
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { AuthFacade } from '@app/ui/authentification/auth.facade'; import { AuthFacade } from '@app/ui/authentification/auth.facade';
import { ToastrService } from 'ngx-toastr';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@Component({ @Component({
@@ -16,7 +15,6 @@ import { ActionType } from '@app/domain/action-type.util';
export class ForgotPasswordComponent { export class ForgotPasswordComponent {
private readonly fb = inject(FormBuilder); private readonly fb = inject(FormBuilder);
private readonly facade = inject(AuthFacade); private readonly facade = inject(AuthFacade);
private readonly toastrService = inject(ToastrService);
protected readonly router = inject(Router); protected readonly router = inject(Router);
fpForm = this.fb.group({ fpForm = this.fb.group({
@@ -33,8 +31,6 @@ export class ForgotPasswordComponent {
if (!this.loading().isLoading) { if (!this.loading().isLoading) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.CREATE: 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()) { if (!this.error().hasError && this.facade.isRequestPasswordSent()) {
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
} }
@@ -51,30 +47,4 @@ export class ForgotPasswordComponent {
} }
this.facade.sendRequestPasswordReset(this.fpForm.getRawValue().email!); 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,
}
);
}
} }

View File

@@ -3,7 +3,6 @@ import { Router, RouterLink } from '@angular/router';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { LoginDto } from '@app/domain/authentification/dto/login-dto'; import { LoginDto } from '@app/domain/authentification/dto/login-dto';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { ToastrService } from 'ngx-toastr';
import { ProgressBarModule } from 'primeng/progressbar'; import { ProgressBarModule } from 'primeng/progressbar';
import { AuthFacade } from '@app/ui/authentification/auth.facade'; import { AuthFacade } from '@app/ui/authentification/auth.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -19,7 +18,6 @@ import { BtnLoadingComponent } from '@app/shared/components/btn-loading/btn-load
}) })
@UntilDestroy() @UntilDestroy()
export class LoginComponent { export class LoginComponent {
private readonly toastrService = inject(ToastrService);
private readonly facade = inject(AuthFacade); private readonly facade = inject(AuthFacade);
private formBuilder = inject(FormBuilder); private formBuilder = inject(FormBuilder);
@@ -37,29 +35,19 @@ export class LoginComponent {
protected readonly error = this.facade.error; protected readonly error = this.facade.error;
constructor() { constructor() {
let message = '';
effect(() => { effect(() => {
if (!this.loading().isLoading) { if (!this.loading().isLoading) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.READ: case ActionType.READ:
if (!this.error().hasError) { if (this.error().hasError) {
if (!this.authResponse()!.isValid && !this.authResponse()?.record.verified) { this.loginForm.enable();
message = `Vous ne pouvez pas vous connecter sans valider la verification envoyé à cet adresse ${this.authResponse()?.record.email!}`; this.loginForm.markAllAsTouched();
this.toastrService.warning(`${message}`, `CONNEXION`, { this.loginForm.markAsDirty();
closeButton: true, return;
progressBar: true,
disableTimeOut: true,
});
return;
}
this.router
.navigate(['/my-profile'], { state: { user: this.authResponse()!.record } })
.then(() => {
message = `Bienvenue parmi nous!`;
this.customToast(ActionType.READ, message);
});
} }
this.router
.navigate(['/my-profile'], { state: { user: this.authResponse()!.record } })
.then(() => {});
break; break;
} }
} }
@@ -68,6 +56,8 @@ export class LoginComponent {
onSubmit() { onSubmit() {
if (this.loginForm.invalid) { if (this.loginForm.invalid) {
this.loginForm.markAllAsTouched();
this.loginForm.markAsDirty();
return; return;
} }
this.loginForm.disable(); this.loginForm.disable();
@@ -78,35 +68,4 @@ export class LoginComponent {
this.facade.login(data); this.facade.login(data);
} }
private customToast(action: ActionType, message: string): void {
if (this.error().hasError) {
switch (this.error().action) {
case ActionType.READ:
this.toastrService.warning(`L'email ou mot de passe est incorrect`, `Erreur`, {
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
});
return;
default:
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.READ ? 'CONNEXION' : ''}`, {
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
});
}
} }

View File

@@ -5,7 +5,6 @@ import { LocationFacade } from '@app/ui/location/location.facade';
import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { ProfileFacade } from '@app/ui/profiles/profile.facade';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
@Component({ @Component({
selector: 'app-my-profile-map', selector: 'app-my-profile-map',
@@ -19,7 +18,6 @@ export class MyProfileMapComponent implements OnInit {
readonly profile = input<ProfileViewModel>(); readonly profile = input<ProfileViewModel>();
private readonly locationFacade = inject(LocationFacade); private readonly locationFacade = inject(LocationFacade);
private readonly profileFacade = inject(ProfileFacade); private readonly profileFacade = inject(ProfileFacade);
private readonly feedbackService = inject(FeedbackService);
private readonly loading = this.profileFacade.loading; private readonly loading = this.profileFacade.loading;
private readonly error = this.profileFacade.error; private readonly error = this.profileFacade.error;
@@ -53,11 +51,6 @@ export class MyProfileMapComponent implements OnInit {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
if (!this.error().hasError) { if (!this.error().hasError) {
this.feedbackService.notify(
ActionType.UPDATE,
'Vos coordonnées géographique ont été enregistrés.',
false
);
} }
break; break;
} }

View File

@@ -20,6 +20,6 @@ export class PdfViewerComponent {
return null; return null;
} }
return `${environment.baseUrl}/api/files/profiles/${currentProfile.id}/${currentProfile.cv}`; return currentProfile.cv;
}); });
} }

View File

@@ -3,7 +3,6 @@ import { Router, RouterLink } from '@angular/router';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { RegisterDto } from '@app/domain/authentification/dto/register-dto'; import { RegisterDto } from '@app/domain/authentification/dto/register-dto';
import { UntilDestroy } from '@ngneat/until-destroy'; import { UntilDestroy } from '@ngneat/until-destroy';
import { ToastrService } from 'ngx-toastr';
import { ProgressBarModule } from 'primeng/progressbar'; import { ProgressBarModule } from 'primeng/progressbar';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { AuthFacade } from '@app/ui/authentification/auth.facade'; import { AuthFacade } from '@app/ui/authentification/auth.facade';
@@ -19,14 +18,12 @@ import { BtnLoadingComponent } from '@app/shared/components/btn-loading/btn-load
}) })
@UntilDestroy() @UntilDestroy()
export class RegisterComponent { export class RegisterComponent {
private readonly toastrService = inject(ToastrService);
private readonly formBuilder = inject(FormBuilder); private readonly formBuilder = inject(FormBuilder);
private readonly router = inject(Router); private readonly router = inject(Router);
protected registerForm = this.formBuilder.group({ protected registerForm = this.formBuilder.group({
email: new FormControl('', [Validators.required, Validators.email]), email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', Validators.required), password: new FormControl('', [Validators.required, Validators.minLength(8)]),
passwordConfirm: new FormControl('', Validators.required), passwordConfirm: new FormControl('', [Validators.required, Validators.minLength(8)]),
}); });
formSubmitted = output<any>(); formSubmitted = output<any>();
@@ -37,7 +34,6 @@ export class RegisterComponent {
protected readonly isVerificationEmailSent = this.authFacade.isVerificationEmailSent; protected readonly isVerificationEmailSent = this.authFacade.isVerificationEmailSent;
constructor() { constructor() {
let message = '';
effect(() => { effect(() => {
switch (this.authLoading().action) { switch (this.authLoading().action) {
case ActionType.CREATE: case ActionType.CREATE:
@@ -46,10 +42,11 @@ export class RegisterComponent {
!this.authError().hasError && !this.authError().hasError &&
this.isVerificationEmailSent() this.isVerificationEmailSent()
) { ) {
this.router.navigate(['/auth']).then(() => { if (this.authError().hasError) {
message = `Votre compte a bien été crée avec succès !\n Un mail vous a été envoyé à l'adresse ${this.registerForm.getRawValue().email!} pour confirmer votre inscription.`; this.registerForm.enable();
this.customToast(ActionType.CREATE, message); return;
}); }
this.router.navigate(['/auth']).then(() => {});
} }
break; break;
} }
@@ -65,12 +62,6 @@ export class RegisterComponent {
this.registerForm.get('password')?.value !== this.registerForm.get('passwordConfirm')?.value this.registerForm.get('password')?.value !== this.registerForm.get('passwordConfirm')?.value
) { ) {
this.registerForm.get('passwordConfirm')?.setErrors({ passwordMismatch: true }); this.registerForm.get('passwordConfirm')?.setErrors({ passwordMismatch: true });
this.toastrService.info(`Les mots de passe ne correspondent pas.`, `Information`, {
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
});
return; return;
} }
@@ -81,30 +72,4 @@ export class RegisterComponent {
this.authFacade.register(data); this.authFacade.register(data);
} }
private customToast(action: ActionType, message: string): void {
if (this.authError().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 ? 'INSCRIPTION' : ''}`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
timeOut: 9000,
}
);
}
} }

View File

@@ -10,6 +10,7 @@ import { mockSettingRepo } from '@app/testing/setting.mock';
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
import { ProfileRepository } from '@app/domain/profiles/profile.repository'; import { ProfileRepository } from '@app/domain/profiles/profile.repository';
import { mockProfileRepo } from '@app/testing/profile.mock'; import { mockProfileRepo } from '@app/testing/profile.mock';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
describe('SettingsComponent', () => { describe('SettingsComponent', () => {
let component: SettingsComponent; let component: SettingsComponent;
@@ -32,6 +33,7 @@ describe('SettingsComponent', () => {
fixture = TestBed.createComponent(SettingsComponent); fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.componentRef.setInput('profile', {} as ProfileViewModel);
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -2,7 +2,6 @@ import { Component, effect, inject, input, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ThemeType } from '@app/domain/settings/setting.model'; import { ThemeType } from '@app/domain/settings/setting.model';
import { SettingsFacade } from '@app/ui/settings/settings.facade'; import { SettingsFacade } from '@app/ui/settings/settings.facade';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model';
import { ProfileFacade } from '@app/ui/profiles/profile.facade'; import { ProfileFacade } from '@app/ui/profiles/profile.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
@@ -19,11 +18,10 @@ export class SettingsComponent implements OnInit {
protected readonly ThemeType = ThemeType; protected readonly ThemeType = ThemeType;
private readonly settingsFacade = inject(SettingsFacade); private readonly settingsFacade = inject(SettingsFacade);
private readonly fb: FormBuilder = inject(FormBuilder); private readonly fb: FormBuilder = inject(FormBuilder);
private readonly feedbackService = inject(FeedbackService);
private readonly profileFacade = inject(ProfileFacade); private readonly profileFacade = inject(ProfileFacade);
private readonly loading = this.profileFacade.loading; private readonly loading = this.profileFacade.loading;
private readonly error = this.profileFacade.error; private readonly error = this.profileFacade.error;
profile = input<ProfileViewModel>(); profile = input.required<ProfileViewModel>();
settings = this.settingsFacade.settings; settings = this.settingsFacade.settings;
@@ -53,11 +51,6 @@ export class SettingsComponent implements OnInit {
case ActionType.UPDATE: case ActionType.UPDATE:
if (!this.error().hasError && this.loading().isDone) { if (!this.error().hasError && this.loading().isDone) {
this.updateForm(userSettings); this.updateForm(userSettings);
this.feedbackService.notify(
ActionType.UPDATE,
'Vos paramètres ont été enregistrés.',
false
);
} }
break; break;
} }

View File

@@ -0,0 +1,3 @@
export const mockFeedbackService = {
notify: jest.fn(),
};

View File

@@ -0,0 +1,9 @@
import { signal } from '@angular/core';
const initialFilters = { page: 1, perPage: 10, totalItems: 0, totalPages: 0, search: '' };
export const mockSearchService = {
getFilters: jest.fn(() => signal(initialFilters)),
setFilters: jest.fn(),
get: jest.fn(),
};

View File

@@ -5,6 +5,12 @@ import { FakeProfileRepository } from '@app/testing/domain/profiles/fake-profile
import { mockProfiles } from '@app/testing/profile.mock'; import { mockProfiles } from '@app/testing/profile.mock';
import { Profile } from '@app/domain/profiles/profile.model'; import { Profile } from '@app/domain/profiles/profile.model';
import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto'; import { ProfileDTO } from '@app/domain/profiles/dto/create-profile.dto';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
import { SearchService } from '@app/infrastructure/search/search.service';
import { mockSearchService } from '@app/testing/search.service.mock';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
import { mockFeedbackService } from '@app/testing/feedback.service.mock';
describe('ProfileFacade', () => { describe('ProfileFacade', () => {
let facade: ProfileFacade; let facade: ProfileFacade;
@@ -14,6 +20,9 @@ describe('ProfileFacade', () => {
providers: [ providers: [
ProfileFacade, ProfileFacade,
{ provide: PROFILE_REPOSITORY_TOKEN, useClass: FakeProfileRepository }, { provide: PROFILE_REPOSITORY_TOKEN, useClass: FakeProfileRepository },
{ provide: SearchService, useValue: mockSearchService },
{ provide: FeedbackService, useValue: mockFeedbackService },
{ provide: ToastrService, useValue: mockToastR },
], ],
}); });

View File

@@ -24,7 +24,7 @@ describe('ProfilePresenter', () => {
avatarUrl: '', avatarUrl: '',
apropos: 'Développeur Angular & Node.js', apropos: 'Développeur Angular & Node.js',
bio: 'Passionné de code.', bio: 'Passionné de code.',
cv: 'cv.pdf', cv: 'http://localhost:8090/api/files/profiles/1/cv.pdf',
isProfileVisible: true, isProfileVisible: true,
missingFields: [], missingFields: [],
projets: ['p1', 'p2'], projets: ['p1', 'p2'],

View File

@@ -4,6 +4,8 @@ import { FakeProjectRepository } from '@app/testing/domain/projects/fake-project
import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token'; import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-repository.token';
import { fakeProjects } from '@app/testing/project.mock'; import { fakeProjects } from '@app/testing/project.mock';
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto'; import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('ProjectFacade', () => { describe('ProjectFacade', () => {
let facade: ProjectFacade; let facade: ProjectFacade;
@@ -13,6 +15,7 @@ describe('ProjectFacade', () => {
providers: [ providers: [
ProjectFacade, ProjectFacade,
{ provide: PROJECT_REPOSITORY_TOKEN, useClass: FakeProjectRepository }, { provide: PROJECT_REPOSITORY_TOKEN, useClass: FakeProjectRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}); });

View File

@@ -3,6 +3,8 @@ import { SectorFacade } from '@app/ui/sectors/sector.facade';
import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token'; import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token';
import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository'; import { FakeSectorRepository } from '@app/testing/domain/sectors/fake-sector.repository';
import { fakeSectors } from '@app/testing/sector.mock'; import { fakeSectors } from '@app/testing/sector.mock';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('SectorFacade', () => { describe('SectorFacade', () => {
let sectorFacade: SectorFacade; let sectorFacade: SectorFacade;
@@ -12,6 +14,7 @@ describe('SectorFacade', () => {
providers: [ providers: [
SectorFacade, SectorFacade,
{ provide: SECTOR_REPOSITORY_TOKEN, useClass: FakeSectorRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useClass: FakeSectorRepository },
{ provide: ToastrService, useValue: mockToastR },
], ],
}); });

View File

@@ -2,12 +2,18 @@ import { UserFacade } from '@app/ui/users/user.facade';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token';
import { FakeUserRepository } from '@app/testing/domain/users/fake-user.repository'; import { FakeUserRepository } from '@app/testing/domain/users/fake-user.repository';
import { ToastrService } from 'ngx-toastr';
import { mockToastR } from '@app/testing/toastr.mock';
describe('UserFacade', () => { describe('UserFacade', () => {
let facade: UserFacade; let facade: UserFacade;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [UserFacade, { provide: USER_REPOSITORY_TOKEN, useClass: FakeUserRepository }], providers: [
UserFacade,
{ provide: USER_REPOSITORY_TOKEN, useClass: FakeUserRepository },
{ provide: ToastrService, useValue: mockToastR },
],
}); });
facade = TestBed.inject(UserFacade); facade = TestBed.inject(UserFacade);
}); });

View File

@@ -17,9 +17,12 @@ import { VerifyAuthenticatedUsecase } from '@app/usecase/authentification/verify
import { VerifyEmailUseCase } from '@app/usecase/authentification/verify-email.usecase'; import { VerifyEmailUseCase } from '@app/usecase/authentification/verify-email.usecase';
import { GetCurrentUserUseCase } from '@app/usecase/authentification/get-current-user.usecase'; import { GetCurrentUserUseCase } from '@app/usecase/authentification/get-current-user.usecase';
import { SendRequestPasswordResetUsecase } from '@app/usecase/authentification/send-request-password-reset.usecase'; import { SendRequestPasswordResetUsecase } from '@app/usecase/authentification/send-request-password-reset.usecase';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
import { first } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class AuthFacade { export class AuthFacade {
private readonly feedbackService = inject(FeedbackService);
private readonly authRepository = inject(AUTH_REPOSITORY_TOKEN); private readonly authRepository = inject(AUTH_REPOSITORY_TOKEN);
private readonly profileFacade = new ProfileFacade(); private readonly profileFacade = new ProfileFacade();
@@ -55,32 +58,54 @@ export class AuthFacade {
login(loginDto: LoginDto) { login(loginDto: LoginDto) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
let message = '';
this.loginUseCase.execute(loginDto).subscribe({ this.loginUseCase
next: async (res: AuthResponse) => { .execute(loginDto)
this.authResponse.set(res); .pipe(first())
this.getCurrentUser(); .subscribe({
this.handleError(ActionType.READ, false, null, false); next: async (res: AuthResponse) => {
}, this.authResponse.set(res);
error: (err) => { this.getCurrentUser();
this.handleError(ActionType.READ, true, err.message, false); this.handleError(ActionType.READ, false, null, false);
},
}); if (!this.authResponse()!.isValid && !this.authResponse()?.record.verified) {
message = `Vous ne pouvez pas vous connecter sans valider la verification envoyé à cet adresse ${this.authResponse()?.record.email!}`;
this.feedbackService.notify(ActionType.READ, `${message}`);
return;
}
message = `Bienvenue parmi nous ${loginDto.email}!`;
this.feedbackService.notify(ActionType.READ, `${message}`);
},
error: (err) => {
this.handleError(ActionType.READ, true, err.message, false);
},
});
} }
register(registerDto: RegisterDto) { register(registerDto: RegisterDto) {
this.handleError(ActionType.CREATE, false, null, true); this.handleError(ActionType.CREATE, false, null, true);
this.registerUseCase.execute(registerDto).subscribe({ let message = '';
next: (user) => {
this.getCurrentUser(); this.registerUseCase
this.sendVerificationEmail(registerDto.email); .execute(registerDto)
this.createDefaultProfile(user.id); .pipe(first())
this.handleError(ActionType.CREATE, false, null, false); .subscribe({
}, next: (user) => {
error: (err) => { this.getCurrentUser();
this.handleError(ActionType.CREATE, true, err.message, false); this.sendVerificationEmail(registerDto.email);
}, this.createDefaultProfile(user.id);
});
message = `Votre compte a bien été crée avec succès !\n Un mail vous a été envoyé à l'adresse ${registerDto.email} pour confirmer votre inscription.`;
this.feedbackService.notify(ActionType.CREATE, `${message}`);
this.handleError(ActionType.CREATE, false, null, false);
},
error: (err) => {
this.handleError(ActionType.CREATE, true, err.message, false);
},
});
} }
logout() { logout() {
@@ -103,28 +128,38 @@ export class AuthFacade {
sendRequestPasswordReset(email: string) { sendRequestPasswordReset(email: string) {
this.handleError(ActionType.CREATE, false, null, true); this.handleError(ActionType.CREATE, false, null, true);
this.senRequestPasswordResetUseCase.execute(email).subscribe({
next: (res) => { let message = '';
this.isRequestPasswordSent.set(res); this.senRequestPasswordResetUseCase
this.handleError(ActionType.CREATE, false, null, false); .execute(email)
}, .pipe(first())
error: (err) => { .subscribe({
this.handleError(ActionType.CREATE, true, err.message, false); next: (res) => {
}, this.isRequestPasswordSent.set(res);
}); this.handleError(ActionType.CREATE, false, null, false);
message = `Un mail de réinitialisation vous a été envoyé à cette adresse mail : ${email}`;
this.feedbackService.notify(ActionType.CREATE, `${message}`);
},
error: (err) => {
this.handleError(ActionType.CREATE, true, err.message, false);
},
});
} }
private sendVerificationEmail(email: string) { private sendVerificationEmail(email: string) {
this.handleError(ActionType.CREATE, false, null, true); this.handleError(ActionType.CREATE, false, null, true);
this.sendVerificationEmailUseCase.execute(email).subscribe({ this.sendVerificationEmailUseCase
next: (res) => { .execute(email)
this.isVerificationEmailSent.set(res); .pipe(first())
this.handleError(ActionType.CREATE, false, null, false); .subscribe({
}, next: (res) => {
error: (err) => { this.isVerificationEmailSent.set(res);
this.handleError(ActionType.CREATE, true, err.message, false); this.handleError(ActionType.CREATE, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.CREATE, true, err.message, false);
},
});
} }
private createDefaultProfile(userId: string) { private createDefaultProfile(userId: string) {
@@ -153,5 +188,8 @@ export class AuthFacade {
) { ) {
this.error.set({ action, hasError, message }); this.error.set({ action, hasError, message });
this.loading.set({ action, isLoading }); this.loading.set({ action, isLoading });
if (hasError) {
this.feedbackService.notify(ActionType.READ, message!, true);
}
} }
} }

View File

@@ -19,12 +19,15 @@ import { SearchService } from '@app/infrastructure/search/search.service';
import { UpdateCoordinateProfileUseCase } from '@app/usecase/profiles/update-coordinate-profile.usecase'; import { UpdateCoordinateProfileUseCase } from '@app/usecase/profiles/update-coordinate-profile.usecase';
import { SettingsProfileDto } from '@app/domain/profiles/dto/settings-profile.dto'; import { SettingsProfileDto } from '@app/domain/profiles/dto/settings-profile.dto';
import { UpdateSettingsProfileUseCase } from '@app/usecase/profiles/update-settings-profile.usecase'; import { UpdateSettingsProfileUseCase } from '@app/usecase/profiles/update-settings-profile.usecase';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
import { first, Subscription } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ProfileFacade { export class ProfileFacade {
private profileRepository = inject(PROFILE_REPOSITORY_TOKEN); private readonly feedbackService = inject(FeedbackService);
private readonly profileRepository = inject(PROFILE_REPOSITORY_TOKEN);
private readonly searchService = inject(SearchService); private readonly searchService = inject(SearchService);
private readonly profilePresenter = new ProfilePresenter(); private readonly profilePresenter = new ProfilePresenter();
@@ -50,14 +53,21 @@ export class ProfileFacade {
message: null, message: null,
}); });
private searchSubscription: Subscription | null = null;
load(search?: SearchFilters) { load(search?: SearchFilters) {
if (this.searchSubscription) {
this.searchSubscription.unsubscribe();
this.searchSubscription = null;
}
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
if (search === undefined || search === null) { if (search === undefined || search === null) {
search = this.searchFilters(); search = this.searchFilters();
} }
this.listUseCase.execute(search).subscribe({ this.searchSubscription = this.listUseCase.execute(search).subscribe({
next: (profilePaginated: ProfilePaginated) => { next: (profilePaginated: ProfilePaginated) => {
const filters = { const filters = {
...this.searchFilters(), ...this.searchFilters(),
@@ -76,93 +86,124 @@ export class ProfileFacade {
this.profilePaginated.set(profileViewModelPaginated); this.profilePaginated.set(profileViewModelPaginated);
this.profiles.set(profileViewModelPaginated.items); this.profiles.set(profileViewModelPaginated.items);
this.handleError(ActionType.READ, false, null, false); this.handleError(ActionType.READ, false, null, false);
this.searchSubscription = null;
}, },
error: (err) => { error: (err) => {
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, err, false);
this.searchSubscription = null;
}, },
}); });
} }
loadOne(profileId: string) { loadOne(profileId: string) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.getUseCase.execute(profileId).subscribe({ this.getUseCase
next: (profile: Profile) => { .execute(profileId)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.READ, false, null, false); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.READ, false, err, false);
},
});
} }
loadOneByUserId(userId: string) { loadOneByUserId(userId: string) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.getUseCase.executeByUserId(userId).subscribe({ this.getUseCase
next: (profile: Profile) => { .executeByUserId(userId)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.READ, false, null, false); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.READ, false, err, false);
},
});
} }
create(profileDto: ProfileDTO) { create(profileDto: ProfileDTO) {
this.handleError(ActionType.CREATE, false, null, true); this.handleError(ActionType.CREATE, false, null, true);
let message = null;
this.createUseCase.execute(profileDto).subscribe({ this.createUseCase
next: (profile: Profile) => { .execute(profileDto)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.CREATE, false, null, false); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.CREATE, false, err, false); this.handleError(ActionType.CREATE, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.CREATE, false, err, false);
},
});
} }
update(profileId: string, profile: Partial<Profile>) { update(profileId: string, profile: Partial<Profile>) {
this.handleError(ActionType.UPDATE, false, null, true); this.handleError(ActionType.UPDATE, false, null, true);
this.updateUseCase.execute(profileId, profile).subscribe({ this.updateUseCase
next: (profile: Profile) => { .execute(profileId, profile)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.UPDATE, false, null, false); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.UPDATE, false, err, false); this.handleError(ActionType.UPDATE, false, null, false);
},
}); const message = `Vos informations personnelles ont bien été modifier !`;
this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.UPDATE, false, err, false);
},
});
} }
updateCoordinate(profileId: string, latitude: number, longitude: number) { updateCoordinate(profileId: string, latitude: number, longitude: number) {
this.handleError(ActionType.UPDATE, false, null, true); this.handleError(ActionType.UPDATE, false, null, true);
this.updateCoordinateUseCase.execute(profileId, latitude, longitude).subscribe({ this.updateCoordinateUseCase
next: (profile: Profile) => { .execute(profileId, latitude, longitude)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.UPDATE, false, null, false); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.UPDATE, false, err, false); this.handleError(ActionType.UPDATE, false, null, false);
},
}); const message = `Vos coordonnées géographique ont été enregistrés.`;
this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.UPDATE, false, err, false);
},
});
} }
updateSettings(profileId: string, settings: SettingsProfileDto) { updateSettings(profileId: string, settings: SettingsProfileDto) {
this.handleError(ActionType.UPDATE, false, null, true); this.handleError(ActionType.UPDATE, false, null, true);
this.updateSettingsUseCase.execute(profileId, settings).subscribe({ this.updateSettingsUseCase
next: (profile: Profile) => { .execute(profileId, settings)
this.profile.set(this.profilePresenter.toViewModel(profile)); .pipe(first())
this.handleError(ActionType.UPDATE, false, null, false, true); .subscribe({
}, next: (profile: Profile) => {
error: (err) => { this.profile.set(this.profilePresenter.toViewModel(profile));
this.handleError(ActionType.UPDATE, false, err, false); this.handleError(ActionType.UPDATE, false, null, false, true);
},
}); const message = `Vos paramètres ont été enregistrés.`;
this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.UPDATE, false, err, false);
},
});
} }
private handleError( private handleError(
@@ -174,5 +215,8 @@ export class ProfileFacade {
) { ) {
this.error.set({ action, hasError, message }); this.error.set({ action, hasError, message });
this.loading.set({ action, isLoading, isDone }); this.loading.set({ action, isLoading, isDone });
if (hasError) {
this.feedbackService.notify(ActionType.READ, message!, true);
}
} }
} }

View File

@@ -38,7 +38,7 @@ export class ProfilePresenter {
reseaux: profile.reseaux, reseaux: profile.reseaux,
apropos: profile.apropos, apropos: profile.apropos,
projets: profile.projets, projets: profile.projets,
cv: profile.cv, cv: `${environment.baseUrl}/api/files/profiles/${profile.id}/${profile.cv}`,
bio: profile.bio, bio: profile.bio,
coordonnees: profile.coordonnees coordonnees: profile.coordonnees
? { latitude: profile!.coordonnees!.lat!, longitude: profile!.coordonnees!.lon! } ? { latitude: profile!.coordonnees!.lat!, longitude: profile!.coordonnees!.lon! }
@@ -78,7 +78,9 @@ export class ProfilePresenter {
return false; return false;
} }
const hasProfession = !!currentProfile.profession; const hasProfession = currentProfile.profession
? currentProfile.profession.toLowerCase() !== 'profession non renseignée'
: false;
const hasSector = !!currentProfile.secteur; const hasSector = !!currentProfile.secteur;
return hasProfession && hasSector; return hasProfession && hasSector;
@@ -87,7 +89,10 @@ export class ProfilePresenter {
private missingFields(currentProfile: Profile) { private missingFields(currentProfile: Profile) {
const missing: string[] = []; const missing: string[] = [];
if (!currentProfile?.profession) { if (
!currentProfile?.profession ||
currentProfile.profession.toLowerCase() === 'profession non renseignée'
) {
missing.push('profession'); missing.push('profession');
} }
if (!currentProfile?.secteur) { if (!currentProfile?.secteur) {

View File

@@ -11,11 +11,14 @@ import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
import { ErrorResponse } from '@app/domain/error-response.util'; import { ErrorResponse } from '@app/domain/error-response.util';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { LoaderAction } from '@app/domain/loader-action.util'; import { LoaderAction } from '@app/domain/loader-action.util';
import { first, Subscription } from 'rxjs';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ProjectFacade { export class ProjectFacade {
private readonly feedbackService = inject(FeedbackService);
private readonly projectRepo = inject(PROJECT_REPOSITORY_TOKEN); private readonly projectRepo = inject(PROJECT_REPOSITORY_TOKEN);
private readonly createUseCase = new CreateProjectUseCase(this.projectRepo); private readonly createUseCase = new CreateProjectUseCase(this.projectRepo);
@@ -33,17 +36,26 @@ export class ProjectFacade {
}); });
private readonly projectPresenter = new ProjectPresenter(); private readonly projectPresenter = new ProjectPresenter();
private projectSubscription: Subscription | null = null;
load(userId: string) { load(userId: string) {
if (this.projectSubscription) {
this.projectSubscription.unsubscribe();
this.projectSubscription = null;
}
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.listUseCase.execute(userId).subscribe({ this.projectSubscription = this.listUseCase.execute(userId).subscribe({
next: (projects: Project[]) => { next: (projects: Project[]) => {
this.projects.set(this.projectPresenter.toViewModels(projects)); this.projects.set(this.projectPresenter.toViewModels(projects));
this.handleError(ActionType.READ, false, null, false); this.handleError(ActionType.READ, false, null, false);
this.projectSubscription = null;
}, },
error: (err) => { error: (err) => {
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, err, false);
this.projectSubscription = null;
}, },
}); });
} }
@@ -51,44 +63,58 @@ export class ProjectFacade {
loadOne(projectId: string) { loadOne(projectId: string) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.getUseCase.execute(projectId).subscribe({ this.getUseCase
next: (project: Project) => { .execute(projectId)
this.project.set(this.projectPresenter.toViewModel(project)); .pipe(first())
this.handleError(ActionType.READ, false, null, false); .subscribe({
}, next: (project: Project) => {
error: (err) => { this.project.set(this.projectPresenter.toViewModel(project));
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.READ, false, err, false);
},
});
} }
create(projectDto: CreateProjectDto) { create(projectDto: CreateProjectDto) {
this.handleError(ActionType.CREATE, false, null, true); this.handleError(ActionType.CREATE, false, null, true);
this.createUseCase.execute(projectDto).subscribe({ this.createUseCase
next: (project: Project) => { .execute(projectDto)
this.project.set(this.projectPresenter.toViewModel(project)); .pipe(first())
this.projects.update((prev) => [...prev, this.projectPresenter.toViewModel(project)]); .subscribe({
this.handleError(ActionType.CREATE, false, null, false); next: (project: Project) => {
}, this.project.set(this.projectPresenter.toViewModel(project));
error: (err) => { this.projects.update((prev) => [...prev, this.projectPresenter.toViewModel(project)]);
this.handleError(ActionType.CREATE, false, err, false); this.handleError(ActionType.CREATE, false, null, false);
},
}); const message = `Le projet ${project.nom} a bien été créer !`;
this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.CREATE, false, err, false);
},
});
} }
update(userId: string, data: any) { update(userId: string, data: any) {
this.handleError(ActionType.UPDATE, false, null, true); this.handleError(ActionType.UPDATE, false, null, true);
this.UpdateUseCase.execute(userId, data).subscribe({ this.UpdateUseCase.execute(userId, data)
next: (project: Project) => { .pipe(first())
this.project.set(this.projectPresenter.toViewModel(project)); .subscribe({
this.handleError(ActionType.UPDATE, false, null, false); next: (project: Project) => {
}, this.project.set(this.projectPresenter.toViewModel(project));
error: (err) => { this.handleError(ActionType.UPDATE, false, null, false);
this.handleError(ActionType.UPDATE, false, err, false);
}, const message = `Les informations du projet ${project.nom} ont bien été modifier !`;
}); this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.UPDATE, false, err, false);
},
});
} }
private handleError( private handleError(
@@ -99,5 +125,9 @@ export class ProjectFacade {
) { ) {
this.error.set({ action, hasError, message }); this.error.set({ action, hasError, message });
this.loading.set({ action, isLoading }); this.loading.set({ action, isLoading });
if (hasError) {
this.feedbackService.notify(ActionType.READ, message!, true);
}
} }
} }

View File

@@ -8,9 +8,12 @@ import { ErrorResponse } from '@app/domain/error-response.util';
import { SectorPresenterModel } from '@app/ui/sectors/sector.presenter.model'; import { SectorPresenterModel } from '@app/ui/sectors/sector.presenter.model';
import { Sector } from '@app/domain/sectors/sector.model'; import { Sector } from '@app/domain/sectors/sector.model';
import { SectorPresenter } from '@app/ui/sectors/sector.presenter'; import { SectorPresenter } from '@app/ui/sectors/sector.presenter';
import { first, Subscription } from 'rxjs';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
@Injectable() @Injectable()
export class SectorFacade { export class SectorFacade {
private readonly feedbackService = inject(FeedbackService);
private readonly sectorRepo = inject(SECTOR_REPOSITORY_TOKEN); private readonly sectorRepo = inject(SECTOR_REPOSITORY_TOKEN);
private readonly listSectorUseCase = new ListSectorUsecase(this.sectorRepo); private readonly listSectorUseCase = new ListSectorUsecase(this.sectorRepo);
@@ -27,31 +30,42 @@ export class SectorFacade {
}); });
private readonly sectorPresenter = new SectorPresenter(); private readonly sectorPresenter = new SectorPresenter();
private sectorSubscription: Subscription | null = null;
load() { load() {
if (this.sectorSubscription) {
this.sectorSubscription.unsubscribe();
this.sectorSubscription = null;
}
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.listSectorUseCase.execute().subscribe({ this.sectorSubscription = this.listSectorUseCase.execute().subscribe({
next: (sectors: Sector[]) => { next: (sectors: Sector[]) => {
this.sectors.set(this.sectorPresenter.toViewModels(sectors)); this.sectors.set(this.sectorPresenter.toViewModels(sectors));
this.handleError(ActionType.READ, false, null, false); this.handleError(ActionType.READ, false, null, false);
this.sectorSubscription = null;
}, },
error: (err) => { error: (err) => {
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, err, false);
this.sectorSubscription = null;
}, },
}); });
} }
loadOne(sectorId: string) { loadOne(sectorId: string) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.getSectorUseCase.execute(sectorId).subscribe({ this.getSectorUseCase
next: (sector: Sector) => { .execute(sectorId)
this.sector.set(this.sectorPresenter.toViewModel(sector)); .pipe(first())
this.handleError(ActionType.READ, false, null, false); .subscribe({
}, next: (sector: Sector) => {
error: (err) => { this.sector.set(this.sectorPresenter.toViewModel(sector));
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.READ, false, err, false);
},
});
} }
private handleError( private handleError(
@@ -62,5 +76,9 @@ export class SectorFacade {
) { ) {
this.error.set({ action, hasError, message }); this.error.set({ action, hasError, message });
this.loading.set({ action, isLoading }); this.loading.set({ action, isLoading });
if (hasError) {
this.feedbackService.notify(ActionType.READ, message!, true);
}
} }
} }

View File

@@ -21,18 +21,19 @@ export class FeedbackService {
*/ */
notify(action: ActionType | null, message: string, hasError: boolean = false): void { notify(action: ActionType | null, message: string, hasError: boolean = false): void {
if (hasError) { if (hasError) {
this.handleError(); const userFriendlyMessage = this.handleErrorMessage(message);
this.handleError(userFriendlyMessage);
} else { } else {
this.handleSuccess(action, message); this.handleSuccess(action, message);
} }
} }
private handleError(): void { private handleError(message: string): void {
this.toastr.error( this.toastr.error(`${message}`, `Erreur`, {
`Une erreur s'est produite, veuillez réessayer ultérieurement`, ...this.TOAST_CONFIG,
`Erreur`, disableTimeOut: true,
this.TOAST_CONFIG progressBar: false,
); });
} }
private handleSuccess(action: ActionType | null, message: string): void { private handleSuccess(action: ActionType | null, message: string): void {
@@ -52,4 +53,30 @@ export class FeedbackService {
this.toastr.success(message, title, this.TOAST_CONFIG); this.toastr.success(message, title, this.TOAST_CONFIG);
} }
private handleErrorMessage(messageError: string): string {
const lowerMessage = messageError.toLowerCase();
// Mapping des erreurs PocketBase courantes vers messages utilisateurs
const errorMessages: Record<string, string> = {
'failed to authenticate': `Votre email n'est pas valide ou votre mot de passe est incorrect`,
'failed to auth': `Échec de l'authentification. Vérifiez vos identifiants.`,
'invalid email': `L'adresse email n'est pas valide.`,
'invalid password': `Le mot de passe ne respecte pas les critères de sécurité.`,
'passwords do not match': `Les mots de passe saisis ne correspondent pas.`,
'record not found': `La ressource demandée n'existe pas.`,
'validation error': `Données invalides. Vérifiez les champs saisis.`,
'network error': `Problème de connexion. Vérifiez votre réseau.`,
};
// Recherche du premier match
for (const [errorKey, userMessage] of Object.entries(errorMessages)) {
if (lowerMessage.includes(errorKey)) {
return userMessage;
}
}
// Message par défaut
return `Une erreur s'est produite, veuillez réessayer ultérieurement`;
}
} }

View File

@@ -7,9 +7,12 @@ import { ActionType } from '@app/domain/action-type.util';
import { ErrorResponse } from '@app/domain/error-response.util'; import { ErrorResponse } from '@app/domain/error-response.util';
import { UserViewModel } from '@app/ui/users/user.presenter.model'; import { UserViewModel } from '@app/ui/users/user.presenter.model';
import { UserPresenter } from '@app/ui/users/user.presenter'; import { UserPresenter } from '@app/ui/users/user.presenter';
import { first } from 'rxjs';
import { FeedbackService } from '@app/ui/shared/services/feedback.service';
@Injectable() @Injectable()
export class UserFacade { export class UserFacade {
private readonly feedbackService = inject(FeedbackService);
private readonly userRepository = inject(USER_REPOSITORY_TOKEN); private readonly userRepository = inject(USER_REPOSITORY_TOKEN);
private readonly getUseCase = new GetUserUseCase(this.userRepository); private readonly getUseCase = new GetUserUseCase(this.userRepository);
@@ -28,28 +31,37 @@ export class UserFacade {
loadOne(userId: string) { loadOne(userId: string) {
this.handleError(ActionType.READ, false, null, true); this.handleError(ActionType.READ, false, null, true);
this.getUseCase.execute(userId).subscribe({ this.getUseCase
next: (user) => { .execute(userId)
this.user.set(this.userPresenter.toViewModel(user)); .pipe(first())
this.handleError(ActionType.READ, false, null, false); .subscribe({
}, next: (user) => {
error: (err) => { this.user.set(this.userPresenter.toViewModel(user));
this.handleError(ActionType.READ, false, err, false); this.handleError(ActionType.READ, false, null, false);
}, },
}); error: (err) => {
this.handleError(ActionType.READ, false, err, false);
},
});
} }
update(userId: string, user: Partial<UserViewModel>) { update(userId: string, user: Partial<UserViewModel>) {
this.handleError(ActionType.UPDATE, false, null, true); this.handleError(ActionType.UPDATE, false, null, true);
this.updateUseCase.execute(userId, user).subscribe({ this.updateUseCase
next: (user) => { .execute(userId, user)
this.user.set(this.userPresenter.toViewModel(user)); .pipe(first())
this.handleError(ActionType.UPDATE, false, null, false); .subscribe({
}, next: (user) => {
error: (err) => { this.user.set(this.userPresenter.toViewModel(user));
this.handleError(ActionType.UPDATE, false, err, false); this.handleError(ActionType.UPDATE, false, null, false);
},
}); const message = `Votre profile a bien été modifier !`;
this.feedbackService.notify(ActionType.UPDATE, message);
},
error: (err) => {
this.handleError(ActionType.UPDATE, false, err, false);
},
});
} }
private handleError( private handleError(
@@ -60,5 +72,9 @@ export class UserFacade {
) { ) {
this.error.set({ action, hasError, message }); this.error.set({ action, hasError, message });
this.loading.set({ action, isLoading }); this.loading.set({ action, isLoading });
if (hasError) {
this.feedbackService.notify(ActionType.READ, message!, true);
}
} }
} }