refacto
This commit is contained in:
@@ -10,14 +10,13 @@ describe('AppComponent', () => {
|
|||||||
let component: AppComponent;
|
let component: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
|
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
let mockProfileRepo: jest.Mocked<ProfileRepository>;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-r
|
|||||||
describe('authGuard', () => {
|
describe('authGuard', () => {
|
||||||
let mockRouter: Partial<Router>;
|
let mockRouter: Partial<Router>;
|
||||||
|
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
let mockProfileRepo: jest.Mocked<ProfileRepository>;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
|
|
||||||
const executeGuard: CanActivateFn = (...guardParameters) =>
|
const executeGuard: CanActivateFn = (...guardParameters) =>
|
||||||
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
||||||
@@ -24,7 +24,6 @@ describe('authGuard', () => {
|
|||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -57,55 +57,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Avatar et info principale -->
|
<!-- Avatar et info principale -->
|
||||||
<div class="relative -mt-16 md:-mt-20 px-4 md:px-8 pb-6">
|
<div class="relative -mt-16 md:-mt-22 px-4 md:px-8 pb-6">
|
||||||
<div class="flex flex-col md:flex-row items-center md:items-end gap-4 md:gap-6">
|
<div class="flex flex-col md:flex-row items-center md:items-end gap-4 md:gap-6">
|
||||||
<!-- Avatar avec bouton edit -->
|
<!-- Avatar avec bouton edit -->
|
||||||
<div class="relative group animate-slide-up">
|
@if (user() != undefined) {
|
||||||
<div
|
<div class="relative group animate-slide-up">
|
||||||
class="w-28 h-28 md:w-36 md:h-36 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-1 shadow-2xl group-hover:scale-105 transition-transform duration-300"
|
<div
|
||||||
>
|
class="w-28 h-28 md:w-36 md:h-36 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-1 shadow-2xl group-hover:scale-105 transition-transform duration-300"
|
||||||
<div class="w-full h-full rounded-full overflow-hidden bg-white">
|
>
|
||||||
@if (user().avatar) {
|
<div class="w-full h-full rounded-full overflow-hidden bg-white">
|
||||||
<img
|
@if (user()!.avatar) {
|
||||||
alt="{{ user().username }}"
|
<img
|
||||||
class="object-cover w-full h-full"
|
alt="{{ user()!.username }}"
|
||||||
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{
|
class="object-cover w-full h-full"
|
||||||
user().avatar
|
src="{{ environment.baseUrl }}/api/files/users/{{ user()!.id }}/{{
|
||||||
}}"
|
user()!.avatar
|
||||||
/>
|
}}"
|
||||||
} @else {
|
/>
|
||||||
<img
|
} @else {
|
||||||
alt="{{ user().username }}"
|
<img
|
||||||
class="object-cover w-full h-full"
|
alt="{{ user()!.username }}"
|
||||||
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user().username }}"
|
class="object-cover w-full h-full"
|
||||||
/>
|
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{
|
||||||
}
|
user()!.username
|
||||||
|
}}"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<!-- Nom et titre -->
|
<!-- Nom et titre -->
|
||||||
<div
|
@if (user() != undefined) {
|
||||||
class="flex-1 text-center md:text-left mb-4 md:mb-0 animate-slide-up animation-delay-100"
|
<div
|
||||||
>
|
class="flex-1 text-center md:text-left mb-4 mt-4 md:mb-0 animate-slide-up animation-delay-100"
|
||||||
@if (user().name) {
|
>
|
||||||
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
@if (user()!.name) {
|
||||||
{{ user().name }}
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
||||||
</h1>
|
{{ user()!.name }}
|
||||||
} @else if (user().username) {
|
</h1>
|
||||||
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
} @else if (user()!.username) {
|
||||||
{{ user().username }}
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
||||||
</h1>
|
{{ user()!.username }}
|
||||||
} @else {
|
</h1>
|
||||||
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
} @else {
|
||||||
{{ user().email }}
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-1">
|
||||||
</h1>
|
{{ user()!.email }}
|
||||||
}
|
</h1>
|
||||||
|
}
|
||||||
|
|
||||||
<p class="text-lg md:text-xl text-indigo-600 dark:text-indigo-400 font-semibold">
|
<p class="text-lg md:text-xl text-indigo-600 dark:text-indigo-400 font-semibold">
|
||||||
{{ profile().profession | uppercase }}
|
{{ profile().profession | uppercase }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,30 +121,32 @@
|
|||||||
<!-- Sidebar - Informations -->
|
<!-- Sidebar - Informations -->
|
||||||
<aside class="lg:col-span-1 space-y-6 animate-slide-up animation-delay-200">
|
<aside class="lg:col-span-1 space-y-6 animate-slide-up animation-delay-200">
|
||||||
<!-- Card Apropos -->
|
<!-- Card Apropos -->
|
||||||
<div
|
@if (profile().apropos) {
|
||||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300"
|
<div
|
||||||
>
|
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300"
|
||||||
<h3
|
|
||||||
class="text-lg font-bold text-gray-900 dark:text-white mb-3 flex items-center gap-2"
|
|
||||||
>
|
>
|
||||||
<svg
|
<h3
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
class="text-lg font-bold text-gray-900 dark:text-white mb-3 flex items-center gap-2"
|
||||||
class="h-6 w-6 text-indigo-500"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
fill-rule="evenodd"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
class="h-6 w-6 text-indigo-500"
|
||||||
clip-rule="evenodd"
|
viewBox="0 0 20 20"
|
||||||
/>
|
fill="currentColor"
|
||||||
</svg>
|
>
|
||||||
À propos
|
<path
|
||||||
</h3>
|
fill-rule="evenodd"
|
||||||
<p class="text-gray-700 dark:text-gray-300 leading-relaxed text-base">
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||||
{{ profile().apropos }}
|
clip-rule="evenodd"
|
||||||
</p>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
|
À propos
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-700 dark:text-gray-300 leading-relaxed text-base">
|
||||||
|
{{ profile().apropos }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Card Bio -->
|
<!-- Card Bio -->
|
||||||
<div
|
<div
|
||||||
@@ -318,7 +326,11 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@switch (menu().toLowerCase()) {
|
@switch (menu().toLowerCase()) {
|
||||||
@case ('home') {
|
@case ('home') {
|
||||||
<app-update-user [user]="user()" />
|
@if (!userLoading().isLoading) {
|
||||||
|
<app-update-user [user]="user()" />
|
||||||
|
} @else {
|
||||||
|
<app-loading message="Chargement encours..." />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@case ('projects') {
|
@case ('projects') {
|
||||||
<app-my-profile-project-list
|
<app-my-profile-project-list
|
||||||
@@ -334,7 +346,11 @@
|
|||||||
<app-pdf-viewer [profile]="profile()" />
|
<app-pdf-viewer [profile]="profile()" />
|
||||||
}
|
}
|
||||||
@default {
|
@default {
|
||||||
<app-update-user [user]="user()" />
|
@if (!userLoading().isLoading) {
|
||||||
|
<app-update-user [user]="user()" />
|
||||||
|
} @else {
|
||||||
|
<app-loading message="Chargement encours..." />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ describe('MyProfileComponent', () => {
|
|||||||
let fixture: ComponentFixture<MyProfileComponent>;
|
let fixture: ComponentFixture<MyProfileComponent>;
|
||||||
|
|
||||||
let mockProfileRepo: ProfileRepository;
|
let mockProfileRepo: ProfileRepository;
|
||||||
let mockUserRepo: UserRepository;
|
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: Partial<ToastrService>;
|
||||||
|
let mockUserRepo: Partial<UserRepository>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockProfileRepo = {
|
mockProfileRepo = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, computed, inject, OnInit, signal } from '@angular/core';
|
import { Component, inject, OnInit, signal } from '@angular/core';
|
||||||
import { ActivatedRoute, RouterOutlet } from '@angular/router';
|
import { ActivatedRoute, RouterOutlet } from '@angular/router';
|
||||||
import { User } from '@app/domain/users/user.model';
|
import { User } from '@app/domain/users/user.model';
|
||||||
import { Location, UpperCasePipe } from '@angular/common';
|
import { Location, UpperCasePipe } from '@angular/common';
|
||||||
@@ -11,6 +11,8 @@ import { MyProfileProjectListComponent } from '@app/shared/components/my-profile
|
|||||||
import { MyProfileUpdateFormComponent } from '@app/shared/components/my-profile-update-form/my-profile-update-form.component';
|
import { MyProfileUpdateFormComponent } from '@app/shared/components/my-profile-update-form/my-profile-update-form.component';
|
||||||
import { PdfViewerComponent } from '@app/shared/features/pdf-viewer/pdf-viewer.component';
|
import { PdfViewerComponent } from '@app/shared/features/pdf-viewer/pdf-viewer.component';
|
||||||
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
||||||
|
import { UserFacade } from '@app/ui/users/user.facade';
|
||||||
|
import { LoadingComponent } from '@app/shared/components/loading/loading.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-profile',
|
selector: 'app-my-profile',
|
||||||
@@ -24,7 +26,9 @@ import { ProfileFacade } from '@app/ui/profiles/profile.facade';
|
|||||||
MyProfileUpdateFormComponent,
|
MyProfileUpdateFormComponent,
|
||||||
PdfViewerComponent,
|
PdfViewerComponent,
|
||||||
UpperCasePipe,
|
UpperCasePipe,
|
||||||
|
LoadingComponent,
|
||||||
],
|
],
|
||||||
|
providers: [UserFacade],
|
||||||
templateUrl: './my-profile.component.html',
|
templateUrl: './my-profile.component.html',
|
||||||
styleUrl: './my-profile.component.scss',
|
styleUrl: './my-profile.component.scss',
|
||||||
})
|
})
|
||||||
@@ -33,17 +37,14 @@ export class MyProfileComponent implements OnInit {
|
|||||||
protected readonly environment = environment;
|
protected readonly environment = environment;
|
||||||
protected menu = signal<string>('home');
|
protected menu = signal<string>('home');
|
||||||
|
|
||||||
protected myProfileQrCode = `${environment.production}`;
|
|
||||||
|
|
||||||
protected location = inject(Location);
|
protected location = inject(Location);
|
||||||
protected readonly route = inject(ActivatedRoute);
|
protected readonly route = inject(ActivatedRoute);
|
||||||
|
|
||||||
protected extraData: { user: User } = this.route.snapshot.data['user'];
|
protected extraData: { user: User } = this.route.snapshot.data['user'];
|
||||||
|
|
||||||
protected user = computed(() => {
|
private readonly userFacade = inject(UserFacade);
|
||||||
if (this.extraData != undefined) return this.extraData.user;
|
protected user = this.userFacade.user;
|
||||||
return {} as User;
|
protected readonly userLoading = this.userFacade.loading;
|
||||||
});
|
|
||||||
|
|
||||||
private readonly profileFacade = new ProfileFacade();
|
private readonly profileFacade = new ProfileFacade();
|
||||||
protected profile = this.profileFacade.profile;
|
protected profile = this.profileFacade.profile;
|
||||||
@@ -51,7 +52,9 @@ export class MyProfileComponent implements OnInit {
|
|||||||
protected readonly error = this.profileFacade.error;
|
protected readonly error = this.profileFacade.error;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.myProfileQrCode = `${this.myProfileQrCode}/profiles/${this.user().id}`;
|
if (this.extraData != undefined) {
|
||||||
this.profileFacade.loadOne(this.user().id);
|
this.profileFacade.loadOne(this.extraData.user.id);
|
||||||
|
this.userFacade.loadOne(this.extraData.user.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,30 +207,32 @@
|
|||||||
<!-- Colonne droite - À propos et Projets -->
|
<!-- Colonne droite - À propos et Projets -->
|
||||||
<div class="lg:col-span-2 space-y-6 animate-slide-up animation-delay-300">
|
<div class="lg:col-span-2 space-y-6 animate-slide-up animation-delay-300">
|
||||||
<!-- Card À propos -->
|
<!-- Card À propos -->
|
||||||
<div
|
@if (profile().apropos) {
|
||||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 md:p-8 hover:shadow-xl transition-shadow duration-300"
|
<div
|
||||||
>
|
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 md:p-8 hover:shadow-xl transition-shadow duration-300"
|
||||||
<h2
|
|
||||||
class="text-2xl font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2"
|
|
||||||
>
|
>
|
||||||
<svg
|
<h2
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
class="text-2xl font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2"
|
||||||
class="h-6 w-6 text-indigo-500"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
fill-rule="evenodd"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
class="h-6 w-6 text-indigo-500"
|
||||||
clip-rule="evenodd"
|
viewBox="0 0 20 20"
|
||||||
/>
|
fill="currentColor"
|
||||||
</svg>
|
>
|
||||||
À propos
|
<path
|
||||||
</h2>
|
fill-rule="evenodd"
|
||||||
<p class="text-gray-700 dark:text-gray-300 leading-relaxed text-base">
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||||
{{ profile().apropos }}
|
clip-rule="evenodd"
|
||||||
</p>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
|
À propos
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-700 dark:text-gray-300 leading-relaxed text-base">
|
||||||
|
{{ profile().apropos }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Card Projets -->
|
<!-- Card Projets -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
|||||||
let component: MyProfileUpdateProjectFormComponent;
|
let component: MyProfileUpdateProjectFormComponent;
|
||||||
let fixture: ComponentFixture<MyProfileUpdateProjectFormComponent>;
|
let fixture: ComponentFixture<MyProfileUpdateProjectFormComponent>;
|
||||||
|
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: jest.Mocked<Partial<ToastrService>>;
|
||||||
let mockProjectRepository: jest.Mocked<ProjectRepository>;
|
let mockProjectRepository: jest.Mocked<Partial<ProjectRepository>>;
|
||||||
|
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
let mockProfileRepo: jest.Mocked<ProfileRepository>;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockToastrService = {
|
mockToastrService = {
|
||||||
@@ -38,7 +38,6 @@ describe('MyProfileUpdateProjectFormComponent', () => {
|
|||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import { ProfileRepository } from '@app/domain/profiles/profile.repository';
|
|||||||
describe('NavBarComponent', () => {
|
describe('NavBarComponent', () => {
|
||||||
let component: NavBarComponent;
|
let component: NavBarComponent;
|
||||||
let fixture: ComponentFixture<NavBarComponent>;
|
let fixture: ComponentFixture<NavBarComponent>;
|
||||||
let mockThemeService: Partial<ThemeService>;
|
let mockThemeService: jest.Mocked<Partial<ThemeService>>;
|
||||||
|
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
let mockProfileRepo: jest.Mocked<ProfileRepository>;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
|
|
||||||
const user: User = {
|
const user: User = {
|
||||||
id: 'adbc123',
|
id: 'adbc123',
|
||||||
@@ -41,7 +41,6 @@ describe('NavBarComponent', () => {
|
|||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Component, inject, Input } from '@angular/core';
|
import { Component, inject, Input } from '@angular/core';
|
||||||
import { JsonPipe } from '@angular/common';
|
|
||||||
import { ThemeService } from '@app/core/services/theme/theme.service';
|
import { ThemeService } from '@app/core/services/theme/theme.service';
|
||||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-reseaux',
|
selector: 'app-reseaux',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [JsonPipe],
|
imports: [],
|
||||||
templateUrl: './reseaux.component.html',
|
templateUrl: './reseaux.component.html',
|
||||||
styleUrl: './reseaux.component.scss',
|
styleUrl: './reseaux.component.scss',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<ng-template #content>
|
<ng-template #content>
|
||||||
@if (user) {
|
@if (user != undefined) {
|
||||||
<div
|
<div
|
||||||
class="w-40 h-40 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
|
class="w-40 h-40 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, effect, inject, Input, output, signal } from '@angular/core';
|
import { Component, effect, inject, Input, signal } from '@angular/core';
|
||||||
import { User } from '@app/domain/users/user.model';
|
import { User } from '@app/domain/users/user.model';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { environment } from '@env/environment';
|
import { environment } from '@env/environment';
|
||||||
@@ -7,21 +7,24 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { UserFacade } from '@app/ui/users/user.facade';
|
import { UserFacade } from '@app/ui/users/user.facade';
|
||||||
import { ActionType } from '@app/domain/action-type.util';
|
import { ActionType } from '@app/domain/action-type.util';
|
||||||
import { LoadingComponent } from '@app/shared/components/loading/loading.component';
|
import { LoadingComponent } from '@app/shared/components/loading/loading.component';
|
||||||
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { UserViewModel } from '@app/ui/users/user.presenter.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-avatar-form',
|
selector: 'app-user-avatar-form',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
providers: [UserFacade],
|
||||||
imports: [ReactiveFormsModule, NgClass, NgTemplateOutlet, LoadingComponent],
|
imports: [ReactiveFormsModule, NgClass, NgTemplateOutlet, LoadingComponent],
|
||||||
templateUrl: './user-avatar-form.component.html',
|
templateUrl: './user-avatar-form.component.html',
|
||||||
styleUrl: './user-avatar-form.component.scss',
|
styleUrl: './user-avatar-form.component.scss',
|
||||||
})
|
})
|
||||||
|
@UntilDestroy()
|
||||||
export class UserAvatarFormComponent {
|
export class UserAvatarFormComponent {
|
||||||
private readonly toastrService = inject(ToastrService);
|
private readonly toastrService = inject(ToastrService);
|
||||||
protected readonly environment = environment;
|
protected readonly environment = environment;
|
||||||
private readonly facade = inject(UserFacade);
|
private readonly facade = inject(UserFacade);
|
||||||
@Input({ required: true }) user: User | undefined = undefined;
|
|
||||||
|
|
||||||
onFormSubmitted = output<any>();
|
@Input({ required: true }) user: UserViewModel | undefined = undefined;
|
||||||
|
|
||||||
file: File | null = null; // Variable to store file
|
file: File | null = null; // Variable to store file
|
||||||
imagePreviewUrl: string | null = null; // URL for image preview
|
imagePreviewUrl: string | null = null; // URL for image preview
|
||||||
@@ -34,13 +37,13 @@ export class UserAvatarFormComponent {
|
|||||||
let message = '';
|
let message = '';
|
||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (!this.loading().isLoading && this.onSubmitted()) {
|
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 !`;
|
message = `Votre photo de profile a bien été modifier !`;
|
||||||
this.customToast(ActionType.UPDATE, message);
|
if (this.onSubmitted()) {
|
||||||
this.onSubmitted.set(false);
|
this.customToast(ActionType.UPDATE, message);
|
||||||
this.onFormSubmitted.emit('');
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,7 @@ export class UserAvatarFormComponent {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('avatar', this.file); // "avatar" est le nom du champ dans PocketBase
|
formData.append('avatar', this.file); // "avatar" est le nom du champ dans PocketBase
|
||||||
|
|
||||||
this.facade.update(this.user?.id!, formData as Partial<User>);
|
this.facade.update(this.user!.id, formData as Partial<User>);
|
||||||
this.onSubmitted.set(true);
|
this.onSubmitted.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, effect, inject, Input, OnInit, output, signal } from '@angular/core';
|
import { Component, effect, inject, Input, OnInit, signal } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FormBuilder,
|
FormBuilder,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -18,6 +18,7 @@ import { NgTemplateOutlet } from '@angular/common';
|
|||||||
selector: 'app-user-form',
|
selector: 'app-user-form',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ReactiveFormsModule, LoadingComponent, NgTemplateOutlet],
|
imports: [ReactiveFormsModule, LoadingComponent, NgTemplateOutlet],
|
||||||
|
providers: [UserFacade],
|
||||||
templateUrl: './user-form.component.html',
|
templateUrl: './user-form.component.html',
|
||||||
styleUrl: './user-form.component.scss',
|
styleUrl: './user-form.component.scss',
|
||||||
})
|
})
|
||||||
@@ -27,12 +28,12 @@ export class UserFormComponent implements OnInit {
|
|||||||
private readonly facade = inject(UserFacade);
|
private readonly facade = inject(UserFacade);
|
||||||
protected readonly ActionType = ActionType;
|
protected readonly ActionType = ActionType;
|
||||||
|
|
||||||
@Input({ required: true }) user: User | undefined = undefined;
|
@Input({ required: true }) userId: string | undefined = undefined;
|
||||||
onFormSubmitted = output<any>();
|
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
protected userForm!: FormGroup;
|
protected userForm!: FormGroup;
|
||||||
|
|
||||||
|
protected readonly user = this.facade.user;
|
||||||
protected readonly loading = this.facade.loading;
|
protected readonly loading = this.facade.loading;
|
||||||
protected readonly error = this.facade.error;
|
protected readonly error = this.facade.error;
|
||||||
protected onSubmitted = signal<boolean>(false);
|
protected onSubmitted = signal<boolean>(false);
|
||||||
@@ -41,23 +42,32 @@ export class UserFormComponent implements OnInit {
|
|||||||
let message = '';
|
let message = '';
|
||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (!this.loading().isLoading && this.onSubmitted()) {
|
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 !`;
|
message = `Vos informations personnelles ont bien été modifier !`;
|
||||||
this.customToast(ActionType.UPDATE, message);
|
if (this.onSubmitted()) {
|
||||||
this.onSubmitted.set(false);
|
this.customToast(ActionType.UPDATE, message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ActionType.READ:
|
||||||
|
this.userForm.setValue({
|
||||||
|
firstname: this.user().name.split(' ').slice(0, -1).join(' ') ?? '',
|
||||||
|
name: this.user().name.split(' ').slice(-1)[0] ?? '',
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.userId !== undefined) {
|
||||||
|
this.facade.loadOne(this.userId!);
|
||||||
|
}
|
||||||
|
|
||||||
this.userForm = this.fb.group({
|
this.userForm = this.fb.group({
|
||||||
firstname: new FormControl(this.user?.name?.split(' ').slice(0, -1).join(' ') ?? '', [
|
firstname: new FormControl('', [Validators.required]),
|
||||||
Validators.required,
|
name: new FormControl('', [Validators.required]),
|
||||||
]),
|
|
||||||
name: new FormControl(this.user?.name?.split(' ').slice(-1)[0] ?? '', [Validators.required]),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +82,7 @@ export class UserFormComponent implements OnInit {
|
|||||||
name: this.userForm.getRawValue()!.firstname! + ' ' + this.userForm.getRawValue()!.name!,
|
name: this.userForm.getRawValue()!.firstname! + ' ' + this.userForm.getRawValue()!.name!,
|
||||||
} as User;
|
} as User;
|
||||||
|
|
||||||
this.facade.update(this.user?.id!, data);
|
this.facade.update(this.userId!, data);
|
||||||
|
|
||||||
this.onFormSubmitted.emit(data);
|
|
||||||
this.onSubmitted.set(true);
|
this.onSubmitted.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { UserFacade } from '@app/ui/users/user.facade';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vertical-profile-item',
|
selector: 'app-vertical-profile-item',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
providers: [UserFacade],
|
||||||
imports: [ChipsComponent, ReseauxComponent, RouterLink],
|
imports: [ChipsComponent, ReseauxComponent, RouterLink],
|
||||||
templateUrl: './vertical-profile-item.component.html',
|
templateUrl: './vertical-profile-item.component.html',
|
||||||
styleUrl: './vertical-profile-item.component.scss',
|
styleUrl: './vertical-profile-item.component.scss',
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ describe('LoginComponent', () => {
|
|||||||
|
|
||||||
// Mocks des services
|
// Mocks des services
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: Partial<ToastrService>;
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
let mockProfileRepo: jest.Mocked<ProfileRepository>;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockToastrService = {
|
mockToastrService = {
|
||||||
@@ -29,7 +29,6 @@ describe('LoginComponent', () => {
|
|||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -43,10 +43,20 @@ export class LoginComponent {
|
|||||||
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) {
|
||||||
|
message = `Vous ne pouvez pas vous connecter sans valider la verification envoyé à cet adresse ${this.authResponse()?.record.email!}`;
|
||||||
|
this.toastrService.warning(`${message}`, `CONNEXION`, {
|
||||||
|
closeButton: true,
|
||||||
|
progressBar: true,
|
||||||
|
disableTimeOut: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.router
|
this.router
|
||||||
.navigate(['/my-profile'], { state: { user: this.authResponse()!.record } })
|
.navigate(['/my-profile'], { state: { user: this.authResponse()!.record } })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
message = ` Bienvenue parmi nous!`;
|
message = `Bienvenue parmi nous!`;
|
||||||
this.customToast(ActionType.READ, message);
|
this.customToast(ActionType.READ, message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ describe('RegisterComponent', () => {
|
|||||||
let component: RegisterComponent;
|
let component: RegisterComponent;
|
||||||
let fixture: ComponentFixture<RegisterComponent>;
|
let fixture: ComponentFixture<RegisterComponent>;
|
||||||
|
|
||||||
let mockToastrService: Partial<ToastrService>;
|
let mockToastrService: jest.Mocked<Partial<ToastrService>>;
|
||||||
let mockProfileRepo: ProfileRepository;
|
let mockProfileRepo: jest.Mocked<Partial<ProfileRepository>>;
|
||||||
let mockAuthRepository: jest.Mocked<AuthRepository>;
|
let mockAuthRepository: jest.Mocked<Partial<AuthRepository>>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockProfileRepo = {
|
mockProfileRepo = {
|
||||||
@@ -33,7 +33,6 @@ describe('RegisterComponent', () => {
|
|||||||
mockAuthRepository = {
|
mockAuthRepository = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
login: jest.fn(),
|
login: jest.fn(),
|
||||||
update: jest.fn(),
|
|
||||||
sendVerificationEmail: jest.fn(),
|
sendVerificationEmail: jest.fn(),
|
||||||
logout: jest.fn(),
|
logout: jest.fn(),
|
||||||
isAuthenticated: jest.fn(),
|
isAuthenticated: jest.fn(),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div
|
<div
|
||||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in animation-delay-100"
|
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in animation-delay-100"
|
||||||
>
|
>
|
||||||
<app-user-form [user]="user" />
|
<app-user-form [userId]="user!.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { UpdateUserComponent } from './update-user.component';
|
import { UpdateUserComponent } from './update-user.component';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { UserRepository } from '@app/domain/users/user.repository';
|
||||||
|
import { USER_REPOSITORY_TOKEN } from '@app/infrastructure/users/user-repository.token';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
describe('UpdateUserComponent', () => {
|
describe('UpdateUserComponent', () => {
|
||||||
let component: UpdateUserComponent;
|
let component: UpdateUserComponent;
|
||||||
let fixture: ComponentFixture<UpdateUserComponent>;
|
let fixture: ComponentFixture<UpdateUserComponent>;
|
||||||
|
|
||||||
|
let mockUserRepo: jest.Mocked<Partial<UserRepository>>;
|
||||||
|
let mockToastrService: jest.Mocked<Partial<ToastrService>>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
mockUserRepo = {
|
||||||
|
getUserById: jest.fn(),
|
||||||
|
};
|
||||||
|
mockToastrService = {
|
||||||
|
warning: jest.fn(),
|
||||||
|
success: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [UpdateUserComponent],
|
imports: [UpdateUserComponent],
|
||||||
|
providers: [
|
||||||
|
provideRouter([]),
|
||||||
|
{ provide: USER_REPOSITORY_TOKEN, useValue: mockUserRepo },
|
||||||
|
{ provide: ToastrService, useValue: mockToastrService },
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(UpdateUserComponent);
|
fixture = TestBed.createComponent(UpdateUserComponent);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { User } from '@app/domain/users/user.model';
|
|
||||||
import { UserFormComponent } from '@app/shared/components/user-form/user-form.component';
|
import { UserFormComponent } from '@app/shared/components/user-form/user-form.component';
|
||||||
import { UserAvatarFormComponent } from '@app/shared/components/user-avatar-form/user-avatar-form.component';
|
import { UserAvatarFormComponent } from '@app/shared/components/user-avatar-form/user-avatar-form.component';
|
||||||
|
import { UserViewModel } from '@app/ui/users/user.presenter.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-update-user',
|
selector: 'app-update-user',
|
||||||
@@ -11,5 +11,5 @@ import { UserAvatarFormComponent } from '@app/shared/components/user-avatar-form
|
|||||||
styleUrl: './update-user.component.scss',
|
styleUrl: './update-user.component.scss',
|
||||||
})
|
})
|
||||||
export class UpdateUserComponent {
|
export class UpdateUserComponent {
|
||||||
@Input({ required: true }) user: User | undefined = undefined;
|
@Input({ required: true }) user: UserViewModel | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export class ProfileFacade {
|
|||||||
|
|
||||||
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.execute(userId).subscribe({
|
||||||
next: (profile: Profile) => {
|
next: (profile: Profile) => {
|
||||||
this.profile.set(ProfilePresenter.toViewModel(profile));
|
this.profile.set(ProfilePresenter.toViewModel(profile));
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ 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';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable()
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class UserFacade {
|
export class UserFacade {
|
||||||
private readonly userRepository = inject(USER_REPOSITORY_TOKEN);
|
private readonly userRepository = inject(USER_REPOSITORY_TOKEN);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user