import Link from "next/link"; import { createClient } from "@supabase/supabase-js"; export const dynamic = "force-dynamic"; interface NewsItem { slug: string; title: string; published_at: string; source_code: string; } interface EventItem { slug: string | null; short_name: string; name: string; start_date: string; city: string | null; country: string | null; } const SOURCE_CODES: { match: RegExp; code: string }[] = [ { match: /blackmagic|davinci/i, code: "BMD" }, { match: /magewell/i, code: "MGW" }, { match: /aja/i, code: "AJA" }, { match: /sony/i, code: "SONY" }, { match: /matrox/i, code: "MTRX" }, { match: /telestream/i, code: "TLST" }, { match: /grass valley/i, code: "GV" }, { match: /lawo/i, code: "LAWO" }, { match: /calrec/i, code: "CLRC" }, { match: /zixi/i, code: "ZIXI" }, { match: /liveu/i, code: "LVU" }, { match: /netflix/i, code: "NFLX" }, { match: /youtube/i, code: "YT" }, { match: /amazon|aws/i, code: "AWS" }, ]; function codeFor(title: string): string { for (const { match, code } of SOURCE_CODES) if (match.test(title)) return code; return "WIRE"; } function hhmm(iso: string): string { const d = new Date(iso); const h = String(d.getUTCHours()).padStart(2, "0"); const m = String(d.getUTCMinutes()).padStart(2, "0"); return `${h}:${m}`; } function fmtEventDate(iso: string): string { // Show "MMM DD" (no year) so each row stays compact. try { return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC", }); } catch { return iso.slice(5, 10); } } // Server-side fetches return arrays — but a network/RLS error can throw. // Each is wrapped so a partial failure renders an empty row rather than // crashing the whole component (the silent build crash that took down // the prior incarnation of this component was caused by an uncaught // throw inside an async server component). async function loadNews(sb: any): Promise { try { const [wp, ai] = await Promise.all([ sb .from("wp_imported_posts") .select("wp_slug, title, wp_published_at") .eq("status", "published") .order("wp_published_at", { ascending: false }) .limit(12), sb .from("ai_rewritten_articles") .select("slug, title, published_at") .eq("status", "published") .order("published_at", { ascending: false, nullsFirst: false }) .limit(6), ]); const items: NewsItem[] = []; for (const r of (wp.data || []) as any[]) { items.push({ slug: r.wp_slug, title: r.title, published_at: r.wp_published_at, source_code: codeFor(r.title || ""), }); } for (const r of (ai.data || []) as any[]) { items.push({ slug: r.slug, title: r.title, published_at: r.published_at, source_code: codeFor(r.title || ""), }); } items.sort( (a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime() ); return items.slice(0, 14); } catch { return []; } } async function loadEvents(sb: any): Promise { try { const { data } = await sb .from("events") .select("slug, short_name, name, start_date, city, country") .in("status", ["confirmed", "tentative"]) .gte("start_date", new Date().toISOString().slice(0, 10)) .order("start_date", { ascending: true }) .limit(12); return ((data || []) as any[]).map((r) => ({ slug: r.slug || null, short_name: r.short_name || r.name, name: r.name, start_date: r.start_date, city: r.city, country: r.country, })); } catch { return []; } } export default async function DoubleTicker() { const sb = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb") as "public" }, auth: { persistSession: false }, } ); const [news, events] = await Promise.all([loadNews(sb), loadEvents(sb)]); // If both queries failed, render nothing — better than a half-blank bar. if (news.length === 0 && events.length === 0) return null; return (
{/* Row 1 — Live wire (news, right-to-left, fast). */} {news.length > 0 && (
Live Wire
{[...news, ...news].map((it, i) => ( [{hhmm(it.published_at)}] [{it.source_code}] {it.title} ))}
)} {/* Row 2 — Industry calendar (events, left-to-right, slower). */} {events.length > 0 && (
On Calendar
{[...events, ...events].map((ev, i) => { const where = [ev.city, ev.country].filter(Boolean).join(", "); const inner = ( [{fmtEventDate(ev.start_date)}] [{ev.short_name}] {ev.name}{where ? ` — ${where}` : ""} ); return ev.slug ? ( {inner} ) : ( {inner} ); })}
)}
); }