From d8ca4ef00b5f83d299811a76382fdda9ddb9db5f Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 30 Sep 2025 23:15:21 +0200 Subject: [PATCH] feat: enhance profile page and authentication with user avatar support - Updated `next.config.ts` to allow images from various external sources, including LinkedIn and GitHub. - Refactored `ProfilePage` to improve layout and display user avatar, name, and role more prominently. - Enhanced `AuthButton` to show user avatar if available, improving user experience. - Updated authentication logic in `auth.ts` to include user avatar and role in session management. - Extended JWT type definitions to support new user fields (firstName, lastName, avatar, role) for better user data handling. --- next.config.ts | 40 +++++ src/app/profile/page.tsx | 326 ++++++++++++++++++++++------------ src/components/AuthButton.tsx | 15 +- src/lib/auth.ts | 8 + src/types/next-auth.d.ts | 4 + 5 files changed, 272 insertions(+), 121 deletions(-) diff --git a/next.config.ts b/next.config.ts index 4fa8b56..fbe3e9b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,46 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: 'standalone', + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'media.licdn.com', + port: '', + pathname: '/**', + }, + { + protocol: 'https', + hostname: 'avatars.githubusercontent.com', + port: '', + pathname: '/**', + }, + { + protocol: 'https', + hostname: 'lh3.googleusercontent.com', + port: '', + pathname: '/**', + }, + { + protocol: 'https', + hostname: 'cdn.discordapp.com', + port: '', + pathname: '/**', + }, + { + protocol: 'https', + hostname: 'images.unsplash.com', + port: '', + pathname: '/**', + }, + { + protocol: 'https', + hostname: 'via.placeholder.com', + port: '', + pathname: '/**', + }, + ], + }, turbopack: { rules: { '*.sql': ['raw'], diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 70b2dc3..67cc6eb 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -140,138 +140,228 @@ export default function ProfilePage() {
-
- {/* Informations générales */} -
-

- Informations générales -

- -
-
- -
- {profile.email} -
-

- L'email ne peut pas être modifié -

+
+ {/* Header avec avatar et infos principales */} +
+
+ {/* Avatar section */} +
+ {profile.avatar ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Avatar +
+ + + +
+
+ ) : ( +
+ + + +
+ )}
- -
- -
- {profile.role} -
-
- -
- -
- {new Date(profile.createdAt).toLocaleDateString('fr-FR')} -
-
- - {profile.lastLoginAt && ( -
- -
- {new Date(profile.lastLoginAt).toLocaleString('fr-FR')} + + {/* User info */} +
+

+ {profile.name || `${profile.firstName || ''} ${profile.lastName || ''}`.trim() || profile.email} +

+

{profile.email}

+ +
+
+
+ {profile.role} +
+
+ + + + + Membre depuis {new Date(profile.createdAt).toLocaleDateString('fr-FR')} +
- )} +
- {/* Formulaire de modification */} -
-

- Modifier le profil -

+
+ {/* Informations générales */} +
+

+ + + + Informations générales +

+ +
+
+ + + +
+
{profile.email}
+
Email principal
+
+
-
-
- - handleChange('firstName', e.target.value)} - placeholder="Votre prénom" - /> -
+
+ + + +
+
{profile.role}
+
Rôle utilisateur
+
+
-
- - handleChange('lastName', e.target.value)} - placeholder="Votre nom de famille" - /> -
- -
- - handleChange('name', e.target.value)} - placeholder="Nom d'affichage personnalisé" - /> -

- Si vide, sera généré automatiquement à partir du prénom et nom -

-
- -
- - handleChange('avatar', e.target.value)} - placeholder="https://example.com/avatar.jpg" - /> + {profile.lastLoginAt && ( +
+ + + +
+
+ {new Date(profile.lastLoginAt).toLocaleString('fr-FR')} +
+
Dernière connexion
+
+
+ )}
- {error && ( -
- {error} -
- )} + {/* Formulaire de modification */} +
+

+ + + + Modifier le profil +

- {success && ( -
- {success} -
- )} + +
+
+ + handleChange('firstName', e.target.value)} + placeholder="Votre prénom" + /> +
-
- +
+ + handleChange('lastName', e.target.value)} + placeholder="Votre nom de famille" + /> +
+
+ +
+ + handleChange('name', e.target.value)} + placeholder="Nom d'affichage personnalisé" + /> +

+ Si vide, sera généré automatiquement à partir du prénom et nom +

+
+ +
+ + handleChange('avatar', e.target.value)} + placeholder="https://example.com/avatar.jpg" + /> + {formData.avatar && ( +
+ Aperçu: + {/* eslint-disable-next-line @next/next/no-img-element */} + Aperçu avatar { + e.currentTarget.style.display = 'none' + }} + /> +
+ )} +
+ + {error && ( +
+ + + + {error} +
+ )} + + {success && ( +
+ + + + {success} +
+ )} + +
+ +
+
- +
diff --git a/src/components/AuthButton.tsx b/src/components/AuthButton.tsx index bbc80b5..5cb5dc9 100644 --- a/src/components/AuthButton.tsx +++ b/src/components/AuthButton.tsx @@ -36,9 +36,18 @@ export function AuthButton() { className="p-1 h-auto" title={`Profil - ${session.user?.email}`} > - - - + {session.user?.avatar ? ( + // eslint-disable-next-line @next/next/no-img-element + Avatar + ) : ( + + + + )}