diff --git a/package.json b/package.json index 03aaa09..56e4feb 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,18 @@ "tsc": "tsc --noEmit", "tsc:watch": "tsc --noEmit --watch", "prettier": "prettier --write \"src/**/*.{ts,html,scss,css,md,json}\"", + "prettier:check": "prettier --check \"src/**/*.{ts,html,scss,css,md,json}\"", + "format": "npm run prettier && npm run lint:fix", + "lint": "ng lint", + "lint:fix": "ng lint --fix", + "clean:imports": "ts-unused-exports tsconfig.json --excludePathsFromReport=\"src/main.ts;src/environments\" && npm run lint:fix", + "fix:all": "npm run format && npm run tsc", "check:all": "npm run format && npm run tsc && npm run lint && npm run test", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "test:ci": "jest --runInBand", - "serve:ssr:TrouveTonProfile": "node dist/trouve-ton-profile/server/server.mjs", - "lint": "ng lint" + "serve:ssr:TrouveTonProfile": "node dist/trouve-ton-profile/server/server.mjs" }, "private": true, "dependencies": { diff --git a/src/app/app.config.ts b/src/app/app.config.ts index d87264e..ef9b891 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -17,6 +17,8 @@ import { PROJECT_REPOSITORY_TOKEN } from '@app/infrastructure/projects/project-r import { PbProjectRepository } from '@app/infrastructure/projects/pb-project.repository'; import { SECTOR_REPOSITORY_TOKEN } from '@app/infrastructure/sectors/sector-repository.token'; import { PbSectorRepository } from '@app/infrastructure/sectors/pb-sector.repository'; +import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; +import { PbUserRepository } from '@app/infrastructure/users/pb-user.repository'; export const appConfig: ApplicationConfig = { providers: [ @@ -34,6 +36,7 @@ export const appConfig: ApplicationConfig = { { provide: PROFILE_REPOSITORY_TOKEN, useExisting: PbProfileRepository }, { provide: PROJECT_REPOSITORY_TOKEN, useExisting: PbProjectRepository }, { provide: SECTOR_REPOSITORY_TOKEN, useExisting: PbSectorRepository }, + { provide: USER_REPOSITORY_TOKEN, useExisting: PbUserRepository }, provideToastr({ timeOut: 10000, positionClass: 'toast-top-right', diff --git a/src/app/core/resolvers/my-profile/my-profile.resolver.spec.ts b/src/app/core/resolvers/my-profile/my-profile.resolver.spec.ts index d9b37ef..200d2b9 100644 --- a/src/app/core/resolvers/my-profile/my-profile.resolver.spec.ts +++ b/src/app/core/resolvers/my-profile/my-profile.resolver.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { myProfileResolver } from './my-profile.resolver'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; describe('myProfileResolver', () => { let mockRouter: Partial; diff --git a/src/app/core/resolvers/my-profile/my-profile.resolver.ts b/src/app/core/resolvers/my-profile/my-profile.resolver.ts index 90c852e..c096a13 100644 --- a/src/app/core/resolvers/my-profile/my-profile.resolver.ts +++ b/src/app/core/resolvers/my-profile/my-profile.resolver.ts @@ -1,6 +1,6 @@ import { ResolveFn, Router } from '@angular/router'; -import { User } from '@app/shared/models/user'; import { inject } from '@angular/core'; +import { User } from '@app/domain/users/user.model'; export const myProfileResolver: ResolveFn<{ user: User }> = (route, state) => { const router = inject(Router); diff --git a/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts b/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts index b0d5469..1c0e259 100644 --- a/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts +++ b/src/app/core/resolvers/profile/detail/detail.resolver.spec.ts @@ -2,8 +2,8 @@ import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { detailResolver } from './detail.resolver'; -import { User } from '@app/shared/models/user'; import { Profile } from '@app/domain/profiles/profile.model'; +import { User } from '@app/domain/users/user.model'; describe('detailResolver', () => { let mockRoute: Partial; diff --git a/src/app/core/resolvers/profile/detail/detail.resolver.ts b/src/app/core/resolvers/profile/detail/detail.resolver.ts index 94b1721..2a7ff6a 100644 --- a/src/app/core/resolvers/profile/detail/detail.resolver.ts +++ b/src/app/core/resolvers/profile/detail/detail.resolver.ts @@ -1,7 +1,7 @@ import { ResolveFn, Router } from '@angular/router'; import { inject } from '@angular/core'; -import { User } from '@app/shared/models/user'; import { Profile } from '@app/domain/profiles/profile.model'; +import { User } from '@app/domain/users/user.model'; export const detailResolver: ResolveFn<{ user: User; profile: Profile }> = (route, state) => { const paramValue = route.params['name']; diff --git a/src/app/core/services/authentication/auth.service.spec.ts b/src/app/core/services/authentication/auth.service.spec.ts index ec7cce5..8e90fd3 100644 --- a/src/app/core/services/authentication/auth.service.spec.ts +++ b/src/app/core/services/authentication/auth.service.spec.ts @@ -3,8 +3,8 @@ import { TestBed } from '@angular/core/testing'; import { AuthService } from './auth.service'; import { LoginDto } from '@app/shared/models/login-dto'; import { RegisterDto } from '@app/shared/models/register-dto'; -import { User } from '@app/shared/models/user'; import { Router } from '@angular/router'; +import { User } from '@app/domain/users/user.model'; describe('AuthService', () => { let authService: AuthService; diff --git a/src/app/core/services/authentication/auth.service.ts b/src/app/core/services/authentication/auth.service.ts index 9b61a30..e381b66 100644 --- a/src/app/core/services/authentication/auth.service.ts +++ b/src/app/core/services/authentication/auth.service.ts @@ -4,7 +4,7 @@ import PocketBase from 'pocketbase'; import { environment } from '@env/environment'; import { Auth } from '@app/shared/models/auth'; import { RegisterDto } from '@app/shared/models/register-dto'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; @Injectable({ providedIn: 'root', diff --git a/src/app/core/services/user/user.service.spec.ts b/src/app/core/services/user/user.service.spec.ts deleted file mode 100644 index 97c400d..0000000 --- a/src/app/core/services/user/user.service.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { UserService } from './user.service'; -import { Router } from '@angular/router'; - -describe('UserService', () => { - let service: UserService; - - const routerSpy = { - navigate: jest.fn(), - navigateByUrl: jest.fn(), - }; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - { provide: Router, useValue: routerSpy }, // <<— spy: neutralise la navigation - ], - imports: [], - }); - service = TestBed.inject(UserService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/core/services/user/user.service.ts b/src/app/core/services/user/user.service.ts deleted file mode 100644 index d767b78..0000000 --- a/src/app/core/services/user/user.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@angular/core'; -import PocketBase from 'pocketbase'; -import { environment } from '@env/environment'; -import { from } from 'rxjs'; -import { User } from '@app/shared/models/user'; - -@Injectable({ - providedIn: 'root', -}) -export class UserService { - getUserById(id: string) { - const pb = new PocketBase(environment.baseUrl); - return from(pb.collection('users').getOne(id)); - } - - updateUser(id: string, data: User | any) { - const pb = new PocketBase(environment.baseUrl); - return from(pb.collection('users').update(id, data)); - } -} diff --git a/src/app/shared/models/user.ts b/src/app/domain/users/user.model.ts similarity index 100% rename from src/app/shared/models/user.ts rename to src/app/domain/users/user.model.ts diff --git a/src/app/domain/users/user.repository.ts b/src/app/domain/users/user.repository.ts new file mode 100644 index 0000000..902da3b --- /dev/null +++ b/src/app/domain/users/user.repository.ts @@ -0,0 +1,7 @@ +import { Observable } from 'rxjs'; +import { User } from '@app/domain/users/user.model'; + +export interface UserRepository { + getUserById(userId: string): Observable; + update(userId: string, user: Partial | User): Observable; +} diff --git a/src/app/infrastructure/users/pb-user.repository.ts b/src/app/infrastructure/users/pb-user.repository.ts new file mode 100644 index 0000000..1307084 --- /dev/null +++ b/src/app/infrastructure/users/pb-user.repository.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { UserRepository } from '@app/domain/users/user.repository'; +import { from, Observable } from 'rxjs'; +import { environment } from '@env/environment'; +import PocketBase from 'pocketbase'; +import { User } from '@app/domain/users/user.model'; + +@Injectable({ + providedIn: 'root', +}) +export class PbUserRepository implements UserRepository { + private pb = new PocketBase(environment.baseUrl); + + getUserById(userId: string): Observable { + return from(this.pb.collection('users').getOne(userId)); + } + + update(userId: string, user: Partial | User): Observable { + return from(this.pb.collection('users').update(userId, user)); + } +} diff --git a/src/app/infrastructure/users/user-repository.token.ts b/src/app/infrastructure/users/user-repository.token.ts new file mode 100644 index 0000000..dea32d8 --- /dev/null +++ b/src/app/infrastructure/users/user-repository.token.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core'; +import { UserRepository } from '@app/domain/users/user.repository'; + +export const USER_REPOSITORY_TOKEN = new InjectionToken('UserRepository'); diff --git a/src/app/routes/my-profile/my-profile.component.spec.ts b/src/app/routes/my-profile/my-profile.component.spec.ts index 59f5295..6f85e2c 100644 --- a/src/app/routes/my-profile/my-profile.component.spec.ts +++ b/src/app/routes/my-profile/my-profile.component.spec.ts @@ -7,12 +7,15 @@ import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-r import { of } from 'rxjs'; import { Profile } from '@app/domain/profiles/profile.model'; import { ToastrService } from 'ngx-toastr'; +import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; +import { UserRepository } from '@app/domain/users/user.repository'; describe('MyProfileComponent', () => { let component: MyProfileComponent; let fixture: ComponentFixture; let mockProfileRepo: ProfileRepository; + let mockUserRepo: UserRepository; let mockToastrService: Partial; beforeEach(async () => { @@ -22,6 +25,12 @@ describe('MyProfileComponent', () => { update: jest.fn(), getByUserId: jest.fn().mockReturnValue(of({} as Profile)), }; + + mockUserRepo = { + update: jest.fn(), + getUserById: jest.fn(), + }; + mockToastrService = { warning: jest.fn(), success: jest.fn(), @@ -33,6 +42,7 @@ describe('MyProfileComponent', () => { providers: [ provideRouter([]), { provide: PROFILE_REPOSITORY_TOKEN, useValue: mockProfileRepo }, + { provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepo }, { provide: ToastrService, useValue: mockToastrService }, ], }).compileComponents(); diff --git a/src/app/routes/my-profile/my-profile.component.ts b/src/app/routes/my-profile/my-profile.component.ts index 6a7166c..ce86df1 100644 --- a/src/app/routes/my-profile/my-profile.component.ts +++ b/src/app/routes/my-profile/my-profile.component.ts @@ -1,6 +1,6 @@ import { Component, computed, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute, RouterOutlet } from '@angular/router'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; import { Location, UpperCasePipe } from '@angular/common'; import { UntilDestroy } from '@ngneat/until-destroy'; import { environment } from '@env/environment'; diff --git a/src/app/routes/profile/profile-detail/profile-detail.component.ts b/src/app/routes/profile/profile-detail/profile-detail.component.ts index e23e6fb..b9ecebd 100644 --- a/src/app/routes/profile/profile-detail/profile-detail.component.ts +++ b/src/app/routes/profile/profile-detail/profile-detail.component.ts @@ -1,7 +1,7 @@ import { Component, computed, inject } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { UpperCasePipe } from '@angular/common'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; import { ChipsComponent } from '@app/shared/components/chips/chips.component'; import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component'; import { UntilDestroy } from '@ngneat/until-destroy'; diff --git a/src/app/shared/components/loading/loading.component.ts b/src/app/shared/components/loading/loading.component.ts index c29ae25..4b14914 100644 --- a/src/app/shared/components/loading/loading.component.ts +++ b/src/app/shared/components/loading/loading.component.ts @@ -8,5 +8,5 @@ import { Component, Input } from '@angular/core'; styleUrl: './loading.component.scss', }) export class LoadingComponent { - @Input() message: string = 'Chargement...'; + @Input() message = 'Chargement...'; } diff --git a/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts b/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts index 9a814c7..0da80f4 100644 --- a/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts +++ b/src/app/shared/components/my-profile-update-project-form/my-profile-update-project-form.component.ts @@ -9,7 +9,6 @@ import { SimpleChanges, } from '@angular/core'; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; -import { NgClass } from '@angular/common'; import { PaginatorModule } from 'primeng/paginator'; import { ProjectPictureFormComponent } from '@app/shared/components/project-picture-form/project-picture-form.component'; import { ToastrService } from 'ngx-toastr'; @@ -22,13 +21,7 @@ import { LoadingComponent } from '@app/shared/components/loading/loading.compone @Component({ selector: 'app-my-profile-update-project-form', standalone: true, - imports: [ - PaginatorModule, - ReactiveFormsModule, - NgClass, - ProjectPictureFormComponent, - LoadingComponent, - ], + imports: [PaginatorModule, ReactiveFormsModule, ProjectPictureFormComponent, LoadingComponent], templateUrl: './my-profile-update-project-form.component.html', styleUrl: './my-profile-update-project-form.component.scss', }) diff --git a/src/app/shared/components/nav-bar/nav-bar.component.spec.ts b/src/app/shared/components/nav-bar/nav-bar.component.spec.ts index 5ce4a8f..b8964c5 100644 --- a/src/app/shared/components/nav-bar/nav-bar.component.spec.ts +++ b/src/app/shared/components/nav-bar/nav-bar.component.spec.ts @@ -6,7 +6,7 @@ import { AuthService } from '@app/core/services/authentication/auth.service'; import { provideRouter } from '@angular/router'; import { signal } from '@angular/core'; import { Auth } from '@app/shared/models/auth'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; describe('NavBarComponent', () => { let component: NavBarComponent; diff --git a/src/app/shared/components/user-avatar-form/user-avatar-form.component.spec.ts b/src/app/shared/components/user-avatar-form/user-avatar-form.component.spec.ts index 597fac9..be70421 100644 --- a/src/app/shared/components/user-avatar-form/user-avatar-form.component.spec.ts +++ b/src/app/shared/components/user-avatar-form/user-avatar-form.component.spec.ts @@ -4,7 +4,8 @@ import { UserAvatarFormComponent } from './user-avatar-form.component'; import { provideRouter } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { AuthService } from '@app/core/services/authentication/auth.service'; -import { UserService } from '@app/core/services/user/user.service'; +import { UserRepository } from '@app/domain/users/user.repository'; +import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; describe('UserAvatarFormComponent', () => { let component: UserAvatarFormComponent; @@ -12,7 +13,7 @@ describe('UserAvatarFormComponent', () => { let mockToastrService: Partial; let mockAuthService: Partial; - let mockUserService: Partial; + let mockUserRepo: UserRepository; beforeEach(async () => { mockToastrService = { @@ -25,19 +26,18 @@ describe('UserAvatarFormComponent', () => { updateUser: jest.fn(), }; - mockUserService = { - updateUser: jest.fn().mockReturnValue({ - subscribe: jest.fn(), - }), + mockUserRepo = { + update: jest.fn(), + getUserById: jest.fn(), }; await TestBed.configureTestingModule({ imports: [UserAvatarFormComponent], providers: [ provideRouter([]), + { provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepo }, { provide: ToastrService, useValue: mockToastrService }, { provide: AuthService, useValue: mockAuthService }, - { provide: UserService, useValue: mockUserService }, ], }).compileComponents(); diff --git a/src/app/shared/components/user-avatar-form/user-avatar-form.component.ts b/src/app/shared/components/user-avatar-form/user-avatar-form.component.ts index 259c31f..36dc341 100644 --- a/src/app/shared/components/user-avatar-form/user-avatar-form.component.ts +++ b/src/app/shared/components/user-avatar-form/user-avatar-form.component.ts @@ -1,11 +1,12 @@ -import { Component, inject, Input, output } from '@angular/core'; -import { User } from '@app/shared/models/user'; +import { Component, effect, inject, Input, output } from '@angular/core'; +import { User } from '@app/domain/users/user.model'; import { ReactiveFormsModule } from '@angular/forms'; import { AuthService } from '@app/core/services/authentication/auth.service'; -import { UserService } from '@app/core/services/user/user.service'; import { environment } from '@env/environment'; import { NgClass } from '@angular/common'; import { ToastrService } from 'ngx-toastr'; +import { UserFacade } from '@app/ui/users/user.facade'; +import { ActionType } from '@app/domain/action-type.util'; @Component({ selector: 'app-user-avatar-form', @@ -17,47 +18,42 @@ import { ToastrService } from 'ngx-toastr'; export class UserAvatarFormComponent { private readonly toastrService = inject(ToastrService); protected readonly environment = environment; + private readonly facade = inject(UserFacade); @Input({ required: true }) user: User | undefined = undefined; onFormSubmitted = output(); - private userService = inject(UserService); private authService = inject(AuthService); file: File | null = null; // Variable to store file imagePreviewUrl: string | null = null; // URL for image preview + protected readonly loading = this.facade.loading; + protected readonly error = this.facade.error; + + constructor() { + let message = ''; + + effect(() => { + if (!this.loading().isLoading) { + switch (this.loading().action) { + case ActionType.UPDATE: + this.authService.updateUser(); + message = `Votre photo de profile a bien été modifier !`; + this.customToast(ActionType.UPDATE, message); + break; + } + } + }); + } + onUserAvatarFormSubmit() { if (this.file != null) { const formData = new FormData(); formData.append('avatar', this.file); // "avatar" est le nom du champ dans PocketBase - this.userService.updateUser(this.user?.id!, formData).subscribe({ - next: (value) => { - this.authService.updateUser(); + this.facade.update(this.user?.id!, formData as Partial); - this.toastrService.success( - `Votre photo de profile a bien été modifier !`, - `Mise à jour`, - { - closeButton: true, - progressAnimation: 'decreasing', - progressBar: true, - } - ); - }, - error: (error) => { - this.toastrService.error( - `Une erreur est survenue lors de la mise à jour de votre photo de profile !`, - `Erreur`, - { - closeButton: true, - progressAnimation: 'decreasing', - progressBar: true, - } - ); - }, - }); this.onFormSubmitted.emit(''); } } @@ -77,4 +73,29 @@ export class UserAvatarFormComponent { }; 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, + } + ); + } } diff --git a/src/app/shared/components/user-form/user-form.component.spec.ts b/src/app/shared/components/user-form/user-form.component.spec.ts index 98ae8e4..d57c925 100644 --- a/src/app/shared/components/user-form/user-form.component.spec.ts +++ b/src/app/shared/components/user-form/user-form.component.spec.ts @@ -3,9 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserFormComponent } from './user-form.component'; import { ToastrService } from 'ngx-toastr'; import { AuthService } from '@app/core/services/authentication/auth.service'; -import { UserService } from '@app/core/services/user/user.service'; import { provideRouter } from '@angular/router'; import { FormBuilder } from '@angular/forms'; +import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; +import { UserRepository } from '@app/domain/users/user.repository'; describe('UserFormComponent', () => { let component: UserFormComponent; @@ -13,7 +14,7 @@ describe('UserFormComponent', () => { let mockToastrService: Partial; let mockAuthService: Partial; - let mockUserService: Partial; + let mockUserRepo: UserRepository; beforeEach(async () => { mockToastrService = { @@ -26,10 +27,9 @@ describe('UserFormComponent', () => { updateUser: jest.fn(), }; - mockUserService = { - updateUser: jest.fn().mockReturnValue({ - subscribe: jest.fn(), - }), + mockUserRepo = { + update: jest.fn(), + getUserById: jest.fn(), }; await TestBed.configureTestingModule({ @@ -37,9 +37,9 @@ describe('UserFormComponent', () => { providers: [ provideRouter([]), FormBuilder, + { provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepo }, { provide: ToastrService, useValue: mockToastrService }, { provide: AuthService, useValue: mockAuthService }, - { provide: UserService, useValue: mockUserService }, ], }).compileComponents(); diff --git a/src/app/shared/components/user-form/user-form.component.ts b/src/app/shared/components/user-form/user-form.component.ts index 55da06f..9b1f2e3 100644 --- a/src/app/shared/components/user-form/user-form.component.ts +++ b/src/app/shared/components/user-form/user-form.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, Input, OnInit, output } from '@angular/core'; +import { Component, effect, inject, Input, OnInit, output } from '@angular/core'; import { FormBuilder, FormControl, @@ -6,33 +6,51 @@ import { ReactiveFormsModule, Validators, } from '@angular/forms'; -import { User } from '@app/shared/models/user'; -import { NgClass } from '@angular/common'; -import { UserService } from '@app/core/services/user/user.service'; +import { User } from '@app/domain/users/user.model'; import { AuthService } from '@app/core/services/authentication/auth.service'; import { UntilDestroy } from '@ngneat/until-destroy'; import { ToastrService } from 'ngx-toastr'; +import { UserFacade } from '@app/ui/users/user.facade'; +import { ActionType } from '@app/domain/action-type.util'; @Component({ selector: 'app-user-form', standalone: true, - imports: [ReactiveFormsModule, NgClass], + imports: [ReactiveFormsModule], templateUrl: './user-form.component.html', styleUrl: './user-form.component.scss', }) @UntilDestroy() export class UserFormComponent implements OnInit { private readonly toastrService = inject(ToastrService); + private readonly facade = inject(UserFacade); @Input({ required: true }) user: User | undefined = undefined; onFormSubmitted = output(); - private userService = inject(UserService); private authService = inject(AuthService); private fb = inject(FormBuilder); protected userForm!: FormGroup; + protected readonly loading = this.facade.loading; + protected readonly error = this.facade.error; + + constructor() { + let message = ''; + + effect(() => { + if (!this.loading().isLoading) { + switch (this.loading().action) { + case ActionType.UPDATE: + this.authService.updateUser(); + message = `Vos informations personnelles ont bien été modifier !`; + this.customToast(ActionType.UPDATE, message); + break; + } + } + }); + } ngOnInit(): void { this.userForm = this.fb.group({ firstname: new FormControl(this.user?.name?.split(' ').slice(0, -1).join(' ') ?? '', [ @@ -53,20 +71,33 @@ export class UserFormComponent implements OnInit { name: this.userForm.getRawValue()!.firstname! + ' ' + this.userForm.getRawValue()!.name!, } as User; - this.userService.updateUser(this.user?.id!, data).subscribe((value) => { - this.authService.updateUser(); + this.facade.update(this.user?.id!, data); - this.toastrService.success( - `Vos informations personnelles ont bien été modifier !`, - `Mise à jour`, + this.onFormSubmitted.emit(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.onFormSubmitted.emit(data); + this.toastrService.success( + `${message}`, + `${action === ActionType.UPDATE ? 'Mise à jour' : ''}`, + { + closeButton: true, + progressAnimation: 'decreasing', + progressBar: true, + } + ); } } diff --git a/src/app/shared/components/user-password-form/user-password-form.component.ts b/src/app/shared/components/user-password-form/user-password-form.component.ts index 797722f..54cfce1 100644 --- a/src/app/shared/components/user-password-form/user-password-form.component.ts +++ b/src/app/shared/components/user-password-form/user-password-form.component.ts @@ -1,7 +1,6 @@ import { Component, inject, Input, output } from '@angular/core'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; import { FormBuilder, FormControl, Validators } from '@angular/forms'; -import { UserService } from '@app/core/services/user/user.service'; import { AuthService } from '@app/core/services/authentication/auth.service'; @Component({ @@ -15,7 +14,6 @@ export class UserPasswordFormComponent { @Input({ required: true }) user: User | undefined = undefined; onFormSubmitted = output(); - private userService = inject(UserService); private authService = inject(AuthService); private fb = inject(FormBuilder); diff --git a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.html b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.html index 60fd723..c3cf8f5 100644 --- a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.html +++ b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.html @@ -1,7 +1,7 @@ -@if (user != undefined) { +@if (user() != undefined) { @@ -33,18 +33,18 @@
- @if (user.avatar) { + @if (user().avatar) { {{ user.username }} } @else { {{ user.username }} } @@ -52,17 +52,17 @@
- @if (user.name) { + @if (user().name) {

- {{ user.name }} + {{ user().name }}

- } @else if (user.username) { + } @else if (user().username) {

- {{ user.username }} + {{ user().username }}

} @else {

Non mentionné

diff --git a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.spec.ts b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.spec.ts index b94a1e3..a6b44fe 100644 --- a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.spec.ts +++ b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.spec.ts @@ -1,14 +1,40 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { VerticalProfileItemComponent } from './vertical-profile-item.component'; +import { UserRepository } from '@app/domain/users/user.repository'; +import { provideRouter } from '@angular/router'; +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 { SectorRepository } from '@app/domain/sectors/sector.repository'; +import { Sector } from '@app/domain/sectors/sector.model'; describe('VerticalProfileItemComponent', () => { let component: VerticalProfileItemComponent; let fixture: ComponentFixture; + let mockUserRepo: UserRepository; + let mockSectorRepo: SectorRepository; + beforeEach(async () => { + mockUserRepo = { + update: jest.fn().mockReturnValue(of({} as User)), + getUserById: jest.fn().mockReturnValue(of({} as User)), + }; + + mockSectorRepo = { + list: jest.fn().mockReturnValue(of({} as Sector)), + getOne: jest.fn().mockReturnValue(of({} as Sector)), + }; + await TestBed.configureTestingModule({ imports: [VerticalProfileItemComponent], + providers: [ + provideRouter([]), + { provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepo }, + { provide: SECTOR_REPOSITORY_TOKEN, useValue: mockSectorRepo }, + ], }).compileComponents(); fixture = TestBed.createComponent(VerticalProfileItemComponent); diff --git a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.ts b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.ts index 651894a..3f89955 100644 --- a/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.ts +++ b/src/app/shared/components/vertical-profile-item/vertical-profile-item.component.ts @@ -1,12 +1,11 @@ import { Component, inject, Input, OnInit } from '@angular/core'; import { Router, RouterLink } from '@angular/router'; -import { UserService } from '@app/core/services/user/user.service'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { User } from '@app/shared/models/user'; import { ChipsComponent } from '@app/shared/components/chips/chips.component'; import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component'; import { environment } from '@env/environment'; import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; +import { UserFacade } from '@app/ui/users/user.facade'; @Component({ selector: 'app-vertical-profile-item', @@ -19,14 +18,14 @@ import { ProfileViewModel } from '@app/ui/profiles/profile.presenter.model'; export class VerticalProfileItemComponent implements OnInit { @Input({ required: true }) profile: ProfileViewModel = {} as ProfileViewModel; protected router = inject(Router); - protected userService = inject(UserService); + private readonly facade = inject(UserFacade); - protected user: User | undefined = undefined; + protected user = this.facade.user; + protected readonly loading = this.facade.loading; + protected readonly error = this.facade.error; ngOnInit(): void { - this.userService - .getUserById(this.profile.utilisateur) - .subscribe((value) => (this.user = value)); + this.facade.loadOne(this.profile.utilisateur); } protected readonly environment = environment; diff --git a/src/app/shared/features/login/login.component.ts b/src/app/shared/features/login/login.component.ts index 902258c..ddbdcf2 100644 --- a/src/app/shared/features/login/login.component.ts +++ b/src/app/shared/features/login/login.component.ts @@ -4,7 +4,7 @@ import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angu import { AuthService } from '@app/core/services/authentication/auth.service'; import { LoginDto } from '@app/shared/models/login-dto'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; import { ToastrService } from 'ngx-toastr'; import { ProgressBarModule } from 'primeng/progressbar'; diff --git a/src/app/shared/features/update-user/update-user.component.ts b/src/app/shared/features/update-user/update-user.component.ts index f6a0e81..997e3ee 100644 --- a/src/app/shared/features/update-user/update-user.component.ts +++ b/src/app/shared/features/update-user/update-user.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; import { UserFormComponent } from '@app/shared/components/user-form/user-form.component'; import { UserAvatarFormComponent } from '@app/shared/components/user-avatar-form/user-avatar-form.component'; diff --git a/src/app/shared/models/auth.ts b/src/app/shared/models/auth.ts index 6de6384..c5e92cd 100644 --- a/src/app/shared/models/auth.ts +++ b/src/app/shared/models/auth.ts @@ -1,4 +1,4 @@ -import { User } from '@app/shared/models/user'; +import { User } from '@app/domain/users/user.model'; export interface Auth { isValid: boolean; diff --git a/src/app/ui/users/user.facade.ts b/src/app/ui/users/user.facade.ts new file mode 100644 index 0000000..1a19096 --- /dev/null +++ b/src/app/ui/users/user.facade.ts @@ -0,0 +1,63 @@ +import { inject, Injectable, signal } from '@angular/core'; +import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token'; +import { GetUserUseCase } from '@app/usecase/users/get-user.usecase'; +import { UpdateUserUseCase } from '@app/usecase/users/update-user.usecase'; +import { LoaderAction } from '@app/domain/loader-action.util'; +import { ActionType } from '@app/domain/action-type.util'; +import { ErrorResponse } from '@app/domain/error-response.util'; +import { UserViewModel } from '@app/ui/users/user.presenter.model'; + +@Injectable({ + providedIn: 'root', +}) +export class UserFacade { + private readonly userRepository = inject(USER_REPOSITORY_TOKEN); + + private readonly getUseCase = new GetUserUseCase(this.userRepository); + private readonly updateUseCase = new UpdateUserUseCase(this.userRepository); + + readonly user = signal({} as UserViewModel); + readonly users = signal([]); + readonly loading = signal({ isLoading: false, action: ActionType.NONE }); + readonly error = signal({ + action: ActionType.NONE, + hasError: false, + message: null, + }); + + loadOne(userId: string) { + this.handleError(ActionType.READ, false, null, true); + this.getUseCase.execute(userId).subscribe({ + next: (user) => { + this.user.set(user); + this.handleError(ActionType.READ, false, null, false); + }, + error: (err) => { + this.handleError(ActionType.READ, false, err, false); + }, + }); + } + + update(userId: string, user: Partial) { + this.handleError(ActionType.UPDATE, false, null, true); + this.updateUseCase.execute(userId, user).subscribe({ + next: (user) => { + this.user.set(user); + this.handleError(ActionType.UPDATE, false, null, false); + }, + error: (err) => { + this.handleError(ActionType.UPDATE, false, err, false); + }, + }); + } + + private handleError( + action: ActionType = ActionType.NONE, + hasError: boolean, + message: string | null = null, + isLoading = false + ) { + this.error.set({ action, hasError, message }); + this.loading.set({ action, isLoading }); + } +} diff --git a/src/app/ui/users/user.presenter.model.ts b/src/app/ui/users/user.presenter.model.ts new file mode 100644 index 0000000..5a6cc05 --- /dev/null +++ b/src/app/ui/users/user.presenter.model.ts @@ -0,0 +1,9 @@ +export interface UserViewModel { + id: string; + username: string; + verified: boolean; + emailVisibility: boolean; + email: string; + name: string; + avatar: string; +} diff --git a/src/app/ui/users/user.presenter.ts b/src/app/ui/users/user.presenter.ts new file mode 100644 index 0000000..ec01e15 --- /dev/null +++ b/src/app/ui/users/user.presenter.ts @@ -0,0 +1,22 @@ +import { UserViewModel } from '@app/ui/users/user.presenter.model'; +import { User } from '@app/domain/users/user.model'; + +export class UserPresenter { + constructor() {} + + toViewModel(user: User): UserViewModel { + return { + id: user.id, + username: user.username, + verified: user.verified, + emailVisibility: user.emailVisibility, + email: user.email, + name: user.name, + avatar: user.avatar, + }; + } + + toViewModels(users: User[]): UserViewModel[] { + return users.map(this.toViewModel); + } +} diff --git a/src/app/usecase/users/get-user.usecase.ts b/src/app/usecase/users/get-user.usecase.ts new file mode 100644 index 0000000..1839c26 --- /dev/null +++ b/src/app/usecase/users/get-user.usecase.ts @@ -0,0 +1,8 @@ +import { UserRepository } from '@app/domain/users/user.repository'; + +export class GetUserUseCase { + constructor(private readonly repo: UserRepository) {} + execute(userId: string) { + return this.repo.getUserById(userId); + } +} diff --git a/src/app/usecase/users/update-user.usecase.ts b/src/app/usecase/users/update-user.usecase.ts new file mode 100644 index 0000000..54571ed --- /dev/null +++ b/src/app/usecase/users/update-user.usecase.ts @@ -0,0 +1,9 @@ +import { UserRepository } from '@app/domain/users/user.repository'; +import { User } from '@app/domain/users/user.model'; + +export class UpdateUserUseCase { + constructor(private readonly repo: UserRepository) {} + execute(userId: string, user: Partial) { + return this.repo.update(userId, user); + } +}