Files
avbeat-com/src/app/nab-2026/page.tsx
Ryan Salazar 95eba5794b manufacturers + NAB Show naming sweep
- Standardize "NAB Show" / "2026 NAB Show" across badges, filter chips,
  and headings; never bare "NAB" in user-facing copy.
- /manufacturers/[slug]: normalize show name at render, strip
  randomstring suffix on booth, hide booth unless the show is within 90
  days, filter single-letter junk out of category display.
- News filter "NAB Show" chip loose-matches any nab-tagged article so
  legacy "NAB 2026" tags still surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 22:07:29 +00:00

235 lines
9.9 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 type { Metadata } from "next";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { createClient } from "@/lib/supabase/server";
import { createAdminClient } from "@/lib/supabase/admin";
import CompanyMentionsHover from "@/components/CompanyMentionsHover";
export const revalidate = 600;
export const metadata: Metadata = {
title: "2026 NAB Show — Live Coverage Hub | Broadcast Beat",
description:
"Broadcast Beat's live coverage of 2026 NAB Show: 1,100+ exhibitors, the latest product launches, booth-by-booth news, and editorial pick stories from the show floor.",
alternates: { canonical: "/nab-2026" },
openGraph: {
title: "2026 NAB Show — Live Coverage Hub",
description:
"Live coverage, exhibitor list, and breaking product news from 2026 NAB Show.",
type: "website",
url: "https://broadcastbeat.com/nab-2026",
},
};
type ExhibitorRow = {
slug: string;
company_name: string;
logo_url: string | null;
featured: boolean;
mention_count: number;
categories: string[] | null;
};
type ArticleRow = {
wp_slug: string;
title: string;
excerpt: string | null;
featured_image: string | null;
wp_published_at: string | null;
};
export default async function NabHub() {
const supabase = await createClient();
const { data: rawExhibitors } = await supabase
.from("tracked_companies")
.select("slug, company_name, logo_url, featured, mention_count, categories")
.eq("directory_visible", true)
.eq("exhibits_nab", true)
.not("slug", "is", null)
.order("featured", { ascending: false })
.order("mention_count", { ascending: false })
.order("company_name", { ascending: true })
.limit(2000);
const exhibitors = (rawExhibitors || []) as ExhibitorRow[];
// Sponsor lookup so the hub can bubble paying advertisers to the top.
let sponsors = new Set<string>();
try {
const adv = createAdminClient("adv");
const { data: ads } = await adv
.from("active_advertisers_by_property")
.select("company_name_lower")
.eq("property", "broadcastbeat");
sponsors = new Set((ads || []).map((r: any) => String(r.company_name_lower)));
} catch { /* tolerate */ }
// Group exhibitors: sponsors first, then everything else alphabetically.
const sponsorRows = exhibitors.filter((e) => sponsors.has(e.company_name.toLowerCase()));
const otherRows = exhibitors
.filter((e) => !sponsors.has(e.company_name.toLowerCase()))
.sort((a, b) => a.company_name.localeCompare(b.company_name));
// Recent NAB stories — anything published in the last 60 days with NAB in
// the title or tags. Cap at 30.
const sixtyDaysAgo = new Date(Date.now() - 60 * 24 * 3600 * 1000).toISOString();
const { data: rawArticles } = await supabase
.from("wp_imported_posts")
.select("wp_slug, title, excerpt, featured_image, wp_published_at")
.eq("status", "published")
.or("title.ilike.%NAB%,title.ilike.%NAB 2026%")
.gte("wp_published_at", sixtyDaysAgo)
.order("wp_published_at", { ascending: false })
.limit(30);
const articles = (rawArticles || []) as ArticleRow[];
return (
<div className="min-h-screen bg-[#0d0d0d]">
<Header />
<CompanyMentionsHover />
{/* Hero */}
<section className="bg-gradient-to-br from-amber-500/10 to-[#0d0d0d] border-b border-[#252525]">
<div className="max-w-6xl mx-auto px-4 py-10">
<div className="flex items-baseline gap-3 mb-2">
<span className="text-[10px] font-bold uppercase tracking-widest bg-amber-500/20 text-amber-300 px-2 py-0.5 rounded">
Live coverage hub
</span>
<span className="text-xs text-[#888]">April 1116, 2026 · Las Vegas</span>
</div>
<h1 className="text-3xl md:text-5xl font-display font-bold text-[#e0e0e0] mb-3">
2026 NAB Show
</h1>
<p className="text-[#aaa] text-base max-w-3xl">
{exhibitors.length.toLocaleString()} exhibitors confirmed.
{sponsorRows.length > 0 && <> {sponsorRows.length} are Broadcast Beat coverage partners.</>}
{" "}Browse the directory, read the latest product news, and find every booth.
</p>
</div>
</section>
<div className="max-w-6xl mx-auto px-4 py-10 space-y-12">
{/* Featured / Coverage Partners */}
{sponsorRows.length > 0 && (
<section>
<header className="flex items-baseline justify-between mb-4">
<h2 className="text-xl font-display font-bold text-[#e0e0e0]">
Coverage Partners at the 2026 NAB Show
</h2>
<span className="text-xs text-[#888]">{sponsorRows.length} exhibiting partner{sponsorRows.length === 1 ? "" : "s"}</span>
</header>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
{sponsorRows.map((e) => (
<Link
key={e.slug}
href={`/manufacturers/${e.slug}`}
data-company-slug={e.slug}
className="bg-[#0d0d0d] border-2 border-emerald-500/40 rounded-lg p-3 hover:border-emerald-500/70 hover:shadow-md transition-all flex flex-col items-center text-center gap-2"
>
<div className="w-full h-16 flex items-center justify-center bg-[#1a1a1a] rounded">
{e.logo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img src={e.logo_url} alt={`${e.company_name} logo`} className="max-h-12 max-w-full object-contain" loading="lazy" />
) : (
<span className="text-2xl font-bold text-[#666]">{e.company_name.charAt(0).toUpperCase()}</span>
)}
</div>
<span className="font-semibold text-[#e0e0e0] text-xs truncate w-full">{e.company_name}</span>
<span className="text-[9px] font-bold uppercase tracking-wider bg-emerald-100 text-emerald-300 px-1.5 py-0.5 rounded">Sponsor</span>
</Link>
))}
</div>
</section>
)}
{/* Recent NAB-related stories */}
{articles.length > 0 && (
<section>
<header className="flex items-baseline justify-between mb-4">
<h2 className="text-xl font-display font-bold text-[#e0e0e0]">
Latest from the show floor
</h2>
<Link href="/news?search=NAB+2026" className="text-xs text-[#3b82f6] hover:underline">
See all NAB coverage
</Link>
</header>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{articles.slice(0, 9).map((a) => (
<Link
key={a.wp_slug}
href={`/news/${a.wp_slug}`}
className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#3b82f6] hover:shadow-sm transition-all"
>
{a.featured_image && (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={a.featured_image}
alt=""
className="w-full aspect-video object-cover group-hover:scale-[1.02] transition-transform"
loading="lazy"
/>
)}
<div className="p-3">
<h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#3b82f6] line-clamp-3 mb-1">
{a.title}
</h3>
{a.wp_published_at && (
<p className="text-[10px] text-[#888]">
{new Date(a.wp_published_at).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
</p>
)}
</div>
</Link>
))}
</div>
</section>
)}
{/* Full exhibitor list */}
<section>
<header className="flex items-baseline justify-between mb-4">
<h2 className="text-xl font-display font-bold text-[#e0e0e0]">
All 2026 NAB Show exhibitors
</h2>
<Link href="/manufacturers" className="text-xs text-[#3b82f6] hover:underline">
Full directory
</Link>
</header>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{otherRows.slice(0, 200).map((e) => (
<Link
key={e.slug}
href={`/manufacturers/${e.slug}`}
data-company-slug={e.slug}
className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#3b82f6] transition-all flex items-center gap-2.5"
>
{e.logo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img src={e.logo_url} alt="" className="w-10 h-10 rounded bg-[#1a1a1a] object-contain flex-shrink-0" loading="lazy" />
) : (
<div className="w-10 h-10 rounded bg-[#1a1a1a] flex items-center justify-center text-[#666] font-bold text-sm flex-shrink-0">
{e.company_name.charAt(0).toUpperCase()}
</div>
)}
<span className="font-medium text-[#e0e0e0] text-xs truncate">{e.company_name}</span>
</Link>
))}
</div>
{otherRows.length > 200 && (
<p className="text-center text-xs text-[#888] mt-4">
Showing 200 of {otherRows.length.toLocaleString()} exhibitors.{" "}
<Link href="/manufacturers" className="text-[#3b82f6] hover:underline">Browse the full directory </Link>
</p>
)}
</section>
</div>
<Footer />
</div>
);
}