chore: refactor settings route
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
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 { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type BsMeta = {
|
||||
book: string;
|
||||
title: string;
|
||||
author: string;
|
||||
totalChapters: number;
|
||||
chapters: { number: number; title: string; totalVerses: number }[];
|
||||
};
|
||||
|
||||
const BS_JSON_DIR = path.join(process.cwd(), "lib", "jsons", "books", "bs");
|
||||
|
||||
// Fallback functions that read from local JSON when DB is empty
|
||||
async function getMetaWithFallback() {
|
||||
const meta = await getBookMeta("bs");
|
||||
if (meta) return meta as unknown as BsMeta;
|
||||
|
||||
const introPath = path.join(BS_JSON_DIR, "intro.json");
|
||||
if (!fs.existsSync(introPath)) return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(introPath, "utf-8")) as BsMeta;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getVerseIndexWithFallback(
|
||||
chapter: number,
|
||||
): Promise<VerseIndexEntry[]> {
|
||||
const index = await getChapterVerseIndex("bs", null, chapter);
|
||||
if (index.length > 0) return index;
|
||||
|
||||
// Fallback: read verse files from local JSON
|
||||
const versesDir = path.join(
|
||||
BS_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("bs", null, chapter, verse);
|
||||
if (v) return v;
|
||||
|
||||
// Fallback: read from local JSON
|
||||
const versesDir = path.join(
|
||||
BS_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: `bs-${chapter}-${verse}`,
|
||||
book: "bs",
|
||||
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 getVerseWithFallback(parseInt(chapter), parseInt(verse));
|
||||
const paragraphs = v ? [v.translation, ...v.purport] : undefined;
|
||||
const quote = extractQuoteFromHl(sp, paragraphs);
|
||||
if (quote) {
|
||||
return buildOgMeta({
|
||||
title: "Śrī Brahma-saṁhitā",
|
||||
detail: `Chapter ${chapter}, Verse ${parseInt(verse)}`,
|
||||
quote,
|
||||
});
|
||||
}
|
||||
return ogForVerse("bs", {
|
||||
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 bsMeta = await getMetaWithFallback();
|
||||
if (!bsMeta) notFound();
|
||||
|
||||
const chapterInfo = bsMeta.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/bs/${chapter}/${v.verse}`,
|
||||
type: "verse",
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bs"
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
← Śrī Brahma-saṁhitā
|
||||
</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="bs"
|
||||
division={null}
|
||||
chapter={parseInt(chapter)}
|
||||
basePath={`/books/bs/${chapter}`}
|
||||
showBengali={false}
|
||||
mobileNavGroups={mobileNavGroups}
|
||||
mobileNavBookTitle={`Chapter ${chapter}: ${title}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user