import Link from "next/link"; import { PublishedCatalogSchema } from "@/lib/schemas/published-book"; import type { PublishedBookEntry } from "@/lib/schemas/published-book"; import LibraryGrid from "./LibraryGrid"; import MyListPanel from "./MyListPanel"; export const revalidate = 60; // ── Data fetch ──────────────────────────────────────────────────────────────── async function fetchCatalog() { const publicUrl = process.env.R2_PUBLIC_URL; if (!publicUrl) return null; try { const res = await fetch( `${publicUrl.replace(/\/$/, "")}/published/index.json`, { next: { revalidate: 60 } }, ); if (!res.ok) return null; const parsed = PublishedCatalogSchema.safeParse(await res.json()); return parsed.success ? parsed.data : null; } catch { return null; } } // ── New Releases Hero ───────────────────────────────────────────────────────── const ACCENT_BADGE: Record = { amber: "bg-amber-500/15 text-amber-400 border-amber-500/30", cyan: "bg-cyan-500/15 text-cyan-400 border-cyan-500/30", emerald: "bg-emerald-500/15 text-emerald-400 border-emerald-500/30", rose: "bg-rose-500/15 text-rose-400 border-rose-500/30", violet: "bg-violet-500/15 text-violet-400 border-violet-500/30", slate: "bg-slate-500/15 text-slate-400 border-slate-500/30", }; const COVER_GRADIENT: Record = { amber: "from-amber-950 via-amber-900 to-amber-800", cyan: "from-cyan-950 via-cyan-900 to-cyan-800", emerald: "from-emerald-950 via-emerald-900 to-emerald-800", rose: "from-rose-950 via-rose-900 to-rose-800", violet: "from-violet-950 via-violet-900 to-violet-800", slate: "from-slate-900 via-slate-800 to-slate-700", }; const COVER_TITLE_COLOR: Record = { amber: "text-amber-100", cyan: "text-cyan-100", emerald: "text-emerald-100", rose: "text-rose-100", violet: "text-violet-100", slate: "text-slate-100", }; function BookCover({ book, className }: { book: PublishedBookEntry; className?: string }) { const accent = book.coverAccent ?? "amber"; const grad = COVER_GRADIENT[accent] ?? COVER_GRADIENT.amber; const titleC = COVER_TITLE_COLOR[accent] ?? COVER_TITLE_COLOR.amber; return (
{book.coverImageUrl ? ( // eslint-disable-next-line @next/next/no-img-element {book.title} ) : ( <>

{book.authorName}

{book.title}

)}
); } function NewReleasesHero({ books }: { books: PublishedBookEntry[] }) { const recent = [...books] .sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()) .slice(0, 5); const [featured, ...shelf] = recent; return (

New Releases

{/* ── Mobile: cinematic portrait card ───────────────────────────────── */}
{/* Full-bleed cover */}
{featured.coverImageUrl ? ( // eslint-disable-next-line @next/next/no-img-element {featured.title} ) : ( <>
)} {/* Gradient overlay — text lives here */}
Latest Release

{featured.title}

{featured.subtitle &&

{featured.subtitle}

}

{featured.authorName}

·

{featured.chapterCount} chapters

{/* Synopsis below */}

{featured.synopsis}

{/* Horizontal shelf */} {shelf.length > 0 && (

Also New

{shelf.map((book) => (

{book.title}

{book.authorName}

))}
)}
{/* ── Desktop: 3-col grid ───────────────────────────────────────────── */}
{featured.coverImageUrl && ( <> {/* eslint-disable-next-line @next/next/no-img-element */}
)}
Latest Release

{featured.title}

{featured.subtitle &&

{featured.subtitle}

}

{featured.synopsis}

{featured.authorName}

·

{featured.chapterCount} chapters

{shelf.slice(0, 2).map((book) => ( {book.coverImageUrl && ( <> {/* eslint-disable-next-line @next/next/no-img-element */}
)}
New

{book.title}

{book.synopsis}

{book.authorName}

))}
); } // ── Page ────────────────────────────────────────────────────────────────────── export default async function LibraryPage() { const catalog = await fetchCatalog(); const configured = !!process.env.R2_PUBLIC_URL; return (
{/* ── Sticky nav ──────────────────────────────────────────────────────── */}

Nexus

Library

{!configured ? (
📚

Library not configured

Set{" "} R2_PUBLIC_URL {" "} in your environment to enable the public reading library.

) : !catalog || catalog.books.length === 0 ? (
📖

No books published yet

Complete a book in Nexus Director and click "Publish to Library" to see it here.

Open Ebook Pipeline →
) : (
{/* ── New Releases Hero ─────────────────────────────────────────── */} {/* ── Personal reading list ─────────────────────────────────────── */} {/* ── Full grid ────────────────────────────────────────────────── */}

All Books

)}
); }