homepage: restore Sony bento layout (hero + side rail from Featured category)
Replaces the 5-slide FeaturedCarouselServer that lost the Sony hero + side-rail layout. New FeaturedBentoFromDb pulls every category=Featured row, sorts featured=true (pinned hero) first then by published date. - Hero (left, 2 columns): Sony BRC-AM7 + HXC-FZ90 — pinned via featured=true - Side rail (right): next 4 Featured-category posts (BVM-HX1710, MCRs/NOCs, Tessera SQ200, Backlight Iconik) Old carousel kept on disk for future use. Layout matches the prior FeaturedBento.tsx visual structure (hero + 4 small cards right). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { Suspense } from "react";
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import FeaturedBentoFromDb from '@/components/FeaturedBentoFromDb';
|
||||||
import FeaturedCarouselServer from '@/components/FeaturedCarouselServer';
|
import FeaturedCarouselServer from '@/components/FeaturedCarouselServer';
|
||||||
import TrendsSidebar from '@/components/TrendsSidebar';
|
import TrendsSidebar from '@/components/TrendsSidebar';
|
||||||
import LiveWireTicker from '@/components/LiveWireTicker';
|
import LiveWireTicker from '@/components/LiveWireTicker';
|
||||||
@@ -158,7 +159,7 @@ export default function HomePage() {
|
|||||||
{/* Featured articles bento */}
|
{/* Featured articles bento */}
|
||||||
<ScrollRevealSection className="section-enter section-enter-3">
|
<ScrollRevealSection className="section-enter section-enter-3">
|
||||||
<Suspense fallback={<BentoSkeleton />}>
|
<Suspense fallback={<BentoSkeleton />}>
|
||||||
<FeaturedCarouselServer />
|
<FeaturedBentoFromDb />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ScrollRevealSection>
|
</ScrollRevealSection>
|
||||||
|
|
||||||
|
|||||||
136
src/components/FeaturedBentoFromDb.tsx
Normal file
136
src/components/FeaturedBentoFromDb.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { createClient } from "@supabase/supabase-js";
|
||||||
|
import { rewriteLegacyImageUrl } from "@/lib/legacy-image";
|
||||||
|
|
||||||
|
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">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280]">
|
||||||
|
Featured · staff editorial
|
||||||
|
</h2>
|
||||||
|
<Link href="/news?category=Featured" className="text-[11px] font-mono text-[var(--color-text-info,#60a5fa)] hover:underline">
|
||||||
|
All featured →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
|
<Link
|
||||||
|
href={`/news/${hero.wp_slug}`}
|
||||||
|
className="lg:col-span-2 group block rounded border border-[#252525] bg-[#0b0f17] overflow-hidden hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
|
||||||
|
>
|
||||||
|
<div className="relative aspect-[16/9] bg-[#111]">
|
||||||
|
<img
|
||||||
|
src={img(hero)}
|
||||||
|
alt={hero.featured_image_alt || hero.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-2 left-2 flex gap-1 text-[10px] font-mono">
|
||||||
|
<span className="bg-[var(--color-text-info,#60a5fa)] text-black px-2 py-0.5 rounded uppercase tracking-wider font-semibold">
|
||||||
|
Featured
|
||||||
|
</span>
|
||||||
|
<span className="bg-black/60 text-white px-2 py-0.5 rounded uppercase tracking-wider">
|
||||||
|
{hero.category || "News"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-5">
|
||||||
|
<h3 className="font-serif text-2xl md:text-3xl leading-snug text-[#e5e7eb] group-hover:text-[var(--color-text-info,#60a5fa)]">
|
||||||
|
{hero.title}
|
||||||
|
</h3>
|
||||||
|
{hero.excerpt && (
|
||||||
|
<p className="font-serif text-[15px] text-[#b0b0b0] mt-3 line-clamp-3">
|
||||||
|
{hero.excerpt}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mt-4 text-[11px] font-mono text-[#6b7280] flex items-center gap-2">
|
||||||
|
<span>{hero.author_name || "Staff Reporter"}</span>
|
||||||
|
<span>·</span>
|
||||||
|
<span>{ago(hero.wp_published_at)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
{rail.map((r) => (
|
||||||
|
<Link
|
||||||
|
key={r.wp_slug}
|
||||||
|
href={`/news/${r.wp_slug}`}
|
||||||
|
className="group flex gap-3 rounded border border-[#252525] bg-[#0b0f17] overflow-hidden hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
|
||||||
|
>
|
||||||
|
<div className="shrink-0 w-[120px] aspect-square bg-[#111] relative">
|
||||||
|
<img src={img(r)} alt={r.featured_image_alt || r.title} className="w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 p-2 pr-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="text-[10px] font-mono text-[#6b7280] mt-2">
|
||||||
|
{ago(r.wp_published_at)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user