225 lines
6.2 KiB
TypeScript
225 lines
6.2 KiB
TypeScript
export const dynamic = "force-dynamic";
|
||
|
||
import type { Metadata } from "next";
|
||
import fs from "fs";
|
||
import path from "path";
|
||
import Link from "next/link";
|
||
import { PageHeader } from "@/components/layout/page-header";
|
||
import { FindInPage } from "@/components/layout/find-in-page";
|
||
import { notFound } from "next/navigation";
|
||
import {
|
||
getBookMeta,
|
||
getSingleVerse,
|
||
getChapterVerseIndex,
|
||
type VerseIndexEntry,
|
||
type VerseRecord,
|
||
} from "@/lib/api";
|
||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||
|
||
type NbsMeta = {
|
||
book: string;
|
||
title: string;
|
||
author: string;
|
||
totalChapters: number;
|
||
chapters: { number: number; title: string; totalVerses: number }[];
|
||
};
|
||
|
||
const NBS_JSON_DIR = path.join(process.cwd(), "lib", "jsons", "books", "nbs");
|
||
|
||
// Fallback functions that read from local JSON when DB is empty
|
||
async function getMetaWithFallback() {
|
||
const meta = await getBookMeta("nbs");
|
||
if (meta) return meta as unknown as NbsMeta;
|
||
|
||
const introPath = path.join(NBS_JSON_DIR, "intro.json");
|
||
if (!fs.existsSync(introPath)) return null;
|
||
try {
|
||
return JSON.parse(fs.readFileSync(introPath, "utf-8")) as NbsMeta;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function getVerseIndexWithFallback(
|
||
chapter: number,
|
||
): Promise<VerseIndexEntry[]> {
|
||
const index = await getChapterVerseIndex("nbs", null, chapter);
|
||
if (index.length > 0) return index;
|
||
|
||
// Fallback: read verse files from local JSON
|
||
const versesDir = path.join(
|
||
NBS_JSON_DIR,
|
||
"chapters",
|
||
String(chapter).padStart(2, "0"),
|
||
"verses",
|
||
);
|
||
if (!fs.existsSync(versesDir)) return [] as VerseIndexEntry[];
|
||
|
||
const files = fs
|
||
.readdirSync(versesDir)
|
||
.filter((f) => f.endsWith(".json"))
|
||
.sort();
|
||
return files.map((f): VerseIndexEntry => {
|
||
const v = JSON.parse(fs.readFileSync(path.join(versesDir, f), "utf-8"));
|
||
return {
|
||
verse: v.verse,
|
||
verseEnd: v.verse_end || null,
|
||
reference: v.reference,
|
||
};
|
||
});
|
||
}
|
||
|
||
async function getVerseWithFallback(
|
||
chapter: number,
|
||
verse: number,
|
||
): Promise<VerseRecord | null> {
|
||
const v = await getSingleVerse("nbs", null, chapter, verse);
|
||
if (v) return v;
|
||
|
||
// Fallback: read from local JSON
|
||
const versesDir = path.join(
|
||
NBS_JSON_DIR,
|
||
"chapters",
|
||
String(chapter).padStart(2, "0"),
|
||
"verses",
|
||
);
|
||
if (!fs.existsSync(versesDir)) return null;
|
||
|
||
const files = fs
|
||
.readdirSync(versesDir)
|
||
.filter((f) => f.endsWith(".json"))
|
||
.sort();
|
||
for (const f of files) {
|
||
const data = JSON.parse(fs.readFileSync(path.join(versesDir, f), "utf-8"));
|
||
if (data.verse === verse) {
|
||
return {
|
||
id: `nbs-${chapter}-${verse}`,
|
||
book: "nbs",
|
||
division: null,
|
||
chapter: data.chapter,
|
||
verse: data.verse,
|
||
verseEnd: data.verse_end || null,
|
||
reference: data.reference,
|
||
bookTitle: data.book_title,
|
||
devanagari: data.devanagari,
|
||
bengali: null,
|
||
transliteration: data.transliteration,
|
||
synonyms: data.synonyms,
|
||
translation: data.translation,
|
||
purport: data.purport,
|
||
};
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
export async function generateMetadata({
|
||
params,
|
||
searchParams,
|
||
}: {
|
||
params: Promise<{ chapter: string; verse: string }>;
|
||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||
}): Promise<Metadata> {
|
||
const { chapter, verse } = await params;
|
||
const sp = await searchParams;
|
||
const v = await getSingleVerse(
|
||
"nbs",
|
||
null,
|
||
parseInt(chapter),
|
||
parseInt(verse),
|
||
);
|
||
const paragraphs = v ? [v.translation, ...v.purport] : undefined;
|
||
const quote = extractQuoteFromHl(sp, paragraphs);
|
||
if (quote) {
|
||
return buildOgMeta({
|
||
title: "Nārada-bhakti-sūtra",
|
||
detail: `Chapter ${chapter}, Verse ${verse}`,
|
||
quote,
|
||
});
|
||
}
|
||
return ogForVerse("nbs", {
|
||
chapter: parseInt(chapter),
|
||
verse: parseInt(verse),
|
||
});
|
||
}
|
||
|
||
export default async function VersePage({
|
||
params,
|
||
}: {
|
||
params: Promise<{ chapter: string; verse: string }>;
|
||
}) {
|
||
const { chapter, verse: verseParam } = await params;
|
||
|
||
const nbsMeta = await getMetaWithFallback();
|
||
if (!nbsMeta) notFound();
|
||
|
||
const chapterInfo = nbsMeta.chapters?.find(
|
||
(c) => c.number === parseInt(chapter),
|
||
);
|
||
if (!chapterInfo?.title) notFound();
|
||
|
||
const title: string = chapterInfo.title;
|
||
|
||
const verseIndex = await getVerseIndexWithFallback(parseInt(chapter));
|
||
if (verseIndex.length === 0) notFound();
|
||
|
||
const requestedVerse = parseInt(verseParam);
|
||
const verse = await getVerseWithFallback(parseInt(chapter), requestedVerse);
|
||
if (!verse) notFound();
|
||
|
||
// Build mobile nav groups — verses for the current chapter
|
||
const mobileNavGroups: MobileNavGroup[] = [
|
||
{
|
||
title: "Verses",
|
||
items: verseIndex.map((v) => ({
|
||
slug: String(v.verse),
|
||
label: v.verseEnd
|
||
? `Verse ${chapter}.${v.verse}–${v.verseEnd}`
|
||
: `Verse ${chapter}.${v.verse}`,
|
||
href: `/books/nbs/${chapter}/${v.verse}`,
|
||
type: "verse",
|
||
})),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div className="min-h-screen bg-background">
|
||
<PageHeader sticky>
|
||
<Link
|
||
href="/books/nbs"
|
||
className="text-sm text-muted-foreground hover:text-foreground"
|
||
>
|
||
← Nārada-bhakti-sūtra
|
||
</Link>
|
||
<div className="mt-2 flex items-start justify-between gap-3">
|
||
<h1 className="text-2xl font-bold text-foreground">
|
||
Chapter {chapter}: {title}
|
||
</h1>
|
||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||
</div>
|
||
</PageHeader>
|
||
|
||
<div className="max-w-5xl mx-auto flex">
|
||
<ChapterViewer
|
||
initialVerse={verse}
|
||
initialVerseNum={requestedVerse}
|
||
verseIndex={verseIndex.map((v) => ({
|
||
verse: v.verse,
|
||
verseEnd: v.verseEnd ?? undefined,
|
||
reference: v.reference,
|
||
}))}
|
||
book="nbs"
|
||
division={null}
|
||
chapter={parseInt(chapter)}
|
||
basePath={`/books/nbs/${chapter}`}
|
||
showBengali={false}
|
||
mobileNavGroups={mobileNavGroups}
|
||
mobileNavBookTitle={`Chapter ${chapter}: ${title}`}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|