profiles => format clean archi

This commit is contained in:
styve Lioumba
2025-10-20 20:34:45 +02:00
parent 4191ac1ed0
commit ef02c6a537
171 changed files with 25748 additions and 23863 deletions

View File

@@ -1,83 +1,115 @@
<section class="text-gray-600">
<div class="container px-5 py-12 mx-auto flex flex-col">
<div class="lg:w-4/6 mx-auto">
<div class="rounded-lg min-h-56 max-h-64 overflow-hidden bg-cover bg-auth">
<div class="w-full flex justify-between items-center">
<a [routerLink]="['/profiles']">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10 ">
<title>Retour</title>
<path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z" clip-rule="evenodd" />
</svg>
</a>
@if (profile().estVerifier) {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class=" hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4">
<title>Profile verifier</title>
<path fill-rule="evenodd"
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"/>
</svg>
}
</div>
<!-- <img alt="{{user().username}}" class="object-cover object-center h-full w-full" src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user().username}}">-->
</div>
<div class="flex flex-col sm:flex-row mt-10">
<div class="sm:w-1/3 text-center sm:pr-8 sm:py-8">
<div class="w-20 h-20 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400">
@if (user().avatar) {
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
src="{{environment.baseUrl}}/api/files/users/{{user().id}}/{{user().avatar}}" loading="lazy">
} @else {
<img alt="{{user().username}}" class="object-cover object-center h-full w-full rounded-full"
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user().username}}" loading="lazy">
}
</div>
<div class="flex flex-col items-center text-center justify-center">
@if (user().name){
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().name}}</h2>
} @else if (user().username){
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().username}}</h2>
} @else {
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">{{user().email}}</h2>
}
<div class="w-12 h-1 bg-indigo-500 rounded mt-2 mb-4"></div>
@if (profile().bio) {
<p class="text-base dark:text-white">{{ profile().bio }}</p>
} @else {
<p class="text-base dark:text-white">Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes
compétences. Nhésitez pas à me contacter pour en savoir plus sur mon parcours et mes domaines
dintervention.</p>
}
@if(profile().secteur){
<div class="space-y-2 flex flex-col my-4">
<p class="text-base dark:text-white">Secteur</p>
<app-chips [sectorId]="profile().secteur"/>
</div>
}
@if (profile().reseaux){
<div class="space-y-2 flex flex-col my-4">
<p class="text-base dark:text-white">Réseaux</p>
<app-reseaux [reseaux]="profile().reseaux"/>
</div>
}
</div>
</div>
<div
class="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left flex-col flex space-y-2">
<h2 class="text-3xl font-extrabold text-black dark:text-white">{{profile().profession | uppercase}}</h2>
<p class="leading-relaxed text-lg mb-4 dark:text-white">{{profile().apropos}}</p>
<app-project-list [userProjectId]="profile().utilisateur"/>
</div>
</div>
</div>
</div>
</section>
<section class="text-gray-600">
<div class="container px-5 py-12 mx-auto flex flex-col">
<div class="lg:w-4/6 mx-auto">
<div class="rounded-lg min-h-56 max-h-64 overflow-hidden bg-cover bg-auth">
<div class="w-full flex justify-between items-center">
<a [routerLink]="['/profiles']">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="hover:text-gray-300 text-white size-6 w-5 h-5 sm:w-9 sm:h-9 mx-4 my-4 hover:w-10 hover:h-10"
>
<title>Retour</title>
<path
fill-rule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z"
clip-rule="evenodd"
/>
</svg>
</a>
@if (profile().estVerifier) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="hover:text-purple-300 size-6 text-purple-500 w-6 h-6 sm:w-11 sm:h-11 text-end mx-4 my-4"
>
<title>Profile verifier</title>
<path
fill-rule="evenodd"
d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clip-rule="evenodd"
/>
</svg>
}
</div>
<!-- <img alt="{{user().username}}" class="object-cover object-center h-full w-full" src="https://api.dicebear.com/9.x/adventurer/svg?seed={{user().username}}">-->
</div>
<div class="flex flex-col sm:flex-row mt-10">
<div class="sm:w-1/3 text-center sm:pr-8 sm:py-8">
<div
class="w-20 h-20 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
>
@if (user().avatar) {
<img
alt="{{ user().username }}"
class="object-cover object-center h-full w-full rounded-full"
src="{{ environment.baseUrl }}/api/files/users/{{ user().id }}/{{ user().avatar }}"
loading="lazy"
/>
} @else {
<img
alt="{{ user().username }}"
class="object-cover object-center h-full w-full rounded-full"
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user().username }}"
loading="lazy"
/>
}
</div>
<div class="flex flex-col items-center text-center justify-center">
@if (user().name) {
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
{{ user().name }}
</h2>
} @else if (user().username) {
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
{{ user().username }}
</h2>
} @else {
<h2 class="font-medium title-font mt-4 text-gray-900 text-lg dark:text-white">
{{ user().email }}
</h2>
}
<div class="w-12 h-1 bg-indigo-500 rounded mt-2 mb-4"></div>
@if (profile().bio) {
<p class="text-base dark:text-white">{{ profile().bio }}</p>
} @else {
<p class="text-base dark:text-white">
Je suis sur la plateforme Trouve Ton Profile pour partager mon expertise et mes
compétences. Nhésitez pas à me contacter pour en savoir plus sur mon parcours et
mes domaines dintervention.
</p>
}
@if (profile().secteur) {
<div class="space-y-2 flex flex-col my-4">
<p class="text-base dark:text-white">Secteur</p>
<app-chips [sectorId]="profile().secteur" />
</div>
}
@if (profile().reseaux) {
<div class="space-y-2 flex flex-col my-4">
<p class="text-base dark:text-white">Réseaux</p>
<app-reseaux [reseaux]="profile().reseaux" />
</div>
}
</div>
</div>
<div
class="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left flex-col flex space-y-2"
>
<h2 class="text-3xl font-extrabold text-black dark:text-white">
{{ profile().profession | uppercase }}
</h2>
<p class="leading-relaxed text-lg mb-4 dark:text-white">{{ profile().apropos }}</p>
<app-project-list [userProjectId]="profile().utilisateur" />
</div>
</div>
</div>
</div>
</section>

View File

@@ -1,29 +1,26 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ProfileDetailComponent} from './profile-detail.component';
import {provideRouter} from "@angular/router";
describe('ProfileDetailComponent', () => {
let component: ProfileDetailComponent;
let fixture: ComponentFixture<ProfileDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileDetailComponent],
providers:[
provideRouter([])
]
})
.compileComponents();
fixture = TestBed.createComponent(ProfileDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileDetailComponent } from './profile-detail.component';
import { provideRouter } from '@angular/router';
describe('ProfileDetailComponent', () => {
let component: ProfileDetailComponent;
let fixture: ComponentFixture<ProfileDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileDetailComponent],
providers: [provideRouter([])],
}).compileComponents();
fixture = TestBed.createComponent(ProfileDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,46 +1,43 @@
import {Component, computed, inject} from '@angular/core';
import {ActivatedRoute, RouterLink} from "@angular/router";
import {QRCodeModule} from "angularx-qrcode";
import {UpperCasePipe} from "@angular/common";
import {User} from "@app/shared/models/user";
import {Profile} from "@app/shared/models/profile";
import {ChipsComponent} from "@app/shared/components/chips/chips.component";
import {ReseauxComponent} from "@app/shared/components/reseaux/reseaux.component";
import {UntilDestroy} from "@ngneat/until-destroy";
import {ProjectListComponent} from "@app/shared/components/project-list/project-list.component";
import {environment} from "@env/environment";
@Component({
selector: 'app-profile-detail',
standalone: true,
imports: [
QRCodeModule,
ChipsComponent,
ReseauxComponent,
RouterLink,
UpperCasePipe,
ProjectListComponent
],
templateUrl: './profile-detail.component.html',
styleUrl: './profile-detail.component.scss'
})
@UntilDestroy()
export class ProfileDetailComponent {
protected readonly environment = environment;
private readonly route = inject(ActivatedRoute);
protected extraData: { user: User, profile: Profile } = this.route.snapshot.data['profile'];
protected user = computed(() => {
if (this.extraData != undefined) return this.extraData.user;
return {} as User;
});
protected profile = computed(() => {
if (this.extraData != undefined) return this.extraData.profile;
return {} as Profile;
});
}
import { Component, computed, inject } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { QRCodeModule } from 'angularx-qrcode';
import { UpperCasePipe } from '@angular/common';
import { User } from '@app/shared/models/user';
import { Profile } from '@app/shared/models/profile';
import { ChipsComponent } from '@app/shared/components/chips/chips.component';
import { ReseauxComponent } from '@app/shared/components/reseaux/reseaux.component';
import { UntilDestroy } from '@ngneat/until-destroy';
import { ProjectListComponent } from '@app/shared/components/project-list/project-list.component';
import { environment } from '@env/environment';
@Component({
selector: 'app-profile-detail',
standalone: true,
imports: [
QRCodeModule,
ChipsComponent,
ReseauxComponent,
RouterLink,
UpperCasePipe,
ProjectListComponent,
],
templateUrl: './profile-detail.component.html',
styleUrl: './profile-detail.component.scss',
})
@UntilDestroy()
export class ProfileDetailComponent {
protected readonly environment = environment;
private readonly route = inject(ActivatedRoute);
protected extraData: { user: User; profile: Profile } = this.route.snapshot.data['profile'];
protected user = computed(() => {
if (this.extraData != undefined) return this.extraData.user;
return {} as User;
});
protected profile = computed(() => {
if (this.extraData != undefined) return this.extraData.profile;
return {} as Profile;
});
}

View File

@@ -1,28 +1,19 @@
<section class="pb-10 relative">
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10 flex sm:flex-row flex-col space-y-2 items-center sm:space-x-4 ">
<div class="flex-1">
<app-search/>
</div>
<div class="">
<app-display-profile-card (onDisplayChange)="showNewDisplay($event)"/>
</div>
</div>
<div class="max-w-6xl mx-auto px-4">
@switch (display()) {
@case ('list'.toUpperCase()) {
<app-horizental-profile-list [profiles]="profiles"/>
}
@case ('grid'.toUpperCase()) {
<app-vertical-profile-list [profiles]="profiles"/>
}
@default {
<app-vertical-profile-list [profiles]="profiles"/>
}
}
</div>
</section>
<section class="pb-10 relative">
<div
class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10 flex sm:flex-row flex-col space-y-2 items-center sm:space-x-4"
>
<div class="flex-1">
<app-search />
</div>
</div>
<div class="max-w-6xl mx-auto px-4">
@if (loading()) {
<div class="flex justify-center items-center h-96">
<p>Chargement...</p>
</div>
} @else {
<app-vertical-profile-list [profiles]="profiles()" />
}
</div>
</section>

View File

@@ -1,27 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileListComponent } from './profile-list.component';
import {provideRouter} from "@angular/router";
describe('ProfileListComponent', () => {
let component: ProfileListComponent;
let fixture: ComponentFixture<ProfileListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileListComponent],
providers:[
provideRouter([])
]
})
.compileComponents();
fixture = TestBed.createComponent(ProfileListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileListComponent } from './profile-list.component';
import { provideRouter } from '@angular/router';
describe('ProfileListComponent', () => {
let component: ProfileListComponent;
let fixture: ComponentFixture<ProfileListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProfileListComponent],
providers: [provideRouter([])],
}).compileComponents();
fixture = TestBed.createComponent(ProfileListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,46 +1,25 @@
import {Component, inject, signal} from '@angular/core';
import {ActivatedRoute} from "@angular/router";
import {SearchComponent} from "@app/shared/features/search/search.component";
import {
HorizentalProfileItemComponent
} from "@app/shared/components/horizental-profile-item/horizental-profile-item.component";
import {
VerticalProfileItemComponent
} from "@app/shared/components/vertical-profile-item/vertical-profile-item.component";
import {DisplayProfileCardComponent} from "@app/shared/features/display-profile-card/display-profile-card.component";
import {JsonPipe} from "@angular/common";
import {
HorizentalProfileListComponent
} from "@app/shared/components/horizental-profile-list/horizental-profile-list.component";
import {
VerticalProfileListComponent
} from "@app/shared/components/vertical-profile-list/vertical-profile-list.component";
import {UntilDestroy} from "@ngneat/until-destroy";
import {Profile} from "@app/shared/models/profile";
@Component({
selector: 'app-profile-list',
standalone: true,
imports: [
SearchComponent,
HorizentalProfileItemComponent,
VerticalProfileItemComponent,
DisplayProfileCardComponent,
JsonPipe,
HorizentalProfileListComponent,
VerticalProfileListComponent
],
templateUrl: './profile-list.component.html',
styleUrl: './profile-list.component.scss'
})
@UntilDestroy()
export class ProfileListComponent {
private readonly route = inject(ActivatedRoute);
protected profiles : Profile[] = this.route.snapshot.data['profiles'] as Profile[];
protected display = signal<string>('grid'.toUpperCase());
showNewDisplay($event: string) {
this.display.set($event.toUpperCase())
}
}
import { Component, inject, OnInit } from '@angular/core';
import { SearchComponent } from '@app/shared/features/search/search.component';
import { DisplayProfileCardComponent } from '@app/shared/features/display-profile-card/display-profile-card.component';
import { VerticalProfileListComponent } from '@app/shared/components/vertical-profile-list/vertical-profile-list.component';
import { UntilDestroy } from '@ngneat/until-destroy';
import { ProfileFacade } from '@app/ui/profiles/profile.facade';
@Component({
selector: 'app-profile-list',
standalone: true,
imports: [SearchComponent, DisplayProfileCardComponent, VerticalProfileListComponent],
templateUrl: './profile-list.component.html',
styleUrl: './profile-list.component.scss',
})
@UntilDestroy()
export class ProfileListComponent implements OnInit {
private readonly facade = inject(ProfileFacade);
protected readonly profiles = this.facade.profiles;
protected readonly loading = this.facade.loading;
protected readonly error = this.facade.error;
ngOnInit() {
this.facade.load();
}
}

View File

@@ -1,18 +1,27 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ProfileListComponent} from "@app/routes/profile/profile-list/profile-list.component";
import {ProfileDetailComponent} from "@app/routes/profile/profile-detail/profile-detail.component";
import {listResolver} from "@app/core/resolvers/profile/list/list.resolver";
import {detailResolver} from "@app/core/resolvers/profile/detail/detail.resolver";
const routes: Routes = [
{path: '', component: ProfileListComponent, title: 'Liste des profiles', resolve: {profiles: listResolver}},
{path: ':name', component: ProfileDetailComponent, title: 'Detail du profile', resolve: {profile: detailResolver}},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProfileRoutingModule {
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProfileListComponent } from '@app/routes/profile/profile-list/profile-list.component';
import { ProfileDetailComponent } from '@app/routes/profile/profile-detail/profile-detail.component';
import { listResolver } from '@app/core/resolvers/profile/list/list.resolver';
import { detailResolver } from '@app/core/resolvers/profile/detail/detail.resolver';
const routes: Routes = [
{
path: '',
component: ProfileListComponent,
title: 'Liste des profiles',
resolve: { profiles: listResolver },
},
{
path: ':name',
component: ProfileDetailComponent,
title: 'Detail du profile',
resolve: { profile: detailResolver },
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ProfileRoutingModule {}

View File

@@ -1,14 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileRoutingModule } from './profile-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
ProfileRoutingModule
]
})
export class ProfileModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileRoutingModule } from './profile-routing.module';
@NgModule({
declarations: [],
imports: [CommonModule, ProfileRoutingModule],
})
export class ProfileModule {}