Deploy-1 fix + P0-6/7/8: Suspense wrap, carousel arrows, banner reposition, search-box removal
Deploy-1 build failed because NewsPageClient now calls useSearchParams. - src/app/news/page.tsx: wrap NewsPageClient in <Suspense> so static generation can bail out to client rendering cleanly. P0-6 (Featured carousel arrows): - FeaturedBento: brighten arrow buttons (text-[#ccc] over bg-[#1a1a1a] with #3a3a3a border; previously near-invisible #666 over the same background), enlarge to w-8 h-8, hover fills to blue. Wrap the arrow group with role/aria + Left/Right keyboard nav. P0-7 (728x90 reposition): - Header: move the leaderboard block from above the main nav to AFTER the sticky nav, so it sits between the nav and The Beat ticker. P0-8 (remove Latest search box): - ArticleFeed: drop the in-section search input + clear button. The top-right header search is now the sole search UI. searchQuery state remains since the active-filter chip code still references it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -506,39 +506,9 @@ export default function ArticleFeed() {
|
|||||||
<span className="font-body text-xs text-[#555]">{sortedFiltered.length?.toLocaleString()} article{sortedFiltered.length !== 1 ? "s" : ""}</span>
|
<span className="font-body text-xs text-[#555]">{sortedFiltered.length?.toLocaleString()} article{sortedFiltered.length !== 1 ? "s" : ""}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search + Filters */}
|
{/* Filters only — site-wide search lives in the top-right header */}
|
||||||
<div className="mb-4 space-y-3">
|
<div className="mb-4 space-y-3">
|
||||||
{/* Search bar row with Advanced Filters toggle */}
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative flex-1">
|
|
||||||
<label htmlFor={`${feedId}-search`} className="sr-only">Search articles</label>
|
|
||||||
<SearchIcon
|
|
||||||
size={14}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-[#555] pointer-events-none"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
id={`${feedId}-search`}
|
|
||||||
ref={searchRef}
|
|
||||||
type="search"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => { setSearchQuery(e.target.value); setPage(1); }}
|
|
||||||
placeholder="Search articles by title, topic, or author..."
|
|
||||||
aria-label="Search articles"
|
|
||||||
aria-controls={`${feedId}-results`}
|
|
||||||
className="search-input search-input-enhanced w-full pl-9 pr-9 py-2 text-sm"
|
|
||||||
/>
|
|
||||||
{searchQuery && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={clearSearch}
|
|
||||||
aria-label="Clear search"
|
|
||||||
className="search-clear-btn right-2.5">
|
|
||||||
<CloseIcon size={11} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{/* Advanced filters toggle button */}
|
{/* Advanced filters toggle button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -159,25 +159,36 @@ export default function FeaturedBento() {
|
|||||||
<span className="section-label">Featured</span>
|
<span className="section-label">Featured</span>
|
||||||
<div className="flex-1 h-px bg-[#2a2a2a]" />
|
<div className="flex-1 h-px bg-[#2a2a2a]" />
|
||||||
{/* Navigation arrows */}
|
{/* Navigation arrows */}
|
||||||
<div className="flex items-center gap-1.5">
|
<div
|
||||||
|
className="flex items-center gap-1.5"
|
||||||
|
role="group"
|
||||||
|
aria-label="Featured stories pagination"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "ArrowLeft") { e.preventDefault(); goPrev(); setPaused(true); }
|
||||||
|
if (e.key === "ArrowRight") { e.preventDefault(); goNext(); setPaused(true); }
|
||||||
|
}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => { goPrev(); setPaused(true); }}
|
onClick={() => { goPrev(); setPaused(true); }}
|
||||||
aria-label="Previous stories"
|
aria-label="Previous stories"
|
||||||
className="w-7 h-7 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#666] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
|
||||||
<polyline points="15 18 9 12 15 6" />
|
<polyline points="15 18 9 12 15 6" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => { goNext(); setPaused(true); }}
|
onClick={() => { goNext(); setPaused(true); }}
|
||||||
aria-label="Next stories"
|
aria-label="Next stories"
|
||||||
className="w-7 h-7 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#666] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
|
||||||
<polyline points="9 18 15 12 9 6" />
|
<polyline points="9 18 15 12 9 6" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{/* Position indicator */}
|
{/* Position indicator */}
|
||||||
<span className="font-body text-[11px] text-[#555] ml-1">
|
<span className="font-body text-[11px] text-[#888] ml-1">
|
||||||
{offset + 1}–{Math.min(offset + CARDS_PER_PAGE, rotatingStories?.length)}/{rotatingStories?.length}
|
{offset + 1}–{Math.min(offset + CARDS_PER_PAGE, rotatingStories?.length)}/{rotatingStories?.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { Suspense } from "react";
|
||||||
import NewsPageClient from "./NewsPageClient";
|
import NewsPageClient from "./NewsPageClient";
|
||||||
import { getLegacyArticlesBySection } from "@/lib/articles/legacy-source";
|
import { getLegacyArticlesBySection } from "@/lib/articles/legacy-source";
|
||||||
|
|
||||||
@@ -13,5 +14,10 @@ export const metadata: Metadata = {
|
|||||||
|
|
||||||
export default async function NewsPage() {
|
export default async function NewsPage() {
|
||||||
const articles = await getLegacyArticlesBySection("news", 200);
|
const articles = await getLegacyArticlesBySection("news", 200);
|
||||||
return <NewsPageClient articles={articles} />;
|
// Suspense boundary required because NewsPageClient uses useSearchParams.
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<NewsPageClient articles={articles} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,15 +365,6 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* HEADER LEADERBOARD — site-wide 728x90 between utility bar and main nav */}
|
|
||||||
{ADS_728X90.length > 0 && (
|
|
||||||
<div className="bg-[#0a0a0a] border-b border-[#1a1a1a] py-3 hidden md:block">
|
|
||||||
<div className="max-w-container mx-auto px-4 flex justify-center">
|
|
||||||
<AdImage ad={shuffle(ADS_728X90)[0]} priority page="header" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* MAIN NAV */}
|
{/* MAIN NAV */}
|
||||||
<div className={`sticky top-0 z-50 transition-shadow duration-200 ${scrolled ? "shadow-nav" : ""}`}>
|
<div className={`sticky top-0 z-50 transition-shadow duration-200 ${scrolled ? "shadow-nav" : ""}`}>
|
||||||
<nav className="bg-[#111111] border-b border-[#252525]" role="navigation" aria-label="Main navigation">
|
<nav className="bg-[#111111] border-b border-[#252525]" role="navigation" aria-label="Main navigation">
|
||||||
@@ -632,6 +623,15 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* HEADER LEADERBOARD — site-wide 728x90 between main nav and the ticker */}
|
||||||
|
{ADS_728X90.length > 0 && (
|
||||||
|
<div className="bg-[#0a0a0a] border-b border-[#1a1a1a] py-3 hidden md:block">
|
||||||
|
<div className="max-w-container mx-auto px-4 flex justify-center">
|
||||||
|
<AdImage ad={shuffle(ADS_728X90)[0]} priority page="header" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user