"use client"; import { useEffect, useState } from "react"; import { Trophy } from "lucide-react"; import type { OpordDifficulty } from "@/lib/opord-recall"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import type { PresenceParticipant } from "@/hooks/useRealtimePresence"; import { cn } from "@/lib/utils"; import { OPORD_LEADERBOARD_FIELDS, readOpordProfiles } from "./opordLeaderboardData"; type LeaderboardRow = { readonly id: number; readonly userId: number | null; readonly name: string; readonly gamesPlayed: number; readonly bestScore: number; readonly bestAccuracy: number; readonly isCurrentUser: boolean; readonly isOutsideTopTen?: boolean; }; function rankClass(index: number): string { if (index === 0) return "font-bold text-amber-400"; if (index === 1) return "font-semibold text-slate-300"; if (index === 2) return "font-semibold text-amber-700"; return "text-muted-foreground"; } export function OpordLeaderboard({ refreshKey = 0, difficulty, currentUserId, activeOperators = [], }: { readonly refreshKey?: number; readonly difficulty: OpordDifficulty; readonly currentUserId?: number | string; readonly activeOperators?: readonly PresenceParticipant[]; }) { const [rows, setRows] = useState(null); const [failed, setFailed] = useState(null); const tier = OPORD_LEADERBOARD_FIELDS[difficulty]; useEffect(() => { let cancelled = false; setFailed(null); setRows(null); const field = `progression.minigames.opordRecall.${tier.scoreField}`; const params = new URLSearchParams({ sort: `-${field}`, limit: "100", depth: "1", }); fetch(`/api/profiles?${params.toString()}`) .then(async (response) => { if (!response.ok) throw new Error("leaderboard fetch failed"); const payload: unknown = await response.json(); return readOpordProfiles(payload); }) .then((profiles) => { if (cancelled) return; const mapped: LeaderboardRow[] = profiles.map((profile) => { const user = typeof profile.user === "object" && profile.user !== null ? profile.user : null; const userId = user?.id ?? (typeof profile.user === "number" ? profile.user : null); const name = user?.displayName || user?.payloadDisplayName || profile.profileTitle || user?.username || `Operator #${profile.id}`; const opordRecall = profile.opordRecall; return { id: profile.id, userId, name, gamesPlayed: opordRecall?.gamesPlayed ?? 0, bestScore: opordRecall?.[tier.scoreField] ?? 0, bestAccuracy: opordRecall?.bestAccuracy ?? 0, isCurrentUser: userId !== null && String(userId) === String(currentUserId), }; }); const scored = mapped .filter((row) => row.bestScore > 0) .sort( (left, right) => right.bestScore - left.bestScore || right.bestAccuracy - left.bestAccuracy, ); const visible = scored.slice(0, 10); const current = mapped.find((row) => row.isCurrentUser); if (current && !visible.some((row) => row.id === current.id)) { visible.push({ ...current, isOutsideTopTen: true }); } setRows(visible); }) .catch((error: unknown) => { if (cancelled) return; setFailed(error instanceof Error ? error.message : "leaderboard fetch failed"); }); return () => { cancelled = true; }; }, [currentUserId, difficulty, refreshKey, tier.scoreField]); return (
Best score for the selected briefing tier.
{activeOperators.length}{" "} {activeOperators.length === 1 ? "operator is" : "operators are"} studying briefings {activeOperators.length > 0 && ( ({activeOperators.map((operator) => operator.name).join(", ")}) )}
{failed !== null ? (

Could not load the leaderboard.

) : rows === null ? (
) : rows.length === 0 ? (

No scores logged for this tier yet. Be the first to brief up.

) : (
{rows.map((row, index) => ( ))}
# Operator Games Accuracy Score
{row.isOutsideTopTen ? "-" : index + 1} {row.name} {row.isCurrentUser && ( (you) )} {row.isOutsideTopTen && ( unranked )} {row.userId !== null && activeOperators.some((operator) => operator.id === row.userId) && ( )} {row.gamesPlayed} {Math.round(row.bestAccuracy * 100)}% {row.bestScore}
)}
); }