Files
avbeat-com/src/components/FeaturedBentoFromDb.tsx

149 lines
6.2 KiB
TypeScript

import Link from "next/link";
import { createClient } from "@supabase/supabase-js";
import { rewriteLegacyImageUrl } from "@/lib/legacy-image";
import StarRating from "@/components/StarRating";
export const dynamic = "force-dynamic";
interface Row {
wp_id: number;
wp_slug: string;
title: string;
excerpt: string | null;
category: string | null;
featured_image: string | null;
featured_image_alt: string | null;
author_name: string | null;
wp_published_at: string | null;
featured: boolean;
}
function ago(iso: string | null): string {
if (!iso) return "";
const t = Date.now() - new Date(iso).getTime();
const m = Math.floor(t / 60000);
if (m < 60) return `${m}m ago`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}h ago`;
const d = Math.floor(h / 24);
return d < 30 ? `${d}d ago` : new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
}
export default async function FeaturedBento() {
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 },
}
);
// Pool: every published row in the Featured category. Sort: pinned
// hero (featured=true) first, then by date desc.
const { data } = await sb
.from("wp_imported_posts")
.select("wp_id, wp_slug, title, excerpt, category, featured_image, featured_image_alt, author_name, wp_published_at, featured")
.eq("status", "published")
.ilike("category", "featured")
.order("featured", { ascending: false })
.order("wp_published_at", { ascending: false, nullsFirst: false })
.limit(7);
const rows = (data || []) as Row[];
if (rows.length === 0) return null;
const hero = rows[0];
const rail = rows.slice(1, 5);
function img(r: Row): string {
return r.featured_image ? rewriteLegacyImageUrl(r.featured_image) : "/assets/images/article-placeholder.svg";
}
return (
<section className="max-w-container mx-auto px-4 my-8" aria-label="Featured stories">
{/* Section header — same shape as "The Latest" on ArticleFeed:
section-label utility + flex-1 hr + accent-blue "all →" link. */}
<div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap">
<span className="section-label">Staff Editorial</span>
<div className="flex-1 h-px bg-[#2a2a2a]" />
<Link href="/news?category=Featured" className="text-[11px] font-mono text-[var(--color-text-info,#60a5fa)] hover:underline whitespace-nowrap">
All featured
</Link>
</div>
{/* Cinematic 21:9 hero — full width, image fills, gradient + overlaid title */}
<Link
href={`/news/${hero.wp_slug}`}
className="group block relative overflow-hidden rounded border border-[#252525] bg-[#0b0f17] hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
>
<div className="relative aspect-[21/9] md:aspect-[21/8] bg-[#111]">
<img
src={img(hero)}
alt={hero.featured_image_alt || hero.title}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/65 to-transparent" />
<span className="absolute top-4 left-4 inline-flex items-center gap-1.5 bg-[var(--color-text-info,#60a5fa)] text-black px-3 py-1 rounded font-mono text-[11px] uppercase tracking-widest font-bold shadow-md">
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 2l2.6 7.6H22l-6.2 4.5L18.4 22 12 17.3 5.6 22l2.6-7.9L2 9.6h7.4z" />
</svg>
Feature Story
</span>
<div className="absolute bottom-0 left-0 right-0 p-6 md:p-10">
{hero.category && (
<div className="text-[10px] font-mono uppercase tracking-widest text-[var(--color-text-info,#60a5fa)] mb-2">
{hero.category}
</div>
)}
<h3 className="font-serif text-2xl md:text-4xl lg:text-5xl text-white leading-tight max-w-3xl group-hover:text-[var(--color-text-info,#60a5fa)] transition-colors">
{hero.title}
</h3>
{hero.excerpt && (
<p className="font-serif text-base md:text-lg text-[#cbd5e1] mt-3 max-w-2xl line-clamp-2">
{hero.excerpt}
</p>
)}
<div className="mt-4 flex flex-wrap items-center gap-3 text-[11px] font-mono text-[#9ca3af]">
<span>{hero.author_name || "Broadcast Beat"}</span>
<StarRating seed={hero.wp_slug} publishedAt={hero.wp_published_at} size="sm" />
<span aria-hidden="true" className="text-[var(--color-text-info,#60a5fa)] ml-2"> Read full story</span>
</div>
</div>
</div>
</Link>
{/* 4-up rail BELOW the hero, vertical cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
{rail.map((r) => (
<Link
key={r.wp_slug}
href={`/news/${r.wp_slug}`}
className="group block rounded border border-[#252525] bg-[#0b0f17] overflow-hidden hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
>
<div className="aspect-[16/10] bg-[#111] relative">
<img
src={img(r)}
alt={r.featured_image_alt || r.title}
className="w-full h-full object-cover"
/>
</div>
<div className="p-3">
<div className="text-[9px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)] mb-1">
{r.category || "News"}
</div>
<h4 className="font-serif text-sm leading-tight text-[#e5e7eb] group-hover:text-[var(--color-text-info,#60a5fa)] line-clamp-3">
{r.title}
</h4>
<div className="mt-2">
<StarRating seed={r.wp_slug} publishedAt={r.wp_published_at} size="sm" showCount={false} />
</div>
</div>
</Link>
))}
</div>
</section>
);
}