import { desc, eq } from "drizzle-orm"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import Link from "@/components/link"; import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui"; import { CurrencyIcon, type CurrencyKind, } from "@/components/shared/currency-icon"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { cached } from "@/lib/cache"; import { db, User, UsersCurrency, UsersSettings } from "@/lib/db"; export async function generateMetadata(): Promise { const t = await getTranslations("pages.leaderboard"); return { title: t("title"), description: t("subtitle", { currency: "credits" }), openGraph: { title: t("title"), description: t("subtitle", { currency: "credits" }), type: "website", }, }; } // 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" }, { key: "diamonds", label: "Diamonds" }, { key: "duckets", label: "Duckets" }, { key: "achievement", label: "Achievement" }, { key: "respects", label: "Respects" }, { key: "onlinetime", label: "Online Time" }, ] 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 cached( "lb_credits", 60_000, () => db .select({ username: User.username, look: User.look, credits: User.credits, }) .from(User) .orderBy(desc(User.credits)) .limit(20), { staleMs: 120000 }, ); return users.map((u) => ({ username: u.username, look: u.look, value: u.credits, })); } catch { return []; } } async function loadCurrencyRows(type: number): Promise { try { return await cached( `lb_currency_${type}`, 60_000, () => db .select({ username: User.username, look: User.look, value: UsersCurrency.amount, }) .from(UsersCurrency) .innerJoin(User, eq(UsersCurrency.userId, User.id)) .where(eq(UsersCurrency.type, type)) .orderBy(desc(UsersCurrency.amount)) .limit(20), { staleMs: 120000 }, ); } catch { return []; } } async function loadSettingsRows( field: "achievementScore" | "respectsReceived" | "onlineTime", ): Promise { try { const column = UsersSettings[field]; return await cached( `lb_settings_${field}`, 60_000, () => db .select({ username: User.username, look: User.look, value: column, }) .from(UsersSettings) .innerJoin(User, eq(UsersSettings.userId, User.id)) .orderBy(desc(column)) .limit(20), { staleMs: 120000 }, ); } 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 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 activeCurrency: CurrencyKind | null = active === "credits" || active === "diamonds" || active === "duckets" ? active : null; 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} {activeCurrency ? ( {row.value.toLocaleString()} ) : ( {formatValue(active, row.value)} )}
)}
); }