import Link from "next/link"; import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui"; import { avatarImageUrl } from "@/lib/format"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; export const dynamic = "force-dynamic"; // The radio_listener_points table in the live AtomCMS DB carries user_id + // points columns, but the generated Prisma model is a stub (id/timestamps only), // so it has no `points`/`userId` delegate fields. We therefore read it via // $queryRaw, joining users for the username/look — the same approach used for // the keyless/@@ignore'd emulator tables (see src/app/admin/logs). MySQL may // return BigInt columns as bigint, so values are coerced defensively. type LeaderRow = { username: string; look: string; points: number | bigint; }; async function loadRows(): Promise<{ username: string; look: string; points: number }[]> { try { const rows = await prisma.$queryRaw` SELECT u.username AS username, u.look AS look, p.points AS points FROM radio_listener_points p INNER JOIN users u ON u.id = p.user_id ORDER BY p.points DESC LIMIT 50 `; return rows.map((r) => ({ username: r.username, look: r.look, points: Number(r.points), })); } catch { return []; } } export default async function RadioLeaderboardPage() { const [rows, imagerBase] = await Promise.all([ loadRows(), siteSettings.get("habbo_imaging_url", "https://www.habbo.com/habbo-imaging/avatarimage"), ]); return (
{rows.length === 0 ? ( No listener points to show yet. ) : ( {rows.map((row, i) => { const avatar = avatarImageUrl(imagerBase ?? "", row.look, { size: "s", headOnly: true, }); return ( ); })}
Rank Avatar Listener Points
{/* eslint-disable-next-line @next/next/no-img-element */} {`${row.username} {row.username} {row.points.toLocaleString()}
)}
); }