From 67131f647028c08151e75e07eb90b3941ecf0669 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 9 Dec 2025 14:11:47 +0100 Subject: [PATCH] Refactor page components to use NavigationWrapper and integrate Prisma for data fetching. Update EventsSection and LeaderboardSection to accept props for events and leaderboard data, enhancing performance and user experience. Implement user authentication in ProfilePage and AdminPage, ensuring secure access to user data. --- app/admin/page.tsx | 344 ++------------------ app/events/page.tsx | 19 +- app/leaderboard/page.tsx | 49 ++- app/page.tsx | 16 +- app/profile/page.tsx | 518 ++---------------------------- components/AdminPanel.tsx | 281 ++++++++++++++++ components/EventsPageSection.tsx | 37 +-- components/EventsSection.tsx | 35 +- components/LeaderboardSection.tsx | 40 +-- components/Navigation.tsx | 31 +- components/NavigationWrapper.tsx | 42 +++ components/PlayerStats.tsx | 102 ++++-- components/ProfileForm.tsx | 446 +++++++++++++++++++++++++ lib/preferences.ts | 25 ++ 14 files changed, 1041 insertions(+), 944 deletions(-) create mode 100644 components/AdminPanel.tsx create mode 100644 components/NavigationWrapper.tsx create mode 100644 components/ProfileForm.tsx create mode 100644 lib/preferences.ts diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 2409d22..a4bbac6 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,328 +1,42 @@ -"use client"; +import { redirect } from "next/navigation"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { Role } from "@/prisma/generated/prisma/client"; +import NavigationWrapper from "@/components/NavigationWrapper"; +import AdminPanel from "@/components/AdminPanel"; -import { useEffect, useState } from "react"; -import { useSession } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import Navigation from "@/components/Navigation"; -import ImageSelector from "@/components/ImageSelector"; -import UserManagement from "@/components/UserManagement"; -import EventManagement from "@/components/EventManagement"; +export default async function AdminPage() { + const session = await auth(); -interface SitePreferences { - id: string; - homeBackground: string | null; - eventsBackground: string | null; - leaderboardBackground: string | null; -} + if (!session?.user) { + redirect("/login"); + } -type AdminSection = "preferences" | "users" | "events"; + if (session.user.role !== Role.ADMIN) { + redirect("/"); + } -export default function AdminPage() { - const { data: session, status } = useSession(); - const router = useRouter(); - const [activeSection, setActiveSection] = - useState("preferences"); - const [preferences, setPreferences] = useState(null); - const [loading, setLoading] = useState(true); - const [isEditing, setIsEditing] = useState(false); - const [formData, setFormData] = useState({ - homeBackground: "", - eventsBackground: "", - leaderboardBackground: "", + // Récupérer les préférences globales du site + let sitePreferences = await prisma.sitePreferences.findUnique({ + where: { id: "global" }, }); - useEffect(() => { - if (status === "unauthenticated") { - router.push("/login"); - return; - } - - if (status === "authenticated" && session?.user?.role !== "ADMIN") { - router.push("/"); - return; - } - - if (status === "authenticated" && session?.user?.role === "ADMIN") { - fetchPreferences(); - } - }, [status, session, router]); - - const fetchPreferences = async () => { - try { - const response = await fetch("/api/admin/preferences"); - if (response.ok) { - const data = await response.json(); - setPreferences(data); - setFormData({ - homeBackground: data.homeBackground || "", - eventsBackground: data.eventsBackground || "", - leaderboardBackground: data.leaderboardBackground || "", - }); - } - } catch (error) { - console.error("Error fetching preferences:", error); - } finally { - setLoading(false); - } - }; - - const handleEdit = () => { - setIsEditing(true); - }; - - const handleSave = async () => { - try { - const response = await fetch("/api/admin/preferences", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(formData), - }); - - if (response.ok) { - await fetchPreferences(); - setIsEditing(false); - } - } catch (error) { - console.error("Error updating preferences:", error); - } - }; - - const handleCancel = () => { - setIsEditing(false); - if (preferences) { - setFormData({ - homeBackground: preferences.homeBackground || "", - eventsBackground: preferences.eventsBackground || "", - leaderboardBackground: preferences.leaderboardBackground || "", - }); - } - }; - - if (status === "loading" || loading) { - return ( -
- -
- Chargement... -
-
- ); + // Si elles n'existent pas, créer une entrée par défaut + if (!sitePreferences) { + sitePreferences = await prisma.sitePreferences.create({ + data: { + id: "global", + homeBackground: null, + eventsBackground: null, + leaderboardBackground: null, + }, + }); } return (
- -
-
-

- - ADMIN - -

- - {/* Navigation Tabs */} -
- - - -
- - {activeSection === "preferences" && ( -
-

- Préférences UI Globales -

-
-
-
-
-

- Images de fond du site -

-

- Ces préférences s'appliquent à tous les utilisateurs -

-
- {!isEditing && ( - - )} -
- - {isEditing ? ( -
- - setFormData({ - ...formData, - homeBackground: url, - }) - } - label="Background Home" - /> - - setFormData({ - ...formData, - eventsBackground: url, - }) - } - label="Background Events" - /> - - setFormData({ - ...formData, - leaderboardBackground: url, - }) - } - label="Background Leaderboard" - /> -
- - -
-
- ) : ( -
-
- - Home: - - {preferences?.homeBackground ? ( -
- Home background { - e.currentTarget.src = "/got-2.jpg"; - }} - /> - - {preferences.homeBackground} - -
- ) : ( - Par défaut - )} -
-
- - Events: - - {preferences?.eventsBackground ? ( -
- Events background { - e.currentTarget.src = "/got-2.jpg"; - }} - /> - - {preferences.eventsBackground} - -
- ) : ( - Par défaut - )} -
-
- - Leaderboard: - - {preferences?.leaderboardBackground ? ( -
- Leaderboard background { - e.currentTarget.src = "/got-2.jpg"; - }} - /> - - {preferences.leaderboardBackground} - -
- ) : ( - Par défaut - )} -
-
- )} -
-
-
- )} - - {activeSection === "users" && ( -
-

- Gestion des Utilisateurs -

- -
- )} - - {activeSection === "events" && ( -
-

- Gestion des Événements -

- -
- )} -
-
+ +
); } diff --git a/app/events/page.tsx b/app/events/page.tsx index bb7b473..8960bdd 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -1,12 +1,21 @@ -import Navigation from "@/components/Navigation"; +import NavigationWrapper from "@/components/NavigationWrapper"; import EventsPageSection from "@/components/EventsPageSection"; +import { prisma } from "@/lib/prisma"; +import { getBackgroundImage } from "@/lib/preferences"; + +export default async function EventsPage() { + const events = await prisma.event.findMany({ + orderBy: { + date: "asc", + }, + }); + + const backgroundImage = await getBackgroundImage("events", "/got-2.jpg"); -export default function EventsPage() { return (
- - + +
); } - diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx index 315b74b..07f623b 100644 --- a/app/leaderboard/page.tsx +++ b/app/leaderboard/page.tsx @@ -1,12 +1,51 @@ -import Navigation from "@/components/Navigation"; +import NavigationWrapper from "@/components/NavigationWrapper"; import LeaderboardSection from "@/components/LeaderboardSection"; +import { prisma } from "@/lib/prisma"; +import { getBackgroundImage } from "@/lib/preferences"; + +interface LeaderboardEntry { + rank: number; + username: string; + score: number; + level: number; + avatar: string | null; +} + +export default async function LeaderboardPage() { + const users = await prisma.user.findMany({ + orderBy: { + score: "desc", + }, + take: 10, + select: { + id: true, + username: true, + score: true, + level: true, + avatar: true, + }, + }); + + const leaderboard: LeaderboardEntry[] = users.map((user, index) => ({ + rank: index + 1, + username: user.username, + score: user.score, + level: user.level, + avatar: user.avatar, + })); + + const backgroundImage = await getBackgroundImage( + "leaderboard", + "/leaderboard-bg.jpg" + ); -export default function LeaderboardPage() { return (
- - + +
); } - diff --git a/app/page.tsx b/app/page.tsx index cb21750..670f317 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,21 @@ -import Navigation from "@/components/Navigation"; +import NavigationWrapper from "@/components/NavigationWrapper"; import HeroSection from "@/components/HeroSection"; import EventsSection from "@/components/EventsSection"; +import { prisma } from "@/lib/prisma"; + +export default async function Home() { + const events = await prisma.event.findMany({ + orderBy: { + date: "asc", + }, + take: 3, + }); -export default function Home() { return (
- + - +
); } diff --git a/app/profile/page.tsx b/app/profile/page.tsx index 1acbe1f..eedbd92 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -1,498 +1,44 @@ -"use client"; +import { redirect } from "next/navigation"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { getBackgroundImage } from "@/lib/preferences"; +import NavigationWrapper from "@/components/NavigationWrapper"; +import ProfileForm from "@/components/ProfileForm"; -import { useEffect, useState, useRef } from "react"; -import { useSession } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import Navigation from "@/components/Navigation"; -import { useBackgroundImage } from "@/hooks/usePreferences"; +export default async function ProfilePage() { + const session = await auth(); -interface UserProfile { - id: string; - email: string; - username: string; - avatar: string | null; - hp: number; - maxHp: number; - xp: number; - maxXp: number; - level: number; - score: number; - createdAt: string; -} - -const formatNumber = (num: number): string => { - return num.toLocaleString("en-US"); -}; - -export default function ProfilePage() { - const { data: session, status } = useSession(); - const router = useRouter(); - const backgroundImage = useBackgroundImage("home", "/got-background.jpg"); - const [profile, setProfile] = useState(null); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(null); - - const [username, setUsername] = useState(""); - const [avatar, setAvatar] = useState(null); - const fileInputRef = useRef(null); - const [uploadingAvatar, setUploadingAvatar] = useState(false); - - // Password change form state - const [showPasswordForm, setShowPasswordForm] = useState(false); - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - const [changingPassword, setChangingPassword] = useState(false); - - useEffect(() => { - if (status === "unauthenticated") { - router.push("/login"); - return; - } - - if (status === "authenticated" && session?.user) { - fetchProfile(); - } - }, [status, session, router]); - - const fetchProfile = async () => { - try { - const response = await fetch("/api/profile"); - if (response.ok) { - const data = await response.json(); - setProfile(data); - setUsername(data.username); - setAvatar(data.avatar); - } else { - setError("Erreur lors du chargement du profil"); - } - } catch (err) { - console.error("Error fetching profile:", err); - setError("Erreur lors du chargement du profil"); - } finally { - setLoading(false); - } - }; - - const handleAvatarUpload = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - - setUploadingAvatar(true); - setError(null); - - try { - const formData = new FormData(); - formData.append("file", file); - - const response = await fetch("/api/profile/avatar", { - method: "POST", - body: formData, - }); - - if (response.ok) { - const data = await response.json(); - setAvatar(data.url); - setSuccess("Avatar mis à jour avec succès"); - setTimeout(() => setSuccess(null), 3000); - } else { - const errorData = await response.json(); - setError(errorData.error || "Erreur lors de l'upload de l'avatar"); - } - } catch (err) { - console.error("Error uploading avatar:", err); - setError("Erreur lors de l'upload de l'avatar"); - } finally { - setUploadingAvatar(false); - if (fileInputRef.current) { - fileInputRef.current.value = ""; - } - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setSaving(true); - setError(null); - setSuccess(null); - - try { - const response = await fetch("/api/profile", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username, - avatar, - }), - }); - - if (response.ok) { - const data = await response.json(); - setProfile(data); - setSuccess("Profil mis à jour avec succès"); - setTimeout(() => setSuccess(null), 3000); - } else { - const errorData = await response.json(); - setError(errorData.error || "Erreur lors de la mise à jour"); - } - } catch (err) { - console.error("Error updating profile:", err); - setError("Erreur lors de la mise à jour du profil"); - } finally { - setSaving(false); - } - }; - - const handlePasswordChange = async (e: React.FormEvent) => { - e.preventDefault(); - setChangingPassword(true); - setError(null); - setSuccess(null); - - try { - const response = await fetch("/api/profile/password", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - currentPassword, - newPassword, - confirmPassword, - }), - }); - - if (response.ok) { - setSuccess("Mot de passe modifié avec succès"); - setCurrentPassword(""); - setNewPassword(""); - setConfirmPassword(""); - setShowPasswordForm(false); - setTimeout(() => setSuccess(null), 3000); - } else { - const errorData = await response.json(); - setError(errorData.error || "Erreur lors de la modification du mot de passe"); - } - } catch (err) { - console.error("Error changing password:", err); - setError("Erreur lors de la modification du mot de passe"); - } finally { - setChangingPassword(false); - } - }; - - if (loading || status === "loading") { - return ( -
- -
-
Chargement...
-
-
- ); + if (!session?.user) { + redirect("/login"); } - if (!profile) { - return ( -
- -
-
Erreur lors du chargement du profil
-
-
- ); + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + select: { + id: true, + email: true, + username: true, + avatar: true, + hp: true, + maxHp: true, + xp: true, + maxXp: true, + level: true, + score: true, + createdAt: true, + }, + }); + + if (!user) { + redirect("/login"); } - const hpPercentage = (profile.hp / profile.maxHp) * 100; - const xpPercentage = (profile.xp / profile.maxXp) * 100; - - const hpColor = - hpPercentage > 60 - ? "from-green-600 to-green-700" - : hpPercentage > 30 - ? "from-yellow-600 to-orange-700" - : "from-red-700 to-red-900"; + const backgroundImage = await getBackgroundImage("home", "/got-background.jpg"); return (
- -
- {/* Background Image */} -
- {/* Dark overlay for readability */} -
-
- - {/* Content */} -
- {/* Title Section */} -
-

- - PROFIL - -

-
- - Gérez votre profil - -
-
- - {/* Profile Card */} -
-
- {/* Messages */} - {error && ( -
- {error} -
- )} - {success && ( -
- {success} -
- )} - - {/* Avatar Section */} -
-
-
- {avatar ? ( - {username} - ) : ( - - {username.charAt(0).toUpperCase()} - - )} -
- {uploadingAvatar && ( -
-
Upload...
-
- )} -
-
- - -
-
- - {/* Username Field */} -
- - setUsername(e.target.value)} - className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition" - required - minLength={3} - maxLength={20} - /> -

- 3-20 caractères -

-
- - {/* Stats Display */} -
-

- Statistiques -

- -
-
-
Score
-
- {formatNumber(profile.score)} -
-
-
-
Niveau
-
- Lv.{profile.level} -
-
-
- - {/* HP Bar */} -
-
- HP - {profile.hp} / {profile.maxHp} -
-
-
-
-
- - {/* XP Bar */} -
-
- XP - {formatNumber(profile.xp)} / {formatNumber(profile.maxXp)} -
-
-
-
-
-
- - {/* Email (read-only) */} -
- - -
- - {/* Submit Button */} -
- -
- - - {/* Password Change Section - Separate form */} -
-
-

- Mot de passe -

- {!showPasswordForm && ( - - )} -
- - {showPasswordForm && ( -
-
- - setCurrentPassword(e.target.value)} - className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition" - required - /> -
- -
- - setNewPassword(e.target.value)} - className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition" - required - minLength={6} - /> -

- Minimum 6 caractères -

-
- -
- - setConfirmPassword(e.target.value)} - className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition" - required - minLength={6} - /> -
- -
- - -
-
- )} -
-
-
-
+ +
); } diff --git a/components/AdminPanel.tsx b/components/AdminPanel.tsx new file mode 100644 index 0000000..cabd99e --- /dev/null +++ b/components/AdminPanel.tsx @@ -0,0 +1,281 @@ +"use client"; + +import { useState } from "react"; +import ImageSelector from "@/components/ImageSelector"; +import UserManagement from "@/components/UserManagement"; +import EventManagement from "@/components/EventManagement"; + +interface SitePreferences { + id: string; + homeBackground: string | null; + eventsBackground: string | null; + leaderboardBackground: string | null; +} + +interface AdminPanelProps { + initialPreferences: SitePreferences; +} + +type AdminSection = "preferences" | "users" | "events"; + +export default function AdminPanel({ initialPreferences }: AdminPanelProps) { + const [activeSection, setActiveSection] = + useState("preferences"); + const [preferences, setPreferences] = useState( + initialPreferences + ); + const [isEditing, setIsEditing] = useState(false); + const [formData, setFormData] = useState({ + homeBackground: initialPreferences.homeBackground || "", + eventsBackground: initialPreferences.eventsBackground || "", + leaderboardBackground: initialPreferences.leaderboardBackground || "", + }); + + const handleEdit = () => { + setIsEditing(true); + }; + + const handleSave = async () => { + try { + const response = await fetch("/api/admin/preferences", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const data = await response.json(); + setPreferences(data); + setIsEditing(false); + } + } catch (error) { + console.error("Error updating preferences:", error); + } + }; + + const handleCancel = () => { + setIsEditing(false); + if (preferences) { + setFormData({ + homeBackground: preferences.homeBackground || "", + eventsBackground: preferences.eventsBackground || "", + leaderboardBackground: preferences.leaderboardBackground || "", + }); + } + }; + + return ( +
+
+

+ + ADMIN + +

+ + {/* Navigation Tabs */} +
+ + + +
+ + {activeSection === "preferences" && ( +
+

+ Préférences UI Globales +

+
+
+
+
+

+ Images de fond du site +

+

+ Ces préférences s'appliquent à tous les utilisateurs +

+
+ {!isEditing && ( + + )} +
+ + {isEditing ? ( +
+ + setFormData({ + ...formData, + homeBackground: url, + }) + } + label="Background Home" + /> + + setFormData({ + ...formData, + eventsBackground: url, + }) + } + label="Background Events" + /> + + setFormData({ + ...formData, + leaderboardBackground: url, + }) + } + label="Background Leaderboard" + /> +
+ + +
+
+ ) : ( +
+
+ + Home: + + {preferences?.homeBackground ? ( +
+ Home background { + e.currentTarget.src = "/got-2.jpg"; + }} + /> + + {preferences.homeBackground} + +
+ ) : ( + Par défaut + )} +
+
+ + Events: + + {preferences?.eventsBackground ? ( +
+ Events background { + e.currentTarget.src = "/got-2.jpg"; + }} + /> + + {preferences.eventsBackground} + +
+ ) : ( + Par défaut + )} +
+
+ + Leaderboard: + + {preferences?.leaderboardBackground ? ( +
+ Leaderboard background { + e.currentTarget.src = "/got-2.jpg"; + }} + /> + + {preferences.leaderboardBackground} + +
+ ) : ( + Par défaut + )} +
+
+ )} +
+
+
+ )} + + {activeSection === "users" && ( +
+

+ Gestion des Utilisateurs +

+ +
+ )} + + {activeSection === "events" && ( +
+

+ Gestion des Événements +

+ +
+ )} +
+
+ ); +} + diff --git a/components/EventsPageSection.tsx b/components/EventsPageSection.tsx index 4e136f4..04b5eac 100644 --- a/components/EventsPageSection.tsx +++ b/components/EventsPageSection.tsx @@ -1,8 +1,5 @@ "use client"; -import { useEffect, useState } from "react"; -import { useBackgroundImage } from "@/hooks/usePreferences"; - interface Event { id: string; date: string; @@ -12,6 +9,11 @@ interface Event { status: "UPCOMING" | "LIVE" | "PAST"; } +interface EventsPageSectionProps { + events: Event[]; + backgroundImage: string; +} + const getEventTypeColor = (type: Event["type"]) => { switch (type) { case "SUMMIT": @@ -65,31 +67,10 @@ const getStatusBadge = (status: Event["status"]) => { } }; -export default function EventsPageSection() { - const [events, setEvents] = useState([]); - const [loading, setLoading] = useState(true); - const backgroundImage = useBackgroundImage("events", "/got-2.jpg"); - - useEffect(() => { - fetch("/api/events") - .then((res) => res.json()) - .then((data) => { - setEvents(data); - setLoading(false); - }) - .catch((err) => { - console.error("Error fetching events:", err); - setLoading(false); - }); - }, []); - - if (loading) { - return ( -
-
Chargement...
-
- ); - } +export default function EventsPageSection({ + events, + backgroundImage, +}: EventsPageSectionProps) { return (
{/* Background Image */} diff --git a/components/EventsSection.tsx b/components/EventsSection.tsx index bb680c1..e912c45 100644 --- a/components/EventsSection.tsx +++ b/components/EventsSection.tsx @@ -1,41 +1,14 @@ -"use client"; - -import { useEffect, useState } from "react"; - interface Event { id: string; date: string; name: string; } -export default function EventsSection() { - const [events, setEvents] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - fetch("/api/events") - .then((res) => res.json()) - .then((data) => { - // Prendre seulement les 3 premiers événements pour la section d'accueil - setEvents(data.slice(0, 3)); - setLoading(false); - }) - .catch((err) => { - console.error("Error fetching events:", err); - setLoading(false); - }); - }, []); - - if (loading) { - return ( -
-
- Chargement... -
-
- ); - } +interface EventsSectionProps { + events: Event[]; +} +export default function EventsSection({ events }: EventsSectionProps) { if (events.length === 0) { return null; } diff --git a/components/LeaderboardSection.tsx b/components/LeaderboardSection.tsx index 9ef0260..5a13d79 100644 --- a/components/LeaderboardSection.tsx +++ b/components/LeaderboardSection.tsx @@ -1,8 +1,5 @@ "use client"; -import { useEffect, useState } from "react"; -import { useBackgroundImage } from "@/hooks/usePreferences"; - interface LeaderboardEntry { rank: number; username: string; @@ -11,39 +8,20 @@ interface LeaderboardEntry { avatar?: string | null; } +interface LeaderboardSectionProps { + leaderboard: LeaderboardEntry[]; + backgroundImage: string; +} + // Format number with consistent locale to avoid hydration mismatch const formatScore = (score: number): string => { return score.toLocaleString("en-US"); }; -export default function LeaderboardSection() { - const [leaderboard, setLeaderboard] = useState([]); - const [loading, setLoading] = useState(true); - const backgroundImage = useBackgroundImage( - "leaderboard", - "/leaderboard-bg.jpg" - ); - - useEffect(() => { - fetch("/api/leaderboard") - .then((res) => res.json()) - .then((data) => { - setLeaderboard(data); - setLoading(false); - }) - .catch((err) => { - console.error("Error fetching leaderboard:", err); - setLoading(false); - }); - }, []); - - if (loading) { - return ( -
-
Chargement...
-
- ); - } +export default function LeaderboardSection({ + leaderboard, + backgroundImage, +}: LeaderboardSectionProps) { return (
{/* Background Image */} diff --git a/components/Navigation.tsx b/components/Navigation.tsx index 95cc029..c26d49f 100644 --- a/components/Navigation.tsx +++ b/components/Navigation.tsx @@ -4,9 +4,32 @@ import Link from "next/link"; import { useSession, signOut } from "next-auth/react"; import PlayerStats from "./PlayerStats"; -export default function Navigation() { +interface UserData { + username: string; + avatar: string | null; + hp: number; + maxHp: number; + xp: number; + maxXp: number; + level: number; +} + +interface NavigationProps { + initialUserData?: UserData | null; + initialIsAdmin?: boolean; +} + +export default function Navigation({ + initialUserData, + initialIsAdmin, +}: NavigationProps) { const { data: session } = useSession(); + // Utiliser initialUserData pour déterminer l'état de connexion pendant l'hydratation + // Cela évite le clignottement au reload + const isAuthenticated = initialUserData !== null || session !== null; + const isAdmin = initialIsAdmin ?? session?.user?.role === "ADMIN"; + return (