Files
got-gaming/components/Leaderboard.tsx

252 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import Avatar from "./Avatar";
interface LeaderboardEntry {
rank: number;
username: string;
email: string;
score: number;
level: number;
avatar?: string | null;
bio?: string | null;
characterClass?: string | null;
}
// Format number with consistent locale to avoid hydration mismatch
const formatScore = (score: number): string => {
return score.toLocaleString("en-US");
};
export default function Leaderboard() {
const [leaderboard, setLeaderboard] = useState<LeaderboardEntry[]>([]);
const [loading, setLoading] = useState(true);
const [selectedEntry, setSelectedEntry] = useState<LeaderboardEntry | null>(
null
);
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 (
<section className="w-full bg-black py-16 px-6 border-t-2 border-pixel-dark-purple">
<div className="max-w-4xl mx-auto text-center text-pixel-gold">
Chargement...
</div>
</section>
);
}
return (
<section className="w-full bg-black py-16 px-6 border-t-2 border-pixel-dark-purple">
<div className="max-w-4xl mx-auto">
<h2 className="text-4xl md:text-5xl font-bold text-center mb-12 text-pixel-gold text-pixel">
LEADERBOARD
</h2>
<div className="bg-black/80 border-2 border-pixel-gold/30 rounded-lg backdrop-blur-sm">
{/* Header */}
<div className="bg-gray-900/80 border-b-2 border-pixel-gold/30 grid grid-cols-12 gap-4 p-4 font-bold text-sm text-gray-300">
<div className="col-span-1 text-center">Rank</div>
<div className="col-span-5">Player</div>
<div className="col-span-3 text-right">Score</div>
<div className="col-span-3 text-right">Level</div>
</div>
{/* Entries */}
<div className="divide-y divide-pixel-gold/10 overflow-visible">
{leaderboard.map((entry) => (
<div
key={entry.rank}
className={`grid grid-cols-12 gap-4 p-4 hover:bg-gray-900/50 transition relative ${
entry.rank <= 3 ? "bg-gray-950/50" : "bg-black/40"
}`}
>
<div className="col-span-1 text-center">
<span
className={`inline-block w-8 h-8 rounded-full flex items-center justify-center font-bold ${
entry.rank === 1
? "bg-pixel-gold text-black"
: entry.rank === 2
? "bg-gray-600 text-white"
: entry.rank === 3
? "bg-orange-800 text-white"
: "bg-gray-900 text-gray-400 border border-gray-800"
}`}
>
{entry.rank}
</span>
</div>
<div className="col-span-5 flex items-center gap-2">
<div
className="flex items-center gap-2 cursor-pointer hover:text-pixel-gold transition"
onClick={() => setSelectedEntry(entry)}
>
<span className="font-bold text-pixel-gold">
{entry.username}
</span>
{entry.characterClass && (
<span className="text-xs text-gray-400 uppercase tracking-wider">
[{entry.characterClass === "WARRIOR" && "⚔️ Guerrier"}
{entry.characterClass === "MAGE" && "🔮 Mage"}
{entry.characterClass === "ROGUE" && "🗡️ Voleur"}
{entry.characterClass === "RANGER" && "🏹 Rôdeur"}
{entry.characterClass === "PALADIN" && "🛡️ Paladin"}
{entry.characterClass === "ENGINEER" && "⚙️ Ingénieur"}
{entry.characterClass === "MERCHANT" && "💰 Marchand"}
{entry.characterClass === "SCHOLAR" && "📚 Érudit"}
{entry.characterClass === "BERSERKER" && "🔥 Berserker"}
{entry.characterClass === "NECROMANCER" &&
"💀 Nécromancien"}
]
</span>
)}
</div>
</div>
<div className="col-span-3 text-right flex items-center justify-end">
<span className="font-mono text-gray-300">
{formatScore(entry.score)}
</span>
</div>
<div className="col-span-3 text-right flex items-center justify-end">
<span className="font-bold text-gray-400">
Lv.{entry.level}
</span>
</div>
</div>
))}
</div>
</div>
{/* Additional info */}
<div className="mt-8 text-center text-sm text-gray-500">
<p>Compete with players worldwide and climb the ranks!</p>
</div>
</div>
{/* Character Modal */}
{selectedEntry && (
<div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={() => setSelectedEntry(null)}
>
<div
className="bg-black border-2 border-pixel-gold/70 rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="p-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold text-pixel-gold uppercase tracking-wider">
{selectedEntry.username}
</h2>
<button
onClick={() => setSelectedEntry(null)}
className="text-gray-400 hover:text-pixel-gold text-2xl font-bold transition"
>
×
</button>
</div>
{/* Avatar and Class */}
<div className="flex items-center gap-6 mb-6">
<Avatar
src={selectedEntry.avatar}
username={selectedEntry.username}
size="xl"
borderClassName="border-4 border-pixel-gold/50"
/>
<div>
<div className="text-xs text-gray-400 uppercase tracking-widest mb-2">
Rank #{selectedEntry.rank}
</div>
<div className="text-sm text-gray-300 mb-2">
{selectedEntry.email}
</div>
{selectedEntry.characterClass && (
<div className="flex items-center gap-2">
<span className="text-2xl">
{selectedEntry.characterClass === "WARRIOR" && "⚔️"}
{selectedEntry.characterClass === "MAGE" && "🔮"}
{selectedEntry.characterClass === "ROGUE" && "🗡️"}
{selectedEntry.characterClass === "RANGER" && "🏹"}
{selectedEntry.characterClass === "PALADIN" && "🛡️"}
{selectedEntry.characterClass === "ENGINEER" && "⚙️"}
{selectedEntry.characterClass === "MERCHANT" && "💰"}
{selectedEntry.characterClass === "SCHOLAR" && "📚"}
{selectedEntry.characterClass === "BERSERKER" && "🔥"}
{selectedEntry.characterClass === "NECROMANCER" && "💀"}
</span>
<span className="text-lg font-bold text-pixel-gold uppercase tracking-wider">
{selectedEntry.characterClass === "WARRIOR" &&
"Guerrier"}
{selectedEntry.characterClass === "MAGE" && "Mage"}
{selectedEntry.characterClass === "ROGUE" && "Voleur"}
{selectedEntry.characterClass === "RANGER" && "Rôdeur"}
{selectedEntry.characterClass === "PALADIN" &&
"Paladin"}
{selectedEntry.characterClass === "ENGINEER" &&
"Ingénieur"}
{selectedEntry.characterClass === "MERCHANT" &&
"Marchand"}
{selectedEntry.characterClass === "SCHOLAR" && "Érudit"}
{selectedEntry.characterClass === "BERSERKER" &&
"Berserker"}
{selectedEntry.characterClass === "NECROMANCER" &&
"Nécromancien"}
</span>
</div>
)}
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="bg-black/60 border border-pixel-gold/30 rounded p-4">
<div className="text-xs text-gray-400 uppercase tracking-widest mb-1">
Score
</div>
<div className="text-2xl font-bold text-pixel-gold">
{formatScore(selectedEntry.score)}
</div>
</div>
<div className="bg-black/60 border border-pixel-gold/30 rounded p-4">
<div className="text-xs text-gray-400 uppercase tracking-widest mb-1">
Niveau
</div>
<div className="text-2xl font-bold text-pixel-gold">
Lv.{selectedEntry.level}
</div>
</div>
</div>
{/* Bio */}
{selectedEntry.bio && (
<div className="border-t border-pixel-gold/30 pt-6">
<div className="text-xs text-pixel-gold uppercase tracking-widest mb-3 font-bold">
Bio
</div>
<p className="text-gray-200 leading-relaxed whitespace-pre-wrap break-words">
{selectedEntry.bio}
</p>
</div>
)}
</div>
</div>
</div>
)}
</section>
);
}