"use client"; import React, { useState, useEffect, useCallback, useId } from "react"; import Link from "next/link"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; import SidebarAdStack from "@/components/SidebarAdStack"; // ─── Types ──────────────────────────────────────────────────────────────────── interface ArticleBlock { title?: string; excerpt?: string; url?: string; category?: string; featured?: boolean; } interface Newsletter { id: string; name: string; description?: string; layout: string; subject_prefix?: string; header_text?: string; accent_color?: string; article_blocks: ArticleBlock[]; created_at: string; updated_at: string; } // ─── Topic options ───────────────────────────────────────────────────────────── const TOPICS = [ "All Topics", "Broadcast", "Live Production", "Post Production", "AI & Automation", "Products", "Technology", "Events", "Industry News", "NAB Show", "IBC", ]; const CATEGORIES = [ "Broadcast", "Post Production", "Animation", "AI & Automation", "Live Production", "NAB Show", ]; // ─── Newsletter Card ─────────────────────────────────────────────────────────── function NewsletterCard({ newsletter }: { newsletter: Newsletter }) { const date = new Date(newsletter.created_at); const formattedDate = date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); const blocks = Array.isArray(newsletter.article_blocks) ? newsletter.article_blocks.filter((b) => b.title) : []; const accent = newsletter.accent_color || "#E67E22"; const layoutLabel: Record = { featured: "Weekly Digest", minimal: "Breaking News", "two-column": "Product Spotlight", standard: "Industry Report", }; return (
{/* Accent bar */}
{/* Meta */}
{layoutLabel[newsletter.layout] || newsletter.layout} ·
{/* Title */}

{newsletter.subject_prefix ? ( {newsletter.subject_prefix} ) : null} {newsletter.name}

{/* Description */} {newsletter.header_text && (

{newsletter.header_text}

)} {/* Article blocks preview */} {blocks.length > 0 && (
    {blocks.slice(0, 3).map((block, i) => (
  • {block.category && ( {block.category}: )} {block.title}
  • ))} {blocks.length > 3 && (
  • +{blocks.length - 3} more stories
  • )}
)} {/* Footer */}
{blocks.length} {blocks.length === 1 ? "story" : "stories"} Issue #{newsletter.id.slice(0, 6).toUpperCase()}
); } // ─── Signup CTA ──────────────────────────────────────────────────────────────── function SignupCTA() { const [email, setEmail] = useState(""); const [selected, setSelected] = useState(["Broadcast"]); const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); const [message, setMessage] = useState(""); const formId = useId(); const toggleCategory = (cat: string) => { setSelected((prev) => prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] ); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setStatus("error"); setMessage("Please enter a valid email address."); return; } setStatus("loading"); setMessage(""); try { const res = await fetch("/api/newsletter/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, topics: selected }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Failed to subscribe"); setStatus("success"); } catch (err: unknown) { setStatus("error"); setMessage(err instanceof Error ? err.message : "Something went wrong. Please try again."); } }; return (
Subscribe

Never Miss an Issue

Get the latest broadcast industry news delivered to your inbox.

{status === "success" ? (

You're subscribed!

Check your inbox for a confirmation email.

) : (
{/* Topic chips */}
Topics you care about
{CATEGORIES.map((cat) => { const checked = selected.includes(cat); return ( ); })}
{/* Email + button */}
{ setEmail(e.target.value); setMessage(""); setStatus("idle"); }} placeholder="Enter your email address" autoComplete="email" aria-required="true" aria-invalid={status === "error"} disabled={status === "loading"} className={`search-input w-full py-2.5 px-4 text-sm ${status === "error" ? "border-red-500" : ""} ${status === "loading" ? "opacity-60 cursor-not-allowed" : ""}`} />
{status === "error" && message && (

{message}

)}

No spam. Unsubscribe anytime.{" "} Privacy Policy

)}
); } // ─── Main Page ───────────────────────────────────────────────────────────────── export default function NewsletterArchivePage() { const [newsletters, setNewsletters] = useState([]); const [years, setYears] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); // Filters const [selectedTopic, setSelectedTopic] = useState("All Topics"); const [selectedYear, setSelectedYear] = useState(""); const [selectedMonth, setSelectedMonth] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const MONTHS = [ { value: "", label: "All Months" }, { value: "1", label: "January" }, { value: "2", label: "February" }, { value: "3", label: "March" }, { value: "4", label: "April" }, { value: "5", label: "May" }, { value: "6", label: "June" }, { value: "7", label: "July" }, { value: "8", label: "August" }, { value: "9", label: "September" }, { value: "10", label: "October" }, { value: "11", label: "November" }, { value: "12", label: "December" }, ]; const fetchArchive = useCallback(async () => { setLoading(true); setError(""); try { const params = new URLSearchParams(); if (selectedTopic && selectedTopic !== "All Topics") params.set("topic", selectedTopic); if (selectedYear) params.set("year", selectedYear); if (selectedYear && selectedMonth) params.set("month", selectedMonth); const res = await fetch(`/api/newsletter/archive?${params.toString()}`); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Failed to load archive"); setNewsletters(data.newsletters || []); setYears(data.years || []); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Failed to load newsletter archive."); } finally { setLoading(false); } }, [selectedTopic, selectedYear, selectedMonth]); useEffect(() => { fetchArchive(); }, [fetchArchive]); // Client-side text search on top of server results const filtered = newsletters.filter((n) => { if (!searchQuery.trim()) return true; const q = searchQuery.toLowerCase(); return ( n.name?.toLowerCase().includes(q) || n.description?.toLowerCase().includes(q) || n.header_text?.toLowerCase().includes(q) || (Array.isArray(n.article_blocks) && n.article_blocks.some( (b) => b.title?.toLowerCase().includes(q) || b.category?.toLowerCase().includes(q) )) ); }); const handleClearFilters = () => { setSelectedTopic("All Topics"); setSelectedYear(""); setSelectedMonth(""); setSearchQuery(""); }; const hasActiveFilters = selectedTopic !== "All Topics" || selectedYear || selectedMonth || searchQuery; return (
{/* Hero */}
Newsletter

Newsletter Archive

Browse past issues of the AV Beat newsletter — searchable by date and topic.

{/* ── Left: Archive list ─────────────────────────────────────────── */}
{/* Search bar */}
setSearchQuery(e.target.value)} placeholder="Search newsletters by title, topic, or content…" className="search-input w-full py-2.5 pl-9 pr-4 text-sm" />
{/* Topic filter chips */}
{TOPICS.map((topic) => ( ))}
{/* Date filters */}
{selectedYear && (
)} {hasActiveFilters && ( )}
{/* Results count */} {!loading && (

{filtered.length === 0 ? "No newsletters found" : `${filtered.length} ${filtered.length === 1 ? "issue" : "issues"} found`} {hasActiveFilters && " (filtered)"}

)} {/* Loading */} {loading && (
{[1, 2, 3].map((i) => (
))}
)} {/* Error */} {!loading && error && (

{error}

)} {/* Empty state */} {!loading && !error && filtered.length === 0 && (

No newsletters found

Try adjusting your filters or search query.

)} {/* Newsletter grid */} {!loading && !error && filtered.length > 0 && (
{filtered.map((newsletter) => ( ))}
)}
{/* ── Right: Sidebar ─────────────────────────────────────────────── */}
); }