Files
EpicNext-Cms/src/app/(site)/radio/leaderboard/page.tsx
T
openhands 7a2c0fd4d4
CI / check (push) Successful in 26s
CI / release (push) Skipped
CI / deploy (push) Successful in 58s
fix: resolve TypeScript and lint issues
- Fix proxy routes to use resolveImagerBase instead of removed resolveUpstreamBase
- Fix avatarImageUrl type signature to use AvatarOptions
- Run biome formatter
2026-08-09 19:42:02 +02:00

99 lines
2.5 KiB
TypeScript

import { sql } from "drizzle-orm";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui";
import { db } from "@/lib/db";
import { avatarImageUrl } from "@/lib/format";
type LeaderRow = {
username: string;
look: string;
points: number | bigint;
};
async function loadRows(): Promise<
{ username: string; look: string; points: number }[]
> {
try {
const [rows] = (await db.execute(sql`
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
`)) as unknown as [LeaderRow[], unknown];
return (rows ?? []).map((r) => ({
username: r.username,
look: r.look,
points: Number(r.points),
}));
} catch {
return [];
}
}
export default async function RadioLeaderboardPage() {
const t = await getTranslations("pages.radioLeaderboard");
const rows = await loadRows();
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🏆" title={t("title")} subtitle={t("subtitle")} />
<ContentCard padded={rows.length === 0}>
{rows.length === 0 ? (
<EmptyState icon="🏆">{t("empty")}</EmptyState>
) : (
<table>
<thead>
<tr>
<th style={{ width: "3.5rem" }}>{t("colRank")}</th>
<th style={{ width: "3.5rem" }}>{t("colAvatar")}</th>
<th>{t("colListener")}</th>
<th style={{ textAlign: "right" }}>{t("colPoints")}</th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => {
const avatar = avatarImageUrl(row.look, {
size: "s",
headOnly: true,
});
return (
<tr key={row.username}>
<td>
<RankBadge position={i + 1} />
</td>
<td>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className="avatar"
src={avatar}
alt={`${row.username} avatar`}
width={40}
height={40}
/>
</td>
<td>
<Link
href={`/u/${encodeURIComponent(row.username)}`}
style={{ fontWeight: 700 }}
>
{row.username}
</Link>
</td>
<td style={{ textAlign: "right", fontWeight: 800 }}>
{row.points.toLocaleString()}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</ContentCard>
</main>
);
}