Files
avbeat-com/src/app/show-coverage/page.tsx

136 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { createClient } from "@supabase/supabase-js";
export const dynamic = "force-dynamic";
export const revalidate = 600;
interface Row {
id: string;
slug: string;
name: string;
short_name: string | null;
start_date: string;
end_date: string;
venue: string | null;
city: string | null;
country: string | null;
url: string | null;
status: string;
category: string | null;
}
function fmtRange(s: string, e: string): string {
const a = new Date(s);
const b = new Date(e);
const sameYear = a.getUTCFullYear() === b.getUTCFullYear();
const sameMo = a.getUTCMonth() === b.getUTCMonth() && sameYear;
const f = (d: Date) =>
d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
if (sameMo) return `${f(a)}${b.getUTCDate()}, ${b.getUTCFullYear()}`;
if (sameYear) return `${f(a)} ${f(b)}, ${b.getUTCFullYear()}`;
return `${f(a)} ${a.getUTCFullYear()} ${f(b)} ${b.getUTCFullYear()}`;
}
function pillStatus(s: string) {
const map: Record<string, string> = {
confirmed: "bg-emerald-500/10 text-emerald-400 border-emerald-500/30",
tentative: "bg-amber-500/10 text-amber-400 border-amber-500/30",
cancelled: "bg-zinc-500/10 text-zinc-400 border-zinc-500/30",
};
return `inline-flex items-center text-[10px] font-mono uppercase tracking-wider px-2 py-0.5 rounded-full border ${map[s] || map.tentative}`;
}
export default async function ShowCoverageIndex() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const schema = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb";
const sb = createClient(url, key, {
db: { schema: schema as "public" },
auth: { persistSession: false },
});
const { data } = await sb
.from("events")
.select("id,slug,name,short_name,start_date,end_date,venue,city,country,url,status,category")
.in("status", ["confirmed", "tentative"])
.order("start_date", { ascending: true });
const events = (data || []) as Row[];
const today = new Date().toISOString().slice(0, 10);
const upcoming = events.filter((e) => e.end_date >= today);
const past = events.filter((e) => e.end_date < today);
return (
<main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]">
<header className="mb-8">
<h1 className="font-serif text-4xl font-bold tracking-tight">Show coverage</h1>
<p className="text-sm text-[#9ca3af] mt-2 font-mono">
{upcoming.length} upcoming · {past.length} past · {events.length} total
</p>
</header>
<section className="mb-12">
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-4">
Upcoming
</h2>
{upcoming.length === 0 ? (
<p className="text-sm text-[#6b7280]">No upcoming events.</p>
) : (
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4">
{upcoming.map((e) => (
<EventCard key={e.slug} e={e} />
))}
</ul>
)}
</section>
{past.length > 0 && (
<section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-4">
Past
</h2>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4">
{past.map((e) => (
<EventCard key={e.slug} e={e} />
))}
</ul>
</section>
)}
</main>
);
}
function EventCard({ e }: { e: Row }) {
return (
<li>
<Link
href={`/show-coverage/${e.slug}`}
className="block p-5 rounded border border-[#252525] hover:border-[var(--color-text-info,#60a5fa)] bg-[#0b0f17] transition-colors"
>
<div className="flex items-start justify-between gap-3">
<h3 className="font-serif text-xl font-semibold">{e.name}</h3>
<span className={pillStatus(e.status)}>{e.status}</span>
</div>
<div className="text-sm text-[#9ca3af] mt-1">
{[e.venue, e.city, e.country].filter(Boolean).join(" · ")}
</div>
<div className="text-xs font-mono text-[#6b7280] mt-2">
{fmtRange(e.start_date, e.end_date)}
{e.url && (
<>
{" · "}
<span className="text-[var(--color-text-info,#60a5fa)]">{new URL(e.url).hostname.replace(/^www\./, "")}</span>
</>
)}
</div>
</Link>
</li>
);
}
export const metadata = {
title: "Show coverage — industry events tracked by BroadcastBeat",
description: "All upcoming and past broadcast / production industry events tracked by BroadcastBeat, with linked coverage.",
};