Refactor component imports and structure: Update import paths for various components to improve organization, moving them into appropriate subdirectories. Remove unused components related to user and event management, enhancing code clarity and maintainability across the application.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s

This commit is contained in:
Julien Froidefond
2025-12-12 16:48:41 +01:00
parent 880e96d6e4
commit 97db800c73
27 changed files with 23 additions and 23 deletions

View File

@@ -0,0 +1,261 @@
"use client";
import { useState } from "react";
import {
Avatar,
Modal,
CloseButton,
Card,
BackgroundSection,
SectionTitle,
} from "@/components/ui";
interface LeaderboardEntry {
rank: number;
username: string;
email: string;
score: number;
level: number;
avatar?: string | null;
bio?: string | null;
characterClass?: 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({
leaderboard,
backgroundImage,
}: LeaderboardSectionProps) {
const [selectedEntry, setSelectedEntry] = useState<LeaderboardEntry | null>(
null
);
return (
<BackgroundSection backgroundImage={backgroundImage}>
{/* Title Section */}
<SectionTitle
variant="gradient"
size="lg"
subtitle="Top Players"
className="mb-12 overflow-hidden"
>
LEADERBOARD
</SectionTitle>
{/* Leaderboard Table */}
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg backdrop-blur-sm overflow-x-auto">
{/* Header */}
<div className="bg-gray-900/80 border-b border-pixel-gold/30 grid grid-cols-12 gap-2 sm:gap-4 p-2 sm:p-4 font-bold text-[10px] sm:text-xs uppercase tracking-widest text-gray-300">
<div className="col-span-2 sm:col-span-1 text-center">Rank</div>
<div className="col-span-5 sm:col-span-6">Player</div>
<div className="col-span-3 text-right">Score</div>
<div className="col-span-2 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-2 sm:gap-4 p-2 sm:p-4 hover:bg-gray-900/50 transition relative ${
entry.rank <= 3
? "bg-gradient-to-r from-pixel-gold/10 via-pixel-gold/5 to-transparent"
: "bg-black/40"
}`}
>
{/* Rank */}
<div className="col-span-2 sm:col-span-1 flex items-center justify-center">
<span
className={`inline-flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 rounded-full font-bold text-xs sm:text-sm ${
entry.rank === 1
? "bg-gradient-to-br from-pixel-gold to-orange-500 text-black shadow-lg shadow-pixel-gold/50"
: entry.rank === 2
? "bg-gradient-to-br from-gray-400 to-gray-500 text-black"
: entry.rank === 3
? "bg-gradient-to-br from-orange-700 to-orange-800 text-white"
: "bg-gray-900 text-gray-400 border border-gray-800"
}`}
>
{entry.rank}
</span>
</div>
{/* Player */}
<div className="col-span-5 sm:col-span-6 flex items-center gap-2 sm:gap-3 min-w-0">
<Avatar
src={entry.avatar}
username={entry.username}
size="sm"
className="flex-shrink-0"
borderClassName="border-pixel-gold/30"
/>
<div
className="flex items-center gap-1 sm:gap-2 cursor-pointer hover:opacity-80 transition min-w-0"
onClick={() => setSelectedEntry(entry)}
>
<span
className={`font-bold text-xs sm:text-sm truncate ${
entry.rank <= 3 ? "text-pixel-gold" : "text-white"
}`}
>
{entry.username}
</span>
{entry.characterClass && (
<span className="text-xs text-gray-400 uppercase tracking-wider">
[{entry.characterClass === "WARRIOR" && "⚔️"}
{entry.characterClass === "MAGE" && "🔮"}
{entry.characterClass === "ROGUE" && "🗡️"}
{entry.characterClass === "RANGER" && "🏹"}
{entry.characterClass === "PALADIN" && "🛡️"}
{entry.characterClass === "ENGINEER" && "⚙️"}
{entry.characterClass === "MERCHANT" && "💰"}
{entry.characterClass === "SCHOLAR" && "📚"}
{entry.characterClass === "BERSERKER" && "🔥"}
{entry.characterClass === "NECROMANCER" && "💀"}]
</span>
)}
{entry.rank <= 3 && (
<span className="text-pixel-gold text-xs"></span>
)}
</div>
</div>
{/* Score */}
<div className="col-span-3 flex items-center justify-end">
<span className="font-mono text-gray-300 text-xs sm:text-sm">
{formatScore(entry.score)}
</span>
</div>
{/* Level */}
<div className="col-span-2 flex items-center justify-end">
<span className="font-bold text-gray-400 text-xs sm:text-sm">
Lv.{entry.level}
</span>
</div>
</div>
))}
</div>
</div>
{/* Footer Info */}
<div className="mt-8 text-center">
<p className="text-gray-500 text-sm">
Compete with players worldwide and climb the ranks!
</p>
<p className="text-gray-600 text-xs mt-2">Rankings update every hour</p>
</div>
{/* Character Modal */}
{selectedEntry && (
<Modal
isOpen={!!selectedEntry}
onClose={() => setSelectedEntry(null)}
size="md"
>
<div className="p-4 sm:p-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl sm:text-3xl font-bold text-pixel-gold uppercase tracking-wider break-words">
{selectedEntry.username}
</h2>
<CloseButton onClick={() => setSelectedEntry(null)} size="md" />
</div>
{/* Avatar and Class */}
<div className="flex items-center gap-4 sm:gap-6 mb-6">
<Avatar
src={selectedEntry.avatar}
username={selectedEntry.username}
size="lg"
className="flex-shrink-0"
borderClassName="border-2 sm: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">
<Card variant="default" className="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>
</Card>
<Card variant="default" className="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>
</Card>
</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>
</Modal>
)}
</BackgroundSection>
);
}