Revert "Add missing admin action files and navigation links"

This reverts commit 41be6835bf.
This commit is contained in:
Simo committed 2026-07-11 20:37:56 +02:00
1 parent 4a1e1115b3
commit 4d515bc400
338 files changed
+5949 -28144

No files matched your search

+8 -2
View File
@@ -25,11 +25,17 @@ export default async function RadioApplyPage() {
})
.catch(() => null);
const submittedDate = existing?.createdAt ? existing.createdAt.toISOString().slice(0, 10) : null;
const submittedDate = existing?.createdAt
? existing.createdAt.toISOString().slice(0, 10)
: null;
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🎤" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="🎤"
title={t("title")}
subtitle={t("subtitle")}
/>
{existing ? (
<ContentCard icon="📋" title={t("latestTitle")}>
+8 -2
View File
@@ -10,7 +10,11 @@ function formatDate(d: Date | null | undefined): string {
return d ? d.toISOString().slice(0, 19).replace("T", " ") : "";
}
export default async function RadioContestDetailPage({ params }: { params: Promise<{ id: string }> }) {
export default async function RadioContestDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
await getTranslations("pages.radioContests");
@@ -21,7 +25,9 @@ export default async function RadioContestDetailPage({ params }: { params: Promi
notFound();
}
const contest = await prisma.radioContests.findUnique({ where: { id: contestId } }).catch(() => null);
const contest = await prisma.radioContests
.findUnique({ where: { id: contestId } })
.catch(() => null);
if (!contest) notFound();
+6 -4
View File
@@ -23,7 +23,11 @@ export default async function RadioContestsPage() {
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🎉" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="🎉"
title={t("title")}
subtitle={t("subtitle")}
/>
<ContentCard padded={contests.length === 0}>
{contests.length === 0 ? (
@@ -48,9 +52,7 @@ export default async function RadioContestsPage() {
</p>
) : null}
<p className="muted" style={{ margin: 0, fontSize: "0.85rem" }}>
{c.startDate
? `${formatDate(c.startDate)} — ${c.endDate ? formatDate(c.endDate) : "ongoing"}`
: t("dateUnknown")}
{c.startDate ? `${formatDate(c.startDate)} — ${c.endDate ? formatDate(c.endDate) : "ongoing"}` : t("dateUnknown")}
</p>
</Link>
);
+9 -5
View File
@@ -10,7 +10,11 @@ function formatDate(d: Date | null | undefined): string {
return d ? d.toISOString().slice(0, 19).replace("T", " ") : "";
}
export default async function RadioGiveawayDetailPage({ params }: { params: Promise<{ id: string }> }) {
export default async function RadioGiveawayDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
await getTranslations("pages.radioGiveaways");
@@ -21,14 +25,14 @@ export default async function RadioGiveawayDetailPage({ params }: { params: Prom
notFound();
}
const giveaway = await prisma.radioGiveaways.findUnique({ where: { id: giveawayId } }).catch(() => null);
const giveaway = await prisma.radioGiveaways
.findUnique({ where: { id: giveawayId } })
.catch(() => null);
if (!giveaway) notFound();
const active = giveaway.isActive ? "Active" : "Ended";
const prizeStr =
giveaway.prize ||
(giveaway.prizeAmount > 0 ? `${giveaway.prizeAmount} ${giveaway.prizeCurrency || "credits"}` : null);
const prizeStr = giveaway.prize || (giveaway.prizeAmount > 0 ? `${giveaway.prizeAmount} ${giveaway.prizeCurrency || "credits"}` : null);
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
+8 -15
View File
@@ -17,21 +17,17 @@ export default async function RadioGiveawaysPage() {
where: { isActive: true },
orderBy: { startDate: "desc" },
take: 50,
select: {
id: true,
title: true,
prize: true,
prizeAmount: true,
prizeCurrency: true,
startDate: true,
endDate: true,
},
select: { id: true, title: true, prize: true, prizeAmount: true, prizeCurrency: true, startDate: true, endDate: true },
})
.catch(() => []);
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🎁" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="🎁"
title={t("title")}
subtitle={t("subtitle")}
/>
<ContentCard padded={giveaways.length === 0}>
{giveaways.length === 0 ? (
@@ -40,8 +36,7 @@ export default async function RadioGiveawaysPage() {
<div className="card-grid sm-2 lg-3" style={{ padding: "1rem" }}>
{giveaways.map((g) => {
const id = g.id.toString();
const prizeStr =
g.prize || (g.prizeAmount > 0 ? `${g.prizeAmount} ${g.prizeCurrency || "credits"}` : null);
const prizeStr = g.prize || (g.prizeAmount > 0 ? `${g.prizeAmount} ${g.prizeCurrency || "credits"}` : null);
return (
<Link
key={id}
@@ -58,9 +53,7 @@ export default async function RadioGiveawaysPage() {
</p>
) : null}
<p className="muted" style={{ margin: 0, fontSize: "0.85rem" }}>
{g.startDate
? `${formatDate(g.startDate)} — ${g.endDate ? formatDate(g.endDate) : "ongoing"}`
: t("dateUnknown")}
{g.startDate ? `${formatDate(g.startDate)} — ${g.endDate ? formatDate(g.endDate) : "ongoing"}` : t("dateUnknown")}
</p>
</Link>
);
+10 -16
View File
@@ -43,9 +43,7 @@ export default async function RadioLayout({ children }: { children: ReactNode })
marginBottom: "0.25rem",
}}
>
<span aria-hidden style={{ fontSize: "1.2rem" }}>
📻
</span>
<span aria-hidden style={{ fontSize: "1.2rem" }}>📻</span>
Radio
</Link>
{RADIO_SIDEBAR_LINKS.map((link) => (
@@ -65,22 +63,18 @@ export default async function RadioLayout({ children }: { children: ReactNode })
transition: "background 0.15s",
}}
>
<span aria-hidden style={{ flex: "none", fontSize: "1rem" }}>
{link.icon}
</span>
{link.key === "schedule"
? t("linkSchedule")
: link.key === "shouts"
? t("linkShouts")
: link.key === "requests"
? t("linkRequests")
: link.key === "apply"
? t("linkApply")
: t("linkLeaderboard")}
<span aria-hidden style={{ flex: "none", fontSize: "1rem" }}>{link.icon}</span>
{link.key === "schedule" ? t("linkSchedule") :
link.key === "shouts" ? t("linkShouts") :
link.key === "requests" ? t("linkRequests") :
link.key === "apply" ? t("linkApply") :
t("linkLeaderboard")}
</Link>
))}
</aside>
<section style={{ flex: 1, minWidth: 0 }}>{children}</section>
<section style={{ flex: 1, minWidth: 0 }}>
{children}
</section>
</div>
);
}
+12 -3
View File
@@ -48,7 +48,11 @@ export default async function RadioLeaderboardPage() {
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🏆" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="🏆"
title={t("title")}
subtitle={t("subtitle")}
/>
<ContentCard padded={rows.length === 0}>
{rows.length === 0 ? (
@@ -85,11 +89,16 @@ export default async function RadioLeaderboardPage() {
/>
</td>
<td>
<Link href={`/u/${encodeURIComponent(row.username)}`} style={{ fontWeight: 700 }}>
<Link
href={`/u/${encodeURIComponent(row.username)}`}
style={{ fontWeight: 700 }}
>
{row.username}
</Link>
</td>
<td style={{ textAlign: "right", fontWeight: 800 }}>{row.points.toLocaleString()}</td>
<td style={{ textAlign: "right", fontWeight: 800 }}>
{row.points.toLocaleString()}
</td>
</tr>
);
})}
+28 -16
View File
@@ -9,7 +9,15 @@ export const revalidate = 300;
// Canonical weekday order so today's slots come out chronologically and we can
// match the current day against whatever casing the `day` column stores.
const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const WEEKDAYS = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
// radio_schedules.start_time / end_time are stored as TIME and surface through
// Prisma as a Date with the time component on a 1970 epoch date — render just
@@ -47,7 +55,9 @@ export default async function RadioPage() {
})
.catch(() => []);
const todaysSlots = schedules.filter((s) => s.day.toLowerCase() === today.toLowerCase());
const todaysSlots = schedules.filter(
(s) => s.day.toLowerCase() === today.toLowerCase(),
);
// Resolve DJ usernames for today's slots in one query. radio_schedules.user_id
// is an UnsignedBigInt while users.id is an Int, so narrow to Number for the
@@ -76,20 +86,11 @@ export default async function RadioPage() {
>
<div className="card-grid sm-2" style={{ alignItems: "stretch" }}>
<div className="card" style={{ display: "grid", gap: "0.35rem", alignContent: "center" }}>
<p
className="muted"
style={{ margin: 0, fontSize: "0.8rem", textTransform: "uppercase", letterSpacing: "0.05em" }}
>
<p className="muted" style={{ margin: 0, fontSize: "0.8rem", textTransform: "uppercase", letterSpacing: "0.05em" }}>
{t("nowPlaying")}
</p>
<p style={{ margin: 0, fontSize: "1.1rem", fontWeight: 700 }}>
{currentSong ? (
currentSong
) : (
<span className="muted" style={{ fontWeight: 400 }}>
{t("nothingPlaying")}
</span>
)}
{currentSong ? currentSong : <span className="muted" style={{ fontWeight: 400 }}>{t("nothingPlaying")}</span>}
</p>
<p className="muted" style={{ margin: 0 }}>
{currentDj ? t("onAir", { dj: currentDj }) : t("noDj")}
@@ -97,7 +98,12 @@ export default async function RadioPage() {
</div>
<div className="card" style={{ display: "flex", alignItems: "center" }}>
{streamUrl ? (
<audio controls src={streamUrl} style={{ width: "100%" }} preload="none">
<audio
controls
src={streamUrl}
style={{ width: "100%" }}
preload="none"
>
{t("audioUnsupported")}
</audio>
) : (
@@ -110,7 +116,11 @@ export default async function RadioPage() {
</ContentCard>
{/* ── Sub-page links ──────────────────────────────────────── */}
<ContentCard icon="🎚️" title={t("exploreTitle")} subtitle={t("exploreSubtitle")}>
<ContentCard
icon="🎚️"
title={t("exploreTitle")}
subtitle={t("exploreSubtitle")}
>
<div className="card-grid sm-2 lg-3">
{RADIO_LINKS.map((l) => (
<Link
@@ -161,7 +171,9 @@ export default async function RadioPage() {
<td>
{formatTime(s.startTime)} &ndash; {formatTime(s.endTime)}
</td>
<td>{s.showName || <span className="muted">{t("untitledShow")}</span>}</td>
<td>
{s.showName || <span className="muted">{t("untitledShow")}</span>}
</td>
<td>
{djName ? (
<Link href={`/u/${djName}`}>{djName}</Link>
+7 -7
View File
@@ -39,16 +39,18 @@ export default async function RadioRequestsPage() {
return (
<>
<ContentCard icon="🎵" title={genericT("linkRequests")} subtitle={genericT("linkRequestsText")} />
<ContentCard
icon="🎵"
title={genericT("linkRequests")}
subtitle={genericT("linkRequestsText")}
/>
{isLoggedIn ? (
<ContentCard icon="✍️" title={t("formTitle")} subtitle={t("formSubtitle")}>
<form action={submitRequest}>
<div className="card-grid sm-2">
<div>
<label htmlFor="songTitle" className="muted">
{t("songLabel")}
</label>
<label htmlFor="songTitle" className="muted">{t("songLabel")}</label>
<input
id="songTitle"
name="songTitle"
@@ -60,9 +62,7 @@ export default async function RadioRequestsPage() {
/>
</div>
<div>
<label htmlFor="artist" className="muted">
{t("artistLabel")}
</label>
<label htmlFor="artist" className="muted">{t("artistLabel")}</label>
<input
id="artist"
name="artist"
+19 -4
View File
@@ -6,7 +6,15 @@ export const revalidate = 300;
// Weekly order so the table reads Monday → Sunday regardless of insert order.
// The `day` column stores a free-form string; we key case-insensitively.
const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] as const;
const DAYS = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
] as const;
// `start_time` / `end_time` are MySQL TIME columns; Prisma hydrates them as a
// Date on the 1970-01-01 epoch. Pull just HH:MM in UTC (Time has no zone).
@@ -50,7 +58,8 @@ export default async function RadioSchedulePage() {
const byDay = new Map<string, Slot[]>();
for (const day of DAYS) byDay.set(day, []);
for (const s of schedules) {
const key = DAYS.find((d) => d.toLowerCase() === s.day.trim().toLowerCase()) ?? s.day;
const key =
DAYS.find((d) => d.toLowerCase() === s.day.trim().toLowerCase()) ?? s.day;
const slot: Slot = {
id: String(s.id),
dj: djById.get(Number(s.userId)) ?? `User #${s.userId}`,
@@ -68,7 +77,11 @@ export default async function RadioSchedulePage() {
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="🗓️" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="🗓️"
title={t("title")}
subtitle={t("subtitle")}
/>
<ContentCard padded={!hasAny}>
{!hasAny ? (
@@ -108,7 +121,9 @@ export default async function RadioSchedulePage() {
<td>{slot.dj}</td>
<td>
{slot.showName || <span className="muted">—</span>}
{slot.description ? <div className="muted">{slot.description}</div> : null}
{slot.description ? (
<div className="muted">{slot.description}</div>
) : null}
</td>
</tr>
));
+9 -2
View File
@@ -22,7 +22,10 @@ export default async function RadioShoutsPage() {
take: 50,
})
.catch(() => []),
siteSettings.get("habbo_imaging_url", "https://www.habbo.com/habbo-imaging/avatarimage"),
siteSettings.get(
"habbo_imaging_url",
"https://www.habbo.com/habbo-imaging/avatarimage",
),
auth(),
]);
@@ -42,7 +45,11 @@ export default async function RadioShoutsPage() {
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard icon="📣" title={t("title")} subtitle={t("subtitle")} />
<ContentCard
icon="📣"
title={t("title")}
subtitle={t("subtitle")}
/>
{isLoggedIn ? (
<ContentCard icon="✍️" title={t("postTitle")} subtitle={t("postSubtitle")}>