import { desc, eq, inArray } from "drizzle-orm"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { db, User, UsersCurrency, UsersSettings } from "@/lib/db"; export const metadata = { title: "Leaderboard" }; // Currency type ids (see UsersCurrency in src/db/schema.ts): // Credits = -1 (lives on users.credits), Duckets = 0, Diamonds = 5. const TABS = [ { key: "credits", label: "Credits", coin: "credits" }, { key: "diamonds", label: "Diamonds", coin: "diamonds" }, { key: "duckets", label: "Duckets", coin: "duckets" }, { key: "achievement", label: "Achievement", coin: "" }, { key: "respects", label: "Respects", coin: "" }, { key: "onlinetime", label: "Online Time", coin: "" }, ] as const; type TabKey = (typeof TABS)[number]["key"]; const CURRENCY_TYPE: Record<"diamonds" | "duckets", number> = { diamonds: 5, duckets: 0, }; function formatValue(key: TabKey, value: number): string { if (key === "onlinetime") { const hours = Math.floor(value / 3600); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ${hours % 24}h`; return `${hours}h`; } return value.toLocaleString(); } type Row = { username: string; look: string; value: number }; async function loadCreditsRows(): Promise { try { const users = await db .select({ username: User.username, look: User.look, credits: User.credits, }) .from(User) .orderBy(desc(User.credits)) .limit(20); return users.map((u) => ({ username: u.username, look: u.look, value: u.credits, })); } catch { return []; } } async function loadCurrencyRows(type: number): Promise { try { const top = await db .select({ userId: UsersCurrency.userId, amount: UsersCurrency.amount, }) .from(UsersCurrency) .where(eq(UsersCurrency.type, type)) .orderBy(desc(UsersCurrency.amount)) .limit(20); if (top.length === 0) return []; const users = await db .select({ id: User.id, username: User.username, look: User.look }) .from(User) .where( inArray( User.id, top.map((t) => t.userId), ), ); const byId = new Map(users.map((u) => [u.id, u])); return top .map((t) => { const u = byId.get(t.userId); if (!u) return null; return { username: u.username, look: u.look, value: t.amount }; }) .filter((r): r is Row => r !== null); } catch { return []; } } async function loadSettingsRows( field: "achievementScore" | "respectsReceived" | "onlineTime", ): Promise { try { const column = UsersSettings[field]; const top = await db .select({ userId: UsersSettings.userId, value: column, }) .from(UsersSettings) .orderBy(desc(column)) .limit(20); if (top.length === 0) return []; const users = await db .select({ id: User.id, username: User.username, look: User.look }) .from(User) .where( inArray( User.id, top.map((t) => t.userId), ), ); const byId = new Map(users.map((u) => [u.id, u])); return top .map((t) => { const u = byId.get(t.userId); if (!u) return null; return { username: u.username, look: u.look, value: Number(t.value ?? 0), }; }) .filter((r): r is Row => r !== null); } catch { return []; } } const SETTINGS_FIELD: Record< "achievement" | "respects" | "onlinetime", "achievementScore" | "respectsReceived" | "onlineTime" > = { achievement: "achievementScore", respects: "respectsReceived", onlinetime: "onlineTime", }; export default async function LeaderboardPage({ searchParams, }: { searchParams: Promise<{ type?: string }>; }) { const t = await getTranslations("pages.leaderboard"); const { type } = await searchParams; const active: TabKey = TABS.some((tab) => tab.key === type) ? (type as TabKey) : "credits"; const activeTab = TABS.find((tab) => tab.key === active) ?? TABS[0]; const rows = await (active === "credits" ? loadCreditsRows() : active === "diamonds" || active === "duckets" ? // eslint-disable-next-line security/detect-object-injection -- active validated as "diamonds"|"duckets" in this branch loadCurrencyRows(CURRENCY_TYPE[active]) : loadSettingsRows( SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD], )); const valueOnly = active === "achievement" || active === "respects" || active === "onlinetime"; return (
{TABS.map((t2) => ( {t(`tab_${t2.key}`)} ))}
{rows.length === 0 ? ( {t("emptyState")} ) : ( {rows.map((row, i) => { return ( ); })}
# {t("colAvatar")} {t("colPlayer")} {t(`tab_${active}`)}
{row.username} {valueOnly ? ( {formatValue(active, row.value)} ) : ( {row.value.toLocaleString()} )}
)}
); }