refacto les card items et list

This commit is contained in:
styve Lioumba
2025-11-16 12:18:02 +01:00
parent b669190bce
commit e33c8a229f
18 changed files with 879 additions and 253 deletions

View File

@@ -6,7 +6,7 @@ import { ProfileRepository } from '@app/domain/profiles/profile.repository';
import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token'; import { PROFILE_REPOSITORY_TOKEN } from '@app/infrastructure/profiles/profile-repository.token';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { Profile } from '@app/domain/profiles/profile.model'; import { Profile } from '@app/domain/profiles/profile.model';
import {ToastrService} from "ngx-toastr"; import { ToastrService } from 'ngx-toastr';
describe('MyProfileComponent', () => { describe('MyProfileComponent', () => {
let component: MyProfileComponent; let component: MyProfileComponent;

View File

@@ -236,19 +236,29 @@
<div <div
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 md:p-8 hover:shadow-xl transition-shadow duration-300" class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 md:p-8 hover:shadow-xl transition-shadow duration-300"
> >
<h2 <!-- Header -->
class="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex items-center gap-2" <div class="flex items-center gap-3 pb-6 mb-8 border-b-2 border-indigo-500">
<div
class="w-12 h-12 bg-indigo-100 dark:bg-indigo-900 rounded-lg flex items-center justify-center"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-indigo-500" class="w-7 h-7 text-indigo-600 dark:text-indigo-400"
viewBox="0 0 20 20" viewBox="0 0 20 20"
fill="currentColor" fill="currentColor"
> >
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" /> <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
</svg> </svg>
Projets </div>
<div class="flex-1">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
Explorer les projets
</h2> </h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Découvrez les réalisations de la communauté
</p>
</div>
</div>
<app-project-list [userProjectId]="profile().utilisateur" /> <app-project-list [userProjectId]="profile().utilisateur" />
</div> </div>
</div> </div>

View File

@@ -1,16 +1,42 @@
<div class="min-h-screen py-4 font-sans"> <div class="w-full space-y-6">
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto">
<h2 class="text-2xl font-bold text-gray-800 mb-8">Mes projets</h2>
@if (projects()) { @if (projects()) {
<div class="relative flex items-center"> <!-- Sélecteur de projet -->
<select <div
[(ngModel)]="projectIdSelected" class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in animation-delay-100"
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]"
> >
<option [value]="null" disabled>Selectionner le projet à modifier</option> <div class="space-y-3">
<label
for="project-select"
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Sélectionnez un projet à modifier
</label>
<option [value]="'add'.toLowerCase()">Ajouter un nouveau projet</option> <div class="relative">
<!-- Icône à gauche -->
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
</svg>
</div>
<!-- Select -->
<select
id="project-select"
[(ngModel)]="projectIdSelected"
class="w-full pl-10 pr-10 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all appearance-none cursor-pointer"
>
<option [value]="null" disabled>Sélectionnez un projet</option>
<!-- Option Ajouter avec style spécial -->
<option [value]="'add'.toLowerCase()" class="font-semibold">
Ajouter un nouveau projet
</option>
@for (project of projects(); track project.id) { @for (project of projects(); track project.id) {
<option [value]="project.id"> <option [value]="project.id">
@@ -18,16 +44,81 @@
</option> </option>
} }
</select> </select>
</div>
}
<div class="w-full my-8"> <!-- Chevron à droite -->
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
class="h-5 w-5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
<!-- Info bulle -->
<p class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
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"
clip-rule="evenodd"
/>
</svg>
Vous avez {{ projects().length }} projet(s) enregistré(s)
</p>
</div>
</div>
<!-- Formulaire du projet sélectionné -->
@if (projectIdSelected() != null) { @if (projectIdSelected() != null) {
<div class="animate-slide-up">
<app-my-profile-update-project-form <app-my-profile-update-project-form
[projectId]="projectIdSelected()" [projectId]="projectIdSelected()"
(formIsUpdated)="onProjectFormSubmitted($event)" (formIsUpdated)="onProjectFormSubmitted($event)"
/> />
</div>
} @else {
<!-- Message d'invite -->
<div
class="bg-gradient-to-r from-indigo-50 to-purple-50 dark:from-gray-800 dark:to-gray-700 rounded-xl p-8 text-center animate-fade-in animation-delay-200"
>
<div
class="inline-flex w-16 h-16 bg-white dark:bg-gray-600 rounded-full items-center justify-center mb-4 shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-8 h-8 text-indigo-600 dark:text-indigo-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Aucun projet sélectionné
</h3>
<p class="text-gray-600 dark:text-gray-300">
Sélectionnez un projet existant ou créez-en un nouveau pour commencer
</p>
</div>
}
} }
</div> </div>
</div>
</div>

View File

@@ -1,98 +1,189 @@
@if (projectId) { @if (projectId) {
<div class="space-y-6">
<!-- Section Image du projet -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in">
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 bg-indigo-100 dark:bg-indigo-900 rounded-lg flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-indigo-600 dark:text-indigo-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Image du projet</h3>
</div>
@if (projectId == 'add'.toLowerCase()) { @if (projectId == 'add'.toLowerCase()) {
<app-project-picture-form [project]="undefined" /> <app-project-picture-form [project]="undefined" />
} @else { } @else {
<app-project-picture-form [project]="project()" /> <app-project-picture-form [project]="project()" />
} }
</div>
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white my-5"> <!-- Section Informations du projet -->
Information du projet <div
</h3> class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 animate-fade-in animation-delay-100"
>
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-purple-600 dark:text-purple-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" />
<path
fill-rule="evenodd"
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Informations du projet</h3>
</div>
@if (loading().isLoading) { @if (loading().isLoading) {
@switch (loading().action) { @switch (loading().action) {
@case (ActionType.NONE || ActionType.CREATE || ActionType.DELETE) {} @case (ActionType.NONE || ActionType.CREATE || ActionType.DELETE) {}
@default { @default {
<p>Chargement...</p> <!-- Loader -->
<app-loading message="Chargement du projet..." />
} }
} }
} @else { } @else {
<form class="mx-8" [formGroup]="projectForm" (ngSubmit)="onSubmit()"> <!-- Formulaire -->
<label class="mb-2 text-sm text-black block dark:text-white">Nom</label> <form [formGroup]="projectForm" (ngSubmit)="onSubmit()" class="space-y-6">
<div class="relative flex items-center"> <!-- Champ Nom -->
<div class="space-y-2">
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Nom du projet
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
</svg>
</div>
<input <input
id="nom"
type="text" type="text"
placeholder="nom du projet"
formControlName="nom" formControlName="nom"
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]" placeholder="Nom du projet"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"
/> />
</div>
</div>
<div class="absolute left-4"> <!-- Champ Lien -->
<div class="space-y-2">
<label for="lien" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Lien du projet
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="22px" class="h-5 w-5 text-gray-400"
height="22px" viewBox="0 0 20 20"
fill="#bbb" fill="currentColor"
viewBox="0 0 512 512"
> >
<path <path
d="M437.02 74.981C388.667 26.629 324.38 0 256 0S123.333 26.629 74.98 74.981C26.629 123.333 0 187.62 0 256s26.629 132.667 74.98 181.019C123.333 485.371 187.62 512 256 512s132.667-26.629 181.02-74.981C485.371 388.667 512 324.38 512 256s-26.629-132.667-74.98-181.019zM256 482c-66.869 0-127.037-29.202-168.452-75.511C113.223 338.422 178.948 290 256 290c-49.706 0-90-40.294-90-90s40.294-90 90-90 90 40.294 90 90-40.294 90-90 90c77.052 0 142.777 48.422 168.452 116.489C383.037 452.798 322.869 482 256 482z" fill-rule="evenodd"
data-original="#000000" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z"
></path> clip-rule="evenodd"
/>
</svg> </svg>
</div> </div>
</div>
<label class="mb-2 text-sm text-black block dark:text-white">Lien</label>
<div class="relative flex items-center">
<input <input
type="text" id="lien"
placeholder="lien vers votre projet ex : http://monprojet" type="url"
formControlName="lien" formControlName="lien"
class="pr-4 pl-14 py-3 text-sm text-black rounded bg-white border border-gray-400 w-full outline-[#333]" placeholder="https://monprojet.com"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"
/> />
</div>
</div>
<div class="absolute left-4"> <!-- Champ Description -->
<svg <div class="space-y-2">
xmlns="http://www.w3.org/2000/svg" <label
width="22px" for="description"
height="22px" class="block text-sm font-medium text-gray-700 dark:text-gray-300"
fill="#bbb"
viewBox="0 0 512 512"
> >
<path Description
d="M437.02 74.981C388.667 26.629 324.38 0 256 0S123.333 26.629 74.98 74.981C26.629 123.333 0 187.62 0 256s26.629 132.667 74.98 181.019C123.333 485.371 187.62 512 256 512s132.667-26.629 181.02-74.981C485.371 388.667 512 324.38 512 256s-26.629-132.667-74.98-181.019zM256 482c-66.869 0-127.037-29.202-168.452-75.511C113.223 338.422 178.948 290 256 290c-49.706 0-90-40.294-90-90s40.294-90 90-90 90 40.294 90 90-40.294 90-90 90c77.052 0 142.777 48.422 168.452 116.489C383.037 452.798 322.869 482 256 482z" </label>
data-original="#000000"
></path>
</svg>
</div>
</div>
<label class="mb-2 text-sm text-black block dark:text-white">description</label>
<div class="relative flex items-center">
<textarea <textarea
placeholder="Type Message" id="description"
formControlName="description" formControlName="description"
class="p-4 bg-white w-full block text-sm border border-gray-300 outline-[#007bff] rounded" placeholder="Décrivez votre projet..."
rows="4" rows="4"
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all resize-none"
></textarea> ></textarea>
</div> </div>
<!-- Boutons d'action -->
<div class="flex flex-col sm:flex-row gap-3 pt-4">
<button <button
type="submit" type="submit"
[ngClass]="{ 'bg-purple-600': projectForm.valid }" [disabled]="projectForm.invalid"
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block" class="flex-1 py-3 px-4 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
> >
<span class="flex items-center justify-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"
/>
</svg>
Sauvegarder Sauvegarder
</span>
</button> </button>
<button <button
type="button" type="button"
(click)="formIsUpdated.emit(null)" (click)="formIsUpdated.emit(null)"
class="!mt-8 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block" class="flex-1 sm:flex-initial py-3 px-6 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-900 dark:text-white font-medium rounded-lg transition-all duration-200"
> >
<span class="flex items-center justify-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
Annuler Annuler
</span>
</button> </button>
</div>
</form> </form>
} }
</div>
</div>
} }

View File

@@ -0,0 +1,30 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
.animation-delay-100 {
animation-delay: 0.1s;
opacity: 0;
animation-fill-mode: forwards;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -17,11 +17,18 @@ import { AuthService } from '@app/core/services/authentication/auth.service';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto'; import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { LoadingComponent } from '@app/shared/components/loading/loading.component';
@Component({ @Component({
selector: 'app-my-profile-update-project-form', selector: 'app-my-profile-update-project-form',
standalone: true, standalone: true,
imports: [PaginatorModule, ReactiveFormsModule, NgClass, ProjectPictureFormComponent], imports: [
PaginatorModule,
ReactiveFormsModule,
NgClass,
ProjectPictureFormComponent,
LoadingComponent,
],
templateUrl: './my-profile-update-project-form.component.html', templateUrl: './my-profile-update-project-form.component.html',
styleUrl: './my-profile-update-project-form.component.scss', styleUrl: './my-profile-update-project-form.component.scss',
}) })

View File

@@ -1,32 +1,56 @@
@if (project) { @if (project) {
<div class="bg-white rounded-2xl border p-6"> <div
class="group bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-2xl hover:-translate-y-2 animate-fade-in"
>
<!-- Image du projet -->
<div class="relative h-48 overflow-hidden bg-gray-100 dark:bg-gray-700">
@if (project.fichier) { @if (project.fichier) {
<img <img
alt="{{ project.nom }}" alt="{{ project.nom }}"
class="object-cover object-center h-full w-full" class="w-full h-full object-cover object-center transition-transform duration-500 group-hover:scale-110"
src="{{ environment.baseUrl }}/api/files/projets/{{ project.id }}/{{ project.fichier }}" src="{{ environment.baseUrl }}/api/files/projets/{{ project.id }}/{{ project.fichier }}"
loading="lazy" loading="lazy"
/> />
} @else { } @else {
<img <img
alt="nouveau-projet" alt="{{ project.nom }}"
class="object-cover object-center h-full w-full" class="w-full h-full object-cover object-center transition-transform duration-500 group-hover:scale-110 opacity-60"
src="https://api.dicebear.com/9.x/shapes/svg?seed={{ project.nom }}" src="https://api.dicebear.com/9.x/shapes/svg?seed={{ project.nom }}"
loading="lazy" loading="lazy"
/> />
} }
<div class="mt-6"> <!-- Overlay gradient au hover -->
<h3 class="text-lg font-bold text-gray-800 mb-3">{{ project.nom }}</h3> <div
<p class="text-gray-800 text-sm">{{ project.description }}</p> class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
<div class="mt-6"> ></div>
</div>
<!-- Contenu -->
<div class="p-6 space-y-4">
<!-- Titre -->
<h3
class="text-xl font-bold text-gray-900 dark:text-white line-clamp-2 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors"
>
{{ project.nom }}
</h3>
<!-- Description -->
<p class="text-sm text-gray-600 dark:text-gray-300 line-clamp-3">
{{ project.description || 'Aucune description disponible.' }}
</p>
<!-- Lien vers le projet -->
@if (project.lien) {
<a <a
class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0" [href]="project.lien"
href="{{ project.lien }}"
target="_blank" target="_blank"
>Explore rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 font-medium text-sm transition-all duration-200 group/link"
>
<span>Explorer le projet</span>
<svg <svg
class="w-4 h-4 ml-2" class="w-4 h-4 transition-transform duration-200 group-hover/link:translate-x-1"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
@@ -38,7 +62,12 @@
<path d="M12 5l7 7-7 7"></path> <path d="M12 5l7 7-7 7"></path>
</svg> </svg>
</a> </a>
}
</div> </div>
</div>
<!-- Bordure animée au hover -->
<div
class="absolute inset-0 border-2 border-transparent group-hover:border-indigo-500 rounded-xl transition-all duration-300 pointer-events-none"
></div>
</div> </div>
} }

View File

@@ -0,0 +1,39 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
/* Line clamp pour limiter le texte */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -1,12 +1,41 @@
<div class="min-h-screen py-4 font-sans"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="max-w-4xl max-lg:max-w-2xl max-sm:max-w-sm mx-auto"> <!-- Grid des projets -->
<h2 class="text-2xl font-bold text-gray-800 mb-8">Explorer les projets</h2> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> @for (project of projects(); track project.id) {
@for (project of projects(); track project) {
<app-project-item [project]="project" /> <app-project-item [project]="project" />
} @empty { } @empty {
<p>Aucun projet</p> <!-- Message si aucun projet -->
<div class="col-span-full">
<div
class="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-700 rounded-xl p-12 text-center"
>
<div
class="inline-flex w-20 h-20 bg-white dark:bg-gray-600 rounded-full items-center justify-center mb-6 shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-gray-400 dark:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">
Aucun projet disponible
</h3>
<p class="text-gray-600 dark:text-gray-300 max-w-md mx-auto">
Il n'y a pas encore de projets à explorer. Revenez plus tard pour découvrir de nouvelles
réalisations !
</p>
</div>
</div>
} }
</div> </div>
</div> </div>
</div>

View File

@@ -0,0 +1,24 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -1,35 +1,107 @@
<form <form
class="space-y-6 px-4 max-w-sm mx-auto font-[sans-serif]"
[formGroup]="userForm" [formGroup]="userForm"
(ngSubmit)="onUserFormSubmit()" (ngSubmit)="onUserFormSubmit()"
class="w-full space-y-6 animate-fade-in"
> >
<h3 class="font-ubuntu font-bold text-xl uppercase dark:text-white mb-4">Mon Identité</h3> <!-- Titre -->
<div class="flex items-center gap-3 pb-4 border-b border-gray-200 dark:border-gray-700">
<div
class="w-10 h-10 bg-indigo-100 dark:bg-indigo-900 rounded-lg flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-indigo-600 dark:text-indigo-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Mon identité</h3>
</div>
<div class="flex items-center"> <!-- Champ Nom -->
<label class="text-gray-400 w-36 text-xs">Nom</label> <div class="space-y-2">
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Nom
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<input <input
id="name"
type="text" type="text"
placeholder="Enter your name"
formControlName="name" formControlName="name"
class="px-4 py-3 bg-gray-100 focus:bg-transparent w-full text-xs outline-[#333] rounded-sm transition-all" placeholder="Votre nom"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"
/> />
</div> </div>
</div>
<div class="flex items-center"> <!-- Champ Prénom -->
<label class="text-gray-400 w-36 text-xs">Prénom</label> <div class="space-y-2">
<label for="firstname" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Prénom
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<input <input
id="firstname"
type="text" type="text"
placeholder="entre votre prénom"
formControlName="firstname" formControlName="firstname"
class="px-4 py-3 bg-gray-100 focus:bg-transparent w-full text-xs outline-[#333] rounded-sm transition-all" placeholder="Votre prénom"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"
/> />
</div> </div>
</div>
<!-- Bouton de soumission -->
<button <button
type="submit" type="submit"
[ngClass]="{ 'bg-purple-600': userForm.valid }" [disabled]="userForm.invalid"
class="!mt-4 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block" class="w-full py-3 px-4 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
> >
<span class="flex items-center justify-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"
/>
</svg>
Modifier mon identité Modifier mon identité
</span>
</button> </button>
</form> </form>

View File

@@ -0,0 +1,24 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -2,15 +2,21 @@
<a <a
[routerLink]="[user.username ? user.username : user.id]" [routerLink]="[user.username ? user.username : user.id]"
[state]="{ user, profile }" [state]="{ user, profile }"
class="cursor-pointer" class="block group"
> >
<div class="group text-center text-gray-500 dark:text-gray-400"> <!-- Card du profil -->
<div
class="relative bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-2xl hover:-translate-y-2"
>
<!-- Badge vérifié (position absolue en haut à droite) -->
@if (profile.estVerifier) { @if (profile.estVerifier) {
<div class="absolute top-3 right-3 z-10 animate-pulse-slow">
<div class="bg-purple-500/20 backdrop-blur-md p-2 rounded-full">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
class="size-6 text-purple-800" class="w-6 h-6 text-purple-500"
> >
<path <path
fill-rule="evenodd" fill-rule="evenodd"
@@ -18,43 +24,75 @@
clip-rule="evenodd" clip-rule="evenodd"
/> />
</svg> </svg>
</div>
</div>
} }
<!-- Partie supérieure avec avatar -->
<div class="p-6 text-center">
<!-- Avatar avec bordure gradient -->
<div class="relative inline-block mb-4">
<div class="w-32 h-32 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-1">
@if (user.avatar) { @if (user.avatar) {
<img <img
class="mx-auto mb-4 w-36 h-36 rounded-full grayscale object-cover object-top ransition duration-500 group-hover:scale-105 group-hover:grayscale-0" class="w-full h-full rounded-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500 group-hover:scale-105"
src="{{ environment.baseUrl }}/api/files/users/{{ user.id }}/{{ user.avatar }}" src="{{ environment.baseUrl }}/api/files/users/{{ user.id }}/{{ user.avatar }}"
alt="{{ user.username }}" alt="{{ user.username }}"
loading="lazy" loading="lazy"
/> />
} @else { } @else {
<img <img
class="mx-auto mb-4 w-36 h-36 rounded-full grayscale object-cover object-top ransition duration-500 group-hover:scale-105 group-hover:grayscale-0" class="w-full h-full rounded-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500 group-hover:scale-105"
src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user.username }}" src="https://api.dicebear.com/9.x/adventurer/svg?seed={{ user.username }}"
alt="{{ user.username }}" alt="{{ user.username }}"
loading="lazy" loading="lazy"
/> />
} }
</div>
</div>
<div <!-- Nom -->
class="translate-y-2 transition duration-300 ease-in-out group-hover:translate-y-0 flex flex-col items-center space-y-2"
>
@if (user.name) { @if (user.name) {
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"> <h3
class="text-lg font-bold text-gray-900 dark:text-white mb-2 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors"
>
{{ user.name }} {{ user.name }}
</h3> </h3>
} @else if (user.username) { } @else if (user.username) {
<h3 class="mb-1 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"> <h3
class="text-lg font-bold text-gray-900 dark:text-white mb-2 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors"
>
{{ user.username }} {{ user.username }}
</h3> </h3>
} @else { } @else {
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white"> <h3 class="text-lg font-bold text-gray-500 dark:text-gray-400 mb-2">Non mentionné</h3>
Non mentionné
</h3>
} }
<p class="font-semibold">{{ profile.profession }}</p>
<!-- Profession -->
<p class="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-4">
{{ profile.profession || 'Profession non renseignée' }}
</p>
</div>
<!-- Partie inférieure avec infos -->
<div class="px-6 pb-6 space-y-3 border-t border-gray-100 dark:border-gray-700 pt-4">
<!-- Secteur -->
<div class="flex justify-center">
<app-chips [sectorId]="profile.secteur" /> <app-chips [sectorId]="profile.secteur" />
</div>
<!-- Réseaux sociaux -->
@if (profile.reseaux) {
<div class="flex justify-center">
<app-reseaux [reseaux]="profile.reseaux" /> <app-reseaux [reseaux]="profile.reseaux" />
</div> </div>
}
</div>
<!-- Indicateur de hover (bordure animée) -->
<div
class="absolute inset-0 border-2 border-transparent group-hover:border-indigo-500 rounded-xl transition-all duration-300 pointer-events-none"
></div>
</div> </div>
</a> </a>
} }

View File

@@ -0,0 +1,23 @@
@keyframes pulseSlow {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.8;
}
}
.animate-pulse-slow {
animation: pulseSlow 3s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

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

View File

@@ -0,0 +1,24 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -1,4 +1,57 @@
@if (cv_link()) { @if (cv_link()) {
<!-- Container du PDF viewer -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden animate-fade-in">
<!-- Header -->
<div
class="bg-gradient-to-r from-indigo-600 to-purple-600 px-6 py-4 flex items-center justify-between"
>
<div class="flex items-center gap-3">
<div
class="w-10 h-10 bg-white/20 backdrop-blur-md rounded-lg flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
/>
</svg>
</div>
<h3 class="text-lg font-bold text-white">Mon CV</h3>
</div>
<!-- Bouton télécharger -->
<a
[href]="cv_link()"
download
target="_blank"
class="flex items-center gap-2 px-4 py-2 bg-white/20 hover:bg-white/30 backdrop-blur-md rounded-lg text-white text-sm font-medium transition-all duration-200 hover:scale-105"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
<span class="hidden sm:inline">Télécharger</span>
</a>
</div>
<!-- PDF Viewer -->
<div class="p-4 bg-gray-100 dark:bg-gray-900">
<pdf-viewer <pdf-viewer
[src]="cv_link()" [src]="cv_link()"
[zoom]="1" [zoom]="1"
@@ -11,8 +64,38 @@
[render-text]="true" [render-text]="true"
[external-link-target]="'blank'" [external-link-target]="'blank'"
[autoresize]="true" [autoresize]="true"
[show-borders]="false" [show-borders]="true"
class="rounded-lg overflow-hidden shadow-inner"
/> />
</div>
</div>
} @else { } @else {
<p>Aucun curriculum vitae (CV) n'a été rajouté.</p> <!-- Message si aucun CV -->
<div
class="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-700 rounded-xl p-12 text-center animate-fade-in"
>
<div
class="inline-flex w-20 h-20 bg-white dark:bg-gray-600 rounded-full items-center justify-center mb-6 shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-10 h-10 text-gray-400 dark:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">Aucun CV disponible</h3>
<p class="text-gray-600 dark:text-gray-300 max-w-md mx-auto">
Aucun curriculum vitae n'a été ajouté pour le moment. Veuillez télécharger votre CV pour le
visualiser ici.
</p>
</div>
} }

View File

@@ -28,25 +28,6 @@
<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"
> >
<div class="flex items-center gap-3 mb-6">
<div
class="w-10 h-10 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-purple-600 dark:text-purple-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Informations personnelles</h3>
</div>
<app-user-form [user]="user" /> <app-user-form [user]="user" />
</div> </div>
</div> </div>