Local Build and Deploy / deploy (push) Successful in 1m7s
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).
150 lines
3.8 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|