import { getTranslations } from "next-intl/server"; import Link from "next/link"; import { ContentCard, EmptyState, OnlineBadge } from "@/components/public/ui"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; // Radio config + today's schedule are read live from the DB / settings table. 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", ]; // 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 // the HH:MM portion in UTC to avoid timezone drift. function formatTime(d: Date | null | undefined): string { if (!d) return ""; return d.toISOString().slice(11, 16); } export default async function RadioPage() { const t = await getTranslations("pages.radio"); // Sub-pages linked from the radio home hub. const RADIO_LINKS: { label: string; href: string; icon: string; text: string }[] = [ { label: t("linkSchedule"), href: "/radio/schedule", icon: "🗓️", text: t("linkScheduleText") }, { label: t("linkShouts"), href: "/radio/shouts", icon: "📣", text: t("linkShoutsText") }, { label: t("linkRequests"), href: "/radio/requests", icon: "🎵", text: t("linkRequestsText") }, { label: t("linkApply"), href: "/radio/apply", icon: "🎤", text: t("linkApplyText") }, { label: t("linkLeaderboard"), href: "/radio/leaderboard", icon: "🏆", text: t("linkLeaderboardText") }, ]; // ── Radio config from website_settings (graceful fallbacks) ── const [streamUrl, currentDj, currentSong] = await Promise.all([ siteSettings.get("radio_stream_url", ""), siteSettings.get("radio_current_dj", ""), siteSettings.get("radio_current_song", ""), ]); // ── Today's schedule (isolated; a DB hiccup degrades to empty) ── const today = WEEKDAYS[new Date().getUTCDay()] ?? ""; const schedules = await prisma.radioSchedules .findMany({ where: { isActive: true }, orderBy: { startTime: "asc" }, }) .catch(() => []); 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 // lookup (DJ ids comfortably fit in a JS-safe integer). const djIds = Array.from(new Set(todaysSlots.map((s) => Number(s.userId)))); const djs = djIds.length ? await prisma.user .findMany({ where: { id: { in: djIds } }, select: { id: true, username: true }, }) .catch(() => []) : []; const djById = new Map(djs.map((d) => [d.id, d.username])); const isLive = Boolean(streamUrl); return (
{/* ── Header: live stream + now playing ─────────────────────── */} } >

{t("nowPlaying")}

{currentSong ? currentSong : {t("nothingPlaying")}}

{currentDj ? t("onAir", { dj: currentDj }) : t("noDj")}

{streamUrl ? ( ) : (

{t("streamOffline")}

)}
{/* ── Sub-page links ──────────────────────────────────────── */}
{RADIO_LINKS.map((l) => (

{l.icon} {l.label}

{l.text}

))}
{/* ── Today's schedule ────────────────────────────────────── */} {t("fullWeek")} } padded={todaysSlots.length === 0} > {todaysSlots.length === 0 ? ( {t("noShowsToday")} ) : ( {todaysSlots.map((s) => { const djName = djById.get(Number(s.userId)); return ( ); })}
{t("colTime")} {t("colShow")} {t("colDj")}
{formatTime(s.startTime)} – {formatTime(s.endTime)} {s.showName || {t("untitledShow")}} {djName ? ( {djName} ) : ( {t("userNumber", { id: String(s.userId) })} )}
)}
); }