diff --git a/src/app/leaderboard/page.tsx b/src/app/leaderboard/page.tsx index e8e77c0..0507d27 100644 --- a/src/app/leaderboard/page.tsx +++ b/src/app/leaderboard/page.tsx @@ -1,38 +1,229 @@ +"use client"; + +import { useState } from "react"; import { mockLeaderboard } from "@/data/mock-leaderboard"; +type SortKey = "earned" | "bounties" | "reputation"; +type SortOrder = "asc" | "desc"; + export default function LeaderboardPage() { - const sorted = [...mockLeaderboard].sort((a, b) => b.earned - a.earned); + const [sortKey, setSortKey] = useState("earned"); + const [sortOrder, setSortOrder] = useState("desc"); + const [searchQuery, setSearchQuery] = useState(""); + + // Filter by search + const filtered = mockLeaderboard.filter((dev) => + dev.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Sort with multiple keys for stable ordering + const sorted = [...filtered].sort((a, b) => { + // Primary sort + const aVal = a[sortKey]; + const bVal = b[sortKey]; + + if (sortOrder === "desc") { + if (bVal !== aVal) return bVal - aVal; + // Secondary sort: earned + if (b.earned !== a.earned) return b.earned - a.earned; + // Tertiary sort: name + return a.name.localeCompare(b.name); + } else { + if (aVal !== bVal) return aVal - bVal; + if (a.earned !== b.earned) return a.earned - b.earned; + return a.name.localeCompare(b.name); + } + }); + + // Calculate rank with ties + const getRank = (index: number): number => { + if (index === 0) return 1; + const current = sorted[index]; + const previous = sorted[index - 1]; + if (current[sortKey] === previous[sortKey]) { + return getRank(index - 1); // Same rank as previous + } + return index + 1; + }; + + // Toggle sort + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortOrder(sortOrder === "desc" ? "asc" : "desc"); + } else { + setSortKey(key); + setSortOrder("desc"); + } + }; + + // Top 3 styling + const getRankStyle = (rank: number) => { + switch (rank) { + case 1: + return "bg-yellow-50 border-l-4 border-l-yellow-400"; + case 2: + return "bg-slate-50 border-l-4 border-l-slate-300"; + case 3: + return "bg-orange-50 border-l-4 border-l-orange-300"; + default: + return ""; + } + }; + + const getRankBadge = (rank: number) => { + switch (rank) { + case 1: + return 🥇; + case 2: + return 🥈; + case 3: + return 🥉; + default: + return #{rank}; + } + }; return (

Leaderboard

- Build a leaderboard UI with sorting and ranking logic. + Top bounty hunters ranked by earnings, completed bounties, and reputation.

-
-
-
Rank
-
Developer
-
Bounties
-
Total Earned
+ {/* Search and Filter Controls */} +
+
+ setSearchQuery(e.target.value)} + className="w-full rounded-lg border border-slate-200 px-4 py-2 text-sm" + />
- {sorted.map((dev, index) => ( -
+ + + +
+
+ + {/* Stats Summary */} +
+
+
+ ${filtered.reduce((sum, d) => sum + d.earned, 0).toLocaleString()} +
+
Total Paid Out
+
+
+
+ {filtered.reduce((sum, d) => sum + d.bounties, 0)}
- ))} +
Bounties Completed
+
+
+
+ {filtered.length} +
+
Active Hunters
+
+
+ + {/* Leaderboard Table */} +
+
+
Rank
+
Developer
+
Bounties
+
Reputation
+
Total Earned
+
+ {sorted.length === 0 ? ( +
+ No developers found matching "{searchQuery}" +
+ ) : ( + sorted.map((dev, index) => { + const rank = getRank(index); + return ( +
+
+ {getRankBadge(rank)} +
+
+
+
+ {dev.name.charAt(0).toUpperCase()} +
+
+
{dev.name}
+
+ {dev.bounties} bounties completed +
+
+
+
+
+ + {dev.bounties} + +
+
+
+ + {dev.reputation} +
+
+
+ + ${dev.earned.toLocaleString()} + +
+
+ ); + }) + )} +
+ + {/* Footer info */} +
+ Showing {sorted.length} of {mockLeaderboard.length} hunters • + Sorted by {sortKey} ({sortOrder === "desc" ? "highest first" : "lowest first"})
);