236 lines
7.6 KiB
TypeScript
236 lines
7.6 KiB
TypeScript
export const dynamic = "force-dynamic";
|
||
|
||
import type { Metadata } from "next";
|
||
import Link from "next/link";
|
||
import { redirect, notFound } from "next/navigation";
|
||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||
import { getBookMeta, getProseChapter, getChapterVerseIndex } from "@/lib/api";
|
||
import { CC_LILAS } from "@/lib/constants";
|
||
import { buildOgMeta } from "@/lib/og";
|
||
import { PageHeader } from "@/components/layout/page-header";
|
||
import { Card, CardContent } from "@/components/ui/card";
|
||
import { buttonVariants } from "@/components/ui/button";
|
||
import { FindInPage } from "@/components/layout/find-in-page";
|
||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
type ChapterInfo = { number: number; title: string; totalVerses: number };
|
||
type LilaInfo = {
|
||
key: string;
|
||
name: string;
|
||
totalChapters: number;
|
||
totalVerses: number;
|
||
chapters: ChapterInfo[];
|
||
};
|
||
type VerseEntry = { verse: number; verseEnd?: number | null; reference: string };
|
||
|
||
export async function generateMetadata({
|
||
params,
|
||
}: {
|
||
params: Promise<{ lila: string; chapter: string }>;
|
||
}): Promise<Metadata> {
|
||
const { lila, chapter } = await params;
|
||
const lilaName = CC_LILAS[lila] || lila;
|
||
return buildOgMeta({
|
||
title: "Śrī Caitanya-caritāmṛta",
|
||
detail: `${lilaName}, Chapter ${chapter}`,
|
||
});
|
||
}
|
||
|
||
function verseLabel(v: VerseEntry): string {
|
||
return v.verseEnd ? `${v.verse}–${v.verseEnd}` : String(v.verse);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Top/bottom navigation — mirrors the verse pages' VerseNav: a Contents
|
||
// toggle (mobile) listing this chapter's verses, Previous/Next (chapter), and
|
||
// a "Chapter X of N" label.
|
||
// ---------------------------------------------------------------------------
|
||
function ChapterNav({
|
||
lila,
|
||
chapters,
|
||
current,
|
||
chapterTitle,
|
||
verseIndex,
|
||
}: {
|
||
lila: string;
|
||
chapters: ChapterInfo[];
|
||
current: number;
|
||
chapterTitle: string;
|
||
verseIndex: VerseEntry[];
|
||
}) {
|
||
const idx = chapters.findIndex((c) => c.number === current);
|
||
const prev = idx > 0 ? chapters[idx - 1] : null;
|
||
const next = idx < chapters.length - 1 ? chapters[idx + 1] : null;
|
||
|
||
// The contents drawer lists the active chapter's verses (same as the
|
||
// verse-page sidebar) so the reader can jump straight in.
|
||
const groups: MobileNavGroup[] = [
|
||
{
|
||
title: "Verses",
|
||
items: verseIndex.map((v) => ({
|
||
slug: String(v.verse),
|
||
label: `Verse ${verseLabel(v)}`,
|
||
href: `/books/cc/${lila}/${current}/${v.verse}`,
|
||
type: "verse",
|
||
})),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div className="flex items-center gap-2">
|
||
<MobileNav
|
||
groups={groups}
|
||
currentSlug=""
|
||
bookTitle={`Chapter ${current}: ${chapterTitle}`}
|
||
/>
|
||
{prev ? (
|
||
<Link
|
||
href={`/books/cc/${lila}/${prev.number}`}
|
||
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
|
||
>
|
||
<ChevronLeft className="size-4" />
|
||
<span className="hidden sm:inline">Previous</span>
|
||
</Link>
|
||
) : (
|
||
<div className="hidden lg:block" />
|
||
)}
|
||
</div>
|
||
|
||
<span className="text-sm text-muted-foreground">
|
||
Chapter {current} of {chapters.length}
|
||
</span>
|
||
|
||
{next ? (
|
||
<Link
|
||
href={`/books/cc/${lila}/${next.number}`}
|
||
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
|
||
>
|
||
<span className="hidden sm:inline">Next</span>
|
||
<ChevronRight className="size-4" />
|
||
</Link>
|
||
) : (
|
||
<div />
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default async function CcChapterPage({
|
||
params,
|
||
}: {
|
||
params: Promise<{ lila: string; chapter: string }>;
|
||
}) {
|
||
const { lila, chapter } = await params;
|
||
const chapterNum = parseInt(chapter);
|
||
|
||
const [meta, summary, verseIndex] = await Promise.all([
|
||
getBookMeta("cc"),
|
||
getProseChapter("cc", 0, `${lila}-${chapterNum}`),
|
||
getChapterVerseIndex("cc", lila, chapterNum),
|
||
]);
|
||
|
||
if (!meta) notFound();
|
||
const lilaInfo = (meta.lilas as LilaInfo[] | undefined)?.find(
|
||
(l) => l.key === lila,
|
||
);
|
||
if (!lilaInfo) notFound();
|
||
const chapterInfo = lilaInfo.chapters.find((ch) => ch.number === chapterNum);
|
||
if (!chapterInfo) notFound();
|
||
if (verseIndex.length === 0) notFound();
|
||
|
||
const firstVerse = verseIndex[0].verse;
|
||
|
||
// Chapters without a summary (e.g. Ādi 7) go straight to the verses.
|
||
if (!summary || summary.paragraphs.length === 0) {
|
||
redirect(`/books/cc/${lila}/${chapterNum}/${firstVerse}`);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-background">
|
||
<PageHeader sticky>
|
||
<Link
|
||
href={`/books/cc/${lila}`}
|
||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||
>
|
||
← {lilaInfo.name}
|
||
</Link>
|
||
<div className="mt-3 flex items-start justify-between gap-3">
|
||
<h1 className="text-2xl font-bold text-foreground">
|
||
Chapter {chapterNum}: {chapterInfo.title}
|
||
</h1>
|
||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||
</div>
|
||
</PageHeader>
|
||
|
||
<div className="max-w-5xl mx-auto flex">
|
||
{/* Desktop sidebar — verses of the active chapter (matches the
|
||
verse-page sidebar) so the reader can jump to any verse. */}
|
||
<aside className="hidden lg:block w-64 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-165px)] overflow-y-auto py-8 pr-4">
|
||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3 px-2">
|
||
Verses
|
||
</h2>
|
||
<nav className="grid grid-cols-3 gap-1">
|
||
{verseIndex.map((v) => (
|
||
<Link
|
||
key={v.verse}
|
||
href={`/books/cc/${lila}/${chapterNum}/${v.verse}`}
|
||
className="px-2 py-1 rounded-md text-sm tabular-nums text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||
>
|
||
{verseLabel(v)}
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
|
||
{/* Main — chapter summary */}
|
||
<main className="flex-1 min-w-0 px-6 py-8 space-y-6">
|
||
{/* Top navigation */}
|
||
<ChapterNav
|
||
lila={lila}
|
||
chapters={lilaInfo.chapters}
|
||
current={chapterNum}
|
||
chapterTitle={chapterInfo.title}
|
||
verseIndex={verseIndex}
|
||
/>
|
||
|
||
<p className="text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||
Chapter Summary
|
||
</p>
|
||
<Card id="book-content">
|
||
<CardContent className="py-8 px-6 sm:px-8">
|
||
<div className="space-y-4">
|
||
{summary.paragraphs.map((paragraph, i) => (
|
||
<div
|
||
key={i}
|
||
className="text-base text-foreground leading-relaxed"
|
||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||
/>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Link
|
||
href={`/books/cc/${lila}/${chapterNum}/${firstVerse}`}
|
||
className={cn(buttonVariants({ size: "lg" }), "gap-2")}
|
||
>
|
||
Read the chapter
|
||
<ChevronRight className="size-4" />
|
||
</Link>
|
||
|
||
{/* Bottom navigation */}
|
||
<ChapterNav
|
||
lila={lila}
|
||
chapters={lilaInfo.chapters}
|
||
current={chapterNum}
|
||
chapterTitle={chapterInfo.title}
|
||
verseIndex={verseIndex}
|
||
/>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|