diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts
index b0591ea..e7352dc 100644
--- a/app/api/auth/me/route.ts
+++ b/app/api/auth/me/route.ts
@@ -17,6 +17,7 @@ export async function GET() {
id: true,
email: true,
name: true,
+ photoUrl: true,
roleId: true,
role: {
select: {
diff --git a/app/api/user/profile/route.ts b/app/api/user/profile/route.ts
new file mode 100644
index 0000000..fcef41f
--- /dev/null
+++ b/app/api/user/profile/route.ts
@@ -0,0 +1,131 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getCurrentUser } from '@/lib/auth';
+import { prisma } from '@/lib/prisma';
+import { hashPassword, verifyPassword } from '@/lib/auth';
+
+// GET - Récupérer le profil de l'utilisateur
+export async function GET() {
+ try {
+ const user = await getCurrentUser();
+ if (!user) {
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
+ }
+
+ const userProfile = await prisma.user.findUnique({
+ where: { id: user.id },
+ select: {
+ id: true,
+ email: true,
+ name: true,
+ photoUrl: true,
+ role: {
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ },
+ },
+ },
+ });
+
+ return NextResponse.json(userProfile);
+ } catch (error) {
+ console.error('Erreur lors de la récupération du profil:', error);
+ return NextResponse.json(
+ { error: 'Erreur serveur' },
+ { status: 500 }
+ );
+ }
+}
+
+// PUT - Mettre à jour le profil de l'utilisateur
+export async function PUT(request: NextRequest) {
+ try {
+ const user = await getCurrentUser();
+ if (!user) {
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
+ }
+
+ const body = await request.json();
+ const { name, email, photoUrl, currentPassword, newPassword } = body;
+
+ // Récupérer l'utilisateur complet pour vérifier le mot de passe si nécessaire
+ const currentUser = await prisma.user.findUnique({
+ where: { id: user.id },
+ });
+
+ if (!currentUser) {
+ return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
+ }
+
+ // Vérifier si l'email est déjà utilisé par un autre utilisateur
+ if (email && email !== currentUser.email) {
+ const existingUser = await prisma.user.findUnique({
+ where: { email },
+ });
+ if (existingUser) {
+ return NextResponse.json(
+ { error: 'Cet email est déjà utilisé' },
+ { status: 400 }
+ );
+ }
+ }
+
+ // Vérifier le mot de passe actuel si un nouveau mot de passe est fourni
+ if (newPassword) {
+ if (!currentPassword) {
+ return NextResponse.json(
+ { error: 'Le mot de passe actuel est requis pour changer le mot de passe' },
+ { status: 400 }
+ );
+ }
+
+ const isValidPassword = await verifyPassword(currentPassword, currentUser.password);
+ if (!isValidPassword) {
+ return NextResponse.json(
+ { error: 'Mot de passe actuel incorrect' },
+ { status: 400 }
+ );
+ }
+ }
+
+ // Préparer les données à mettre à jour
+ const updateData: any = {};
+ if (name !== undefined) updateData.name = name;
+ if (email !== undefined) updateData.email = email;
+ if (photoUrl !== undefined) updateData.photoUrl = photoUrl;
+ if (newPassword) {
+ updateData.password = await hashPassword(newPassword);
+ }
+
+ // Mettre à jour l'utilisateur
+ const updatedUser = await prisma.user.update({
+ where: { id: user.id },
+ data: updateData,
+ select: {
+ id: true,
+ email: true,
+ name: true,
+ photoUrl: true,
+ role: {
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ },
+ },
+ },
+ });
+
+ return NextResponse.json({
+ success: true,
+ user: updatedUser,
+ });
+ } catch (error) {
+ console.error('Erreur lors de la mise à jour du profil:', error);
+ return NextResponse.json(
+ { error: 'Erreur serveur' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/users/route.ts b/app/api/users/route.ts
index 2adba1b..e6e47f6 100644
--- a/app/api/users/route.ts
+++ b/app/api/users/route.ts
@@ -15,6 +15,7 @@ export async function GET(request: NextRequest) {
id: true,
email: true,
name: true,
+ photoUrl: true,
roleId: true,
role: {
select: {
diff --git a/app/dashboard/parametres/compte/page.tsx b/app/dashboard/parametres/compte/page.tsx
new file mode 100644
index 0000000..f036e35
--- /dev/null
+++ b/app/dashboard/parametres/compte/page.tsx
@@ -0,0 +1,24 @@
+import { redirect } from 'next/navigation';
+import { getCurrentUser } from '@/lib/auth';
+import { hasPageAccess } from '@/lib/permissions';
+import DashboardLayout from '@/components/DashboardLayout';
+import ModifierCompteContent from '@/components/ModifierCompteContent';
+
+export default async function ModifierComptePage() {
+ const user = await getCurrentUser();
+
+ if (!user) {
+ redirect('/login');
+ }
+
+ const hasAccess = await hasPageAccess(user.id, '/dashboard/parametres');
+ if (!hasAccess) {
+ redirect('/login');
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/components/ConfigurationContent.tsx b/components/ConfigurationContent.tsx
index 9d25cd1..a8eac70 100644
--- a/components/ConfigurationContent.tsx
+++ b/components/ConfigurationContent.tsx
@@ -345,6 +345,7 @@ export default function ConfigurationContent() {
id: string;
email: string;
name: string | null;
+ photoUrl?: string | null;
roleId: string | null;
role: {
id: string;
@@ -495,9 +496,17 @@ export default function ConfigurationContent() {
>
{/* Avatar */}
-
- {getUserInitials(user.name, user.email)}
-
+ {user.photoUrl ? (
+

+ ) : (
+
+ {getUserInitials(user.name, user.email)}
+
+ )}
{/* Informations */}
diff --git a/components/DashboardLayout.tsx b/components/DashboardLayout.tsx
index de35636..6a8e8e2 100644
--- a/components/DashboardLayout.tsx
+++ b/components/DashboardLayout.tsx
@@ -10,6 +10,7 @@ interface User {
id: string;
email: string;
name: string | null;
+ photoUrl?: string | null;
roleId?: string | null;
}
@@ -71,7 +72,14 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
fetcher
);
+ // Récupérer le profil complet de l'utilisateur pour la photo
+ const { data: userProfile } = useSWR<{ photoUrl?: string | null }>(
+ '/api/user/profile',
+ fetcher
+ );
+
const accessiblePages = userPagesData?.pages || [];
+ const userPhotoUrl = userProfile?.photoUrl;
// Calculer le nombre total de messages non lus
const totalUnreadCount = Array.isArray(conversations)
@@ -377,9 +385,17 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
onClick={() => setShowProfileMenu(!showProfileMenu)}
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 border border-gray-200 transition-all duration-200 hover:shadow-sm group"
>
-
- {getUserInitials()}
-
+ {userPhotoUrl ? (
+

+ ) : (
+
+ {getUserInitials()}
+
+ )}
{user.name || 'Utilisateur'}
{user.email}
diff --git a/components/ModifierCompteContent.tsx b/components/ModifierCompteContent.tsx
new file mode 100644
index 0000000..6805122
--- /dev/null
+++ b/components/ModifierCompteContent.tsx
@@ -0,0 +1,351 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+import { useNotification } from './NotificationProvider';
+import useSWR from 'swr';
+
+interface UserProfile {
+ id: string;
+ email: string;
+ name: string | null;
+ photoUrl: string | null;
+ role: {
+ id: string;
+ name: string;
+ description: string | null;
+ } | null;
+}
+
+const fetcher = (url: string) => fetch(url).then((res) => res.json());
+
+export default function ModifierCompteContent() {
+ const router = useRouter();
+ const { showNotification } = useNotification();
+ const { data: userProfile, mutate } = useSWR
('/api/user/profile', fetcher);
+
+ const [loading, setLoading] = useState(false);
+ const [formData, setFormData] = useState({
+ name: '',
+ email: '',
+ photoUrl: '',
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ });
+
+ useEffect(() => {
+ if (userProfile) {
+ setFormData({
+ name: userProfile.name || '',
+ email: userProfile.email || '',
+ photoUrl: userProfile.photoUrl || '',
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ });
+ }
+ }, [userProfile]);
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData((prev) => ({ ...prev, [name]: value }));
+ };
+
+ const handleImageUpload = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file) {
+ // Vérifier la taille du fichier (max 5MB)
+ if (file.size > 5 * 1024 * 1024) {
+ showNotification('L\'image est trop grande (max 5MB)', 'error');
+ return;
+ }
+
+ // Vérifier le type de fichier
+ if (!file.type.startsWith('image/')) {
+ showNotification('Veuillez sélectionner une image', 'error');
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setFormData((prev) => ({ ...prev, photoUrl: reader.result as string }));
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+
+ try {
+ // Vérifier que les mots de passe correspondent si un nouveau mot de passe est fourni
+ if (formData.newPassword) {
+ if (formData.newPassword !== formData.confirmPassword) {
+ showNotification('Les mots de passe ne correspondent pas', 'error');
+ setLoading(false);
+ return;
+ }
+
+ if (formData.newPassword.length < 6) {
+ showNotification('Le mot de passe doit contenir au moins 6 caractères', 'error');
+ setLoading(false);
+ return;
+ }
+ }
+
+ const updateData: any = {
+ name: formData.name.trim() || null,
+ email: formData.email.trim(),
+ photoUrl: formData.photoUrl || null,
+ };
+
+ if (formData.newPassword) {
+ updateData.currentPassword = formData.currentPassword;
+ updateData.newPassword = formData.newPassword;
+ }
+
+ const response = await fetch('/api/user/profile', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(updateData),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ showNotification('Profil mis à jour avec succès', 'success');
+ mutate();
+ // Réinitialiser les champs de mot de passe
+ setFormData((prev) => ({
+ ...prev,
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ }));
+ } else {
+ showNotification(data.error || 'Erreur lors de la mise à jour', 'error');
+ }
+ } catch (error) {
+ console.error('Erreur:', error);
+ showNotification('Erreur lors de la mise à jour', 'error');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getUserInitials = () => {
+ if (userProfile?.name) {
+ const names = userProfile.name.split(' ');
+ if (names.length >= 2) {
+ return `${names[0].charAt(0)}${names[1].charAt(0)}`.toUpperCase();
+ }
+ return userProfile.name.charAt(0).toUpperCase();
+ }
+ return userProfile?.email?.charAt(0).toUpperCase() || 'U';
+ };
+
+ if (!userProfile) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
Modifier mon compte
+
+ Mettez à jour vos informations personnelles et votre mot de passe
+
+
+
+
+
+ );
+}
diff --git a/components/ParametresContent.tsx b/components/ParametresContent.tsx
index aa79f2f..e08a4fe 100644
--- a/components/ParametresContent.tsx
+++ b/components/ParametresContent.tsx
@@ -10,6 +10,7 @@ interface User {
id: string;
email: string;
name: string | null;
+ photoUrl?: string | null;
roleId: string | null;
role: {
id: string;
@@ -328,16 +329,27 @@ export default function ParametresContent() {
-
- {getUserInitials()}
-
+ {user?.photoUrl ? (
+

+ ) : (
+
+ {getUserInitials()}
+
+ )}
{user?.name || 'Utilisateur'}
{user?.email}
-