Files
EpicNext-Cms/src/app/(site)/radio/requests/page.tsx
T
openhands b9c6001f08
Local Build and Deploy / deploy (push) Successful in 1m7s
refactor: centralize duplicated formatDate helper
21 files defined their own local formatDate (with three different output
formats: date, datetime, datetime-seconds, and varying null fallbacks).
Replace them with a single shared helper at src/lib/format-date.ts that
takes a variant + optional fallback, preserving the exact previous output
per call site (verified identical string results).

Removes ~21 copies of the same logic. photos.tsx and media-grid.tsx keep
their epoch-ms based formatters since those are a different input shape.

Verified: tsc --noEmit clean, full test suite green (301/301).
2026-07-18 19:17:17 +02:00

150 lines
3.8 KiB
TypeScript

import { getTranslations } from "next-intl/server";
import { formatDate } from "@/lib/format-date";
import { submitRequest } from "@/actions/radio-requests";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic";
export default async function RadioRequestsPage() {
const t = await getTranslations("pages.radioRequests");
const genericT = await getTranslations("pages.radio");
const [requests, session] = await Promise.all([
prisma.radioSongRequests
.findMany({
orderBy: { createdAt: "desc" },
take: 50,
})
.catch(() => []),
auth(),
]);
const isLoggedIn = Boolean(session?.user?.id);
const userIds = Array.from(new Set(requests.map((r) => Number(r.userId))));
const users = userIds.length
? await prisma.user
.findMany({
where: { id: { in: userIds } },
select: { id: true, username: true },
})
.catch(() => [])
: [];
const userById = new Map(users.map((u) => [u.id, u.username]));
return (
<>
<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>
<input
id="songTitle"
name="songTitle"
type="text"
required
maxLength={255}
placeholder={t("songPlaceholder")}
style={{ width: "100%", marginTop: "0.3rem" }}
/>
</div>
<div>
<label htmlFor="artist" className="muted">
{t("artistLabel")}
</label>
<input
id="artist"
name="artist"
type="text"
maxLength={255}
placeholder={t("artistPlaceholder")}
style={{ width: "100%", marginTop: "0.3rem" }}
/>
</div>
</div>
<button
type="submit"
className="btn btn-primary"
style={{ marginTop: "1rem" }}
>
{t("submitButton")}
</button>
</form>
</ContentCard>
) : (
<ContentCard padded>
<EmptyState icon="🔒">{t("loginToRequest")}</EmptyState>
</ContentCard>
)}
<ContentCard
icon="📋"
title={t("latestTitle")}
subtitle={t("latestSubtitle")}
padded={requests.length === 0}
>
{requests.length === 0 ? (
<EmptyState icon="🎵">{t("empty")}</EmptyState>
) : (
<table>
<thead>
<tr>
<th>{t("colSong")}</th>
<th>{t("colArtist")}</th>
<th>{t("colRequester")}</th>
<th>{t("colDate")}</th>
<th>{t("colStatus")}</th>
</tr>
</thead>
<tbody>
{requests.map((r) => {
const requester = userById.get(Number(r.userId));
const isPlayed = r.isPlayed;
const isApproved = r.isApproved;
let status = t("statusPending");
if (isPlayed) status = t("statusPlayed");
else if (isApproved) status = t("statusApproved");
return (
<tr key={String(r.id)}>
<td>{r.songTitle || <span className="muted">—</span>}</td>
<td>{r.artist || <span className="muted">—</span>}</td>
<td>
{requester ? (
<span>{requester}</span>
) : (
<span className="muted">#{String(r.userId)}</span>
)}
</td>
<td className="muted">
{r.submittedAt ? formatDate(r.submittedAt, "datetime", "") : "—"}
</td>
<td>{status}</td>
</tr>
);
})}
</tbody>
</table>
)}
</ContentCard>
</>
);
}