Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5abe33c09 | ||
|
|
f96e7dd2f1 | ||
|
|
0cf6df5942 | ||
|
|
5c72416ae8 | ||
|
|
197e13c0ef | ||
|
|
0608e2b6f6 | ||
|
|
cb376f8497 | ||
|
|
eac56b7974 | ||
|
|
be4c2f17fe | ||
|
|
6cc9285bad | ||
|
|
ab35767374 | ||
|
|
c551d2f7a8 | ||
|
|
a4fd63695a | ||
|
|
91621693a6 | ||
|
|
79f0bd9223 | ||
|
|
7585e6cb31 | ||
|
|
c8de98c8de | ||
|
|
72a092b3c9 | ||
|
|
752ad20fed | ||
|
|
0280a81777 | ||
|
|
69e5f2c45c | ||
|
|
45c7cf047e | ||
|
|
a4baf6d215 | ||
|
|
455ed0f7cb | ||
|
|
7c3cf656b5 | ||
|
|
23d06a3db5 | ||
|
|
3accc753d0 | ||
|
|
b04ed1d3aa | ||
|
|
9808dd3033 | ||
|
|
b248f962a1 | ||
|
|
61581b67a7 | ||
|
|
eb0c981d3c | ||
|
|
37305201bd | ||
|
|
4471ef10aa | ||
|
|
397a09e814 | ||
|
|
9e68184750 | ||
|
|
439664a97d | ||
|
|
cc5633c37a | ||
|
|
6a0559e608 | ||
|
|
25a729959f | ||
|
|
af5ab9cf6b | ||
|
|
884a984e50 | ||
|
|
ead8ad02e8 | ||
|
|
4b159cb4d9 | ||
|
|
3903423d06 | ||
|
|
a46e29d998 | ||
|
|
922d76e21f | ||
|
|
909ae67320 | ||
|
|
482a845f37 |
@@ -1,3 +1,5 @@
|
||||
NEXT_PUBLIC_SITE_URL=https://prabhupadasays.org
|
||||
|
||||
# BFF API Server (server-side fetches)
|
||||
BFF_URL=http://localhost:8080
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import {
|
||||
BookOpen,
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function BbdChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/bbd/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Beyond Birth and Death" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bbd"
|
||||
@@ -77,12 +81,15 @@ export default async function BbdChapterPage({
|
||||
>
|
||||
← Beyond Birth and Death
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function BbdChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBbdMeta, getBbdIntroSegments } from "@/lib/bbd";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function BbdPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bbd", name: "Beyond Birth and Death" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -101,9 +102,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Bhagavad-gītā
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<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">
|
||||
|
||||
@@ -3,9 +3,13 @@ export const dynamic = "force-dynamic";
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getChapterVerseIndex } from "@/lib/api";
|
||||
import Link from "next/link";
|
||||
import { getChapterVerseIndex, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -13,6 +17,14 @@ export async function generateMetadata({
|
||||
params: Promise<{ chapter: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { chapter } = await params;
|
||||
// Non-numeric segments are front matter (dedication, foreword, …).
|
||||
if (isNaN(parseInt(chapter))) {
|
||||
const segment = await getProseChapter("bg", 0, chapter);
|
||||
return buildOgMeta({
|
||||
title: BOOK_NAMES["bg"],
|
||||
detail: segment?.title ?? "Front Matter",
|
||||
});
|
||||
}
|
||||
return buildOgMeta({ title: BOOK_NAMES["bg"], detail: `Chapter ${chapter}` });
|
||||
}
|
||||
|
||||
@@ -22,6 +34,50 @@ export default async function ChapterPage({
|
||||
params: Promise<{ chapter: string }>;
|
||||
}) {
|
||||
const { chapter } = await params;
|
||||
|
||||
// Front matter — non-numeric slugs (dedication, foreword, preface,
|
||||
// introduction, disciplic-succession) resolve to prose intro-segments.
|
||||
if (isNaN(parseInt(chapter))) {
|
||||
const segment = await getProseChapter("bg", 0, chapter);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bg"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← {BOOK_NAMES["bg"]}
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Numeric chapters — redirect to the first verse.
|
||||
const verseIndex = await getChapterVerseIndex("bg", null, parseInt(chapter));
|
||||
if (verseIndex.length === 0) {
|
||||
notFound();
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
|
||||
@@ -18,7 +20,10 @@ type ChapterInfo = {
|
||||
};
|
||||
|
||||
export default async function BgPage() {
|
||||
const meta = await getBookMeta("bg");
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("bg"),
|
||||
getProseChapters("bg", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) notFound();
|
||||
|
||||
const intro = meta as unknown as {
|
||||
@@ -31,6 +36,7 @@ export default async function BgPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bg", name: "Bhagavad-gītā As It Is" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -43,7 +49,39 @@ export default async function BgPage() {
|
||||
</h1>
|
||||
<p className="mt-1 text-muted-foreground">{intro.author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{/* Front Matter */}
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/bg/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chapters */}
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Chapters
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
@@ -57,7 +95,9 @@ export default async function BgPage() {
|
||||
<span className="text-sm text-primary font-medium">
|
||||
Chapter {ch.number}
|
||||
</span>
|
||||
<p className="font-medium text-foreground">{ch.title}</p>
|
||||
<p className="font-medium text-foreground">
|
||||
{ch.title}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground sm:shrink-0 sm:ml-4">
|
||||
<ExtractedVerseCount count={ch.totalVerses} />
|
||||
@@ -68,6 +108,7 @@ export default async function BgPage() {
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -62,6 +65,7 @@ export default async function BkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/bk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Bhagavāner Kathā" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bk"
|
||||
@@ -69,12 +73,15 @@ export default async function BkChapterPage({
|
||||
>
|
||||
← Bhagavāner Kathā
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -92,7 +99,7 @@ export default async function BkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBkMeta } from "@/lib/bk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function BkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bk", name: "Bhagavāner Kathā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
type VerseRecord,
|
||||
} from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -187,9 +188,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Śrī Brahma-saṁhitā
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<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">
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -59,13 +60,16 @@ export default async function BsChapterPage({
|
||||
>
|
||||
← Śrī Brahma-saṁhitā
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -80,6 +82,7 @@ export default async function BsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bs", name: "Śrī Brahma-saṁhitā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -62,6 +65,7 @@ export default async function CbhcChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/cbhc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Śrī Caitanya-bhāgavata Commentary" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/cbhc"
|
||||
@@ -69,12 +73,15 @@ export default async function CbhcChapterPage({
|
||||
>
|
||||
← Śrī Caitanya-bhāgavata Commentary
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -92,7 +99,7 @@ export default async function CbhcChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getCbhcMeta } from "@/lib/cbhc";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function CbhcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/cbhc", name: "Śrī Caitanya-bhāgavata Commentary" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -111,9 +112,12 @@ export default async function CcVersePage({
|
||||
>
|
||||
← {lilaInfo.name}
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {chapterInfo.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getChapterVerseIndex } from "@/lib/api";
|
||||
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,
|
||||
@@ -18,15 +37,191 @@ export async function generateMetadata({
|
||||
});
|
||||
}
|
||||
|
||||
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 verseIndex = await getChapterVerseIndex("cc", lila, parseInt(chapter));
|
||||
if (verseIndex.length === 0) {
|
||||
notFound();
|
||||
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}`);
|
||||
}
|
||||
redirect(`/books/cc/${lila}/${chapter}/${verseIndex[0].verse}`);
|
||||
|
||||
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>
|
||||
|
||||
{/* Bottom navigation */}
|
||||
<ChapterNav
|
||||
lila={lila}
|
||||
chapters={lilaInfo.chapters}
|
||||
current={chapterNum}
|
||||
chapterTitle={chapterInfo.title}
|
||||
verseIndex={verseIndex}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { ExtractedVerseCount } from "../../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES, CC_LILAS } from "@/lib/constants";
|
||||
|
||||
@@ -14,12 +15,15 @@ export async function generateMetadata({
|
||||
params: Promise<{ lila: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { lila } = await params;
|
||||
const lilaName = CC_LILAS[lila] || lila;
|
||||
const bookTitle = BOOK_NAMES["cc"] || "Śrī Caitanya-caritāmṛta";
|
||||
return buildOgMeta({
|
||||
title: bookTitle,
|
||||
detail: lilaName,
|
||||
});
|
||||
// A non-līlā segment (front-flap, preface, introduction) is front matter.
|
||||
if (!CC_LILAS[lila]) {
|
||||
const segment = await getProseChapter("cc", 0, lila);
|
||||
if (segment) {
|
||||
return buildOgMeta({ title: bookTitle, detail: segment.title });
|
||||
}
|
||||
}
|
||||
return buildOgMeta({ title: bookTitle, detail: CC_LILAS[lila] || lila });
|
||||
}
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -47,7 +51,48 @@ export default async function LilaPage({
|
||||
|
||||
const lilas = meta.lilas as LilaInfo[];
|
||||
const lilaInfo = lilas.find((l) => l.key === lila);
|
||||
if (!lilaInfo) notFound();
|
||||
|
||||
// Front matter — the param is not a līlā key, so resolve it as a prose
|
||||
// intro-segment (front-flap, preface, introduction).
|
||||
if (!lilaInfo) {
|
||||
const segment = await getProseChapter("cc", 0, lila);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/cc"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← Śrī Caitanya-caritāmṛta
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const totalVerses = lilaInfo.chapters.reduce(
|
||||
(sum, ch) => sum + ch.totalVerses,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
@@ -26,7 +28,10 @@ export function generateMetadata(): Metadata {
|
||||
}
|
||||
|
||||
export default async function CcPage() {
|
||||
const meta = await getBookMeta("cc");
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("cc"),
|
||||
getProseChapters("cc", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) {
|
||||
return (
|
||||
<div className="p-8 text-center text-muted-foreground">
|
||||
@@ -41,6 +46,7 @@ export default async function CcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/cc", name: "Śrī Caitanya-caritāmṛta" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -51,7 +57,33 @@ export default async function CcPage() {
|
||||
<h1 className="mt-3 text-3xl font-bold text-foreground">{title}</h1>
|
||||
<p className="mt-1 text-muted-foreground">{author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/cc/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="divide-y divide-border">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
|
||||
@@ -6,7 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -389,6 +392,19 @@ export function ChapterViewer({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Structured data for the currently-shown verse (SSR'd for the initial
|
||||
verse, updates on client navigation). */}
|
||||
{currentVerse ? (
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `${basePath}/${currentVerseNum}`,
|
||||
headline: `${BOOK_NAMES[book] ?? book} ${currentVerse.reference}`,
|
||||
description: currentVerse.translation,
|
||||
isPartOf: BOOK_NAMES[book] ?? book,
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* ---- Sidebar: Verse Index ---- */}
|
||||
{/* Sticky sidebar. The max-h leaves 76px at the viewport bottom for
|
||||
the site footer so the sidebar doesn't slide behind it when the
|
||||
@@ -440,7 +456,8 @@ export function ChapterViewer({
|
||||
mobileNavBookTitle={mobileNavBookTitle}
|
||||
/>
|
||||
|
||||
{/* Verse content */}
|
||||
{/* Verse content — id targeted by each page header's FindInPage. */}
|
||||
<div id="book-content">
|
||||
{loading && !currentVerse ? (
|
||||
<div className="flex items-center justify-center py-24 text-muted-foreground gap-2">
|
||||
<Loader2 className="size-5 animate-spin" />
|
||||
@@ -453,6 +470,7 @@ export function ChapterViewer({
|
||||
Verse not found.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom navigation */}
|
||||
<VerseNav
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -76,6 +79,7 @@ export default async function DsChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ds/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Dialectic Spiritualism" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ds"
|
||||
@@ -83,12 +87,15 @@ export default async function DsChapterPage({
|
||||
>
|
||||
← Dialectic Spiritualism
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Chapter {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -106,7 +113,7 @@ export default async function DsChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getDsMeta, getDsIntroSegments } from "@/lib/ds";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function DsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ds", name: "Dialectic Spiritualism" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function EjChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ej/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Easy Journey to Other Planets" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ej"
|
||||
@@ -77,12 +81,15 @@ export default async function EjChapterPage({
|
||||
>
|
||||
← Easy Journey to Other Planets
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function EjChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEjMeta, getEjIntroSegments } from "@/lib/ej";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function EjPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ej", name: "Easy Journey to Other Planets" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function EkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ek/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Elevation to Kṛṣṇa Consciousness" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ek"
|
||||
@@ -77,12 +81,15 @@ export default async function EkChapterPage({
|
||||
>
|
||||
← Elevation to Kṛṣṇa Consciousness
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function EkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEkMeta, getEkIntroSegments } from "@/lib/ek";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function EkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ek", name: "Elevation to Kṛṣṇa Consciousness" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -66,6 +69,7 @@ export default async function GgChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/gg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Gītār Gān" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/gg"
|
||||
@@ -73,12 +77,15 @@ export default async function GgChapterPage({
|
||||
>
|
||||
← Gītār Gān
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function GgChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getGgMeta, getGgIntroSegments } from "@/lib/gg";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function GgPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/gg", name: "Gītār Gān" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function HkhChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/hkh/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Kṛṣṇa Consciousness Handbook" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/hkh"
|
||||
@@ -77,12 +81,15 @@ export default async function HkhChapterPage({
|
||||
>
|
||||
← The Kṛṣṇa Consciousness Handbook
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function HkhChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getHkhMeta, getHkhIntroSegments } from "@/lib/hkh";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function HkhPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/hkh", name: "The Kṛṣṇa Consciousness Handbook" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
type IsoSidebarItem,
|
||||
} from "@/lib/iso";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type guards
|
||||
@@ -102,6 +105,7 @@ export default async function IsoMantraPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/iso/${slug}`, headline: content.title ?? "Śrī Īśopaniṣad", isPartOf: "Śrī Īśopaniṣad" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/iso"
|
||||
@@ -109,7 +113,8 @@ export default async function IsoMantraPage({
|
||||
>
|
||||
← Śrī Īśopaniṣad
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{verse && (
|
||||
<>
|
||||
<span className="text-primary">{verse.reference}: </span>
|
||||
@@ -118,6 +123,8 @@ export default async function IsoMantraPage({
|
||||
)}
|
||||
{intro && intro.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -157,7 +164,7 @@ export default async function IsoMantraPage({
|
||||
|
||||
function VerseContent({ verse }: { verse: IsoVerse }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
|
||||
{/* Devanagari */}
|
||||
<section>
|
||||
@@ -233,7 +240,7 @@ function VerseContent({ verse }: { verse: IsoVerse }) {
|
||||
|
||||
function IntroContent({ intro }: { intro: IsoIntroSegment }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{intro.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getIsoMeta, getIsoIntroSegments } from "@/lib/iso";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -28,6 +30,7 @@ export default async function IsoPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/iso", name: "Śrī Īśopaniṣad" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -6,12 +6,15 @@ import { notFound } from "next/navigation";
|
||||
import { getProseChapter, getProseChapters } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -65,6 +68,7 @@ export default async function KbChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/kb/${vol}/${slug}`, headline: chapter.title, description: chapter.paragraphs?.[0], isPartOf: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={`/books/kb/${vol}`}
|
||||
@@ -72,12 +76,15 @@ export default async function KbChapterPage({
|
||||
>
|
||||
← Volume {vol}
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterLabel && (
|
||||
<span className="text-primary">{chapterLabel}: </span>
|
||||
)}
|
||||
{chapter.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function KbChapterPage({
|
||||
/>
|
||||
|
||||
{/* Chapter content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{chapter.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export async function generateMetadata({
|
||||
|
||||
@@ -3,7 +3,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -27,6 +29,7 @@ export default async function KbPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/kb", name: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function LclChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/lcl/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Life Comes from Life" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/lcl"
|
||||
@@ -77,12 +81,15 @@ export default async function LclChapterPage({
|
||||
>
|
||||
← Life Comes from Life
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function LclChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLclMeta, getLclIntroSegments } from "@/lib/lcl";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function LclPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/lcl", name: "Life Comes from Life" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -66,6 +69,7 @@ export default async function LobChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/lob/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Light of the Bhāgavata" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/lob"
|
||||
@@ -73,12 +77,15 @@ export default async function LobChapterPage({
|
||||
>
|
||||
← Light of the Bhāgavata
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Text {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function LobChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) =>
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLobMeta, getLobIntroSegments } from "@/lib/lob";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function LobPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/lob", name: "Light of the Bhāgavata" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -66,6 +69,7 @@ export default async function MgChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/mg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/mg"
|
||||
@@ -73,12 +77,15 @@ export default async function MgChapterPage({
|
||||
>
|
||||
← Kṛṣṇa Consciousness: The Matchless Gift
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function MgChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMgMeta, getMgIntroSegments } from "@/lib/mg";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function MgPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mg", name: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
type VerseRecord,
|
||||
} from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import type { Metadata } from "next";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
@@ -187,9 +188,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Mukunda-mālā-stotra
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<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">
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -59,13 +60,16 @@ export default async function MmChapterPage({
|
||||
>
|
||||
← Mukunda-mālā-stotra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -79,6 +81,7 @@ export default async function MmPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mm", name: "Mukunda-mālā-stotra" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function MogChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/mog/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Message of Godhead" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/mog"
|
||||
@@ -77,12 +81,15 @@ export default async function MogChapterPage({
|
||||
>
|
||||
← Message of Godhead
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function MogChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMogMeta, getMogIntroSegments } from "@/lib/mog";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function MogPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mog", name: "Message of Godhead" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { Metadata } from "next";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
} from "@/lib/api";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
|
||||
type NbsMeta = {
|
||||
book: string;
|
||||
@@ -192,9 +193,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Nārada-bhakti-sūtra
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<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">
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -62,13 +63,16 @@ export default async function NbsChapterPage({
|
||||
>
|
||||
← Nārada-bhakti-sūtra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -79,6 +81,7 @@ export default async function NbsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/nbs", name: "Nārada-bhakti-sūtra" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function NodChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/nod/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Nectar of Devotion" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/nod"
|
||||
@@ -77,12 +81,15 @@ export default async function NodChapterPage({
|
||||
>
|
||||
← The Nectar of Devotion
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function NodChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNodMeta, getNodIntroSegments } from "@/lib/nod";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -27,6 +29,7 @@ export default async function NodPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/nod", name: "The Nectar of Devotion" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
type NoiSidebarItem,
|
||||
} from "@/lib/noi";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type guards
|
||||
@@ -93,6 +96,7 @@ export default async function NoiTextPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/noi/${slug}`, headline: content.title ?? "The Nectar of Instruction", isPartOf: "The Nectar of Instruction" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/noi"
|
||||
@@ -100,7 +104,8 @@ export default async function NoiTextPage({
|
||||
>
|
||||
← The Nectar of Instruction
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{verse && (
|
||||
<>
|
||||
<span className="text-primary">Text {verse.text}: </span>
|
||||
@@ -109,6 +114,8 @@ export default async function NoiTextPage({
|
||||
)}
|
||||
{intro && intro.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -148,7 +155,7 @@ export default async function NoiTextPage({
|
||||
|
||||
function VerseContent({ verse }: { verse: NoiVerse }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
|
||||
{/* Devanagari */}
|
||||
<section>
|
||||
@@ -224,7 +231,7 @@ function VerseContent({ verse }: { verse: NoiVerse }) {
|
||||
|
||||
function IntroContent({ intro }: { intro: NoiIntroSegment }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{intro.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNoiMeta, getNoiIntroSegments } from "@/lib/noi";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function NoiPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/noi", name: "The Nectar of Instruction" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function OchkmChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ochkm/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On Chanting Hare Kṛṣṇa" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ochkm"
|
||||
@@ -77,12 +81,15 @@ export default async function OchkmChapterPage({
|
||||
>
|
||||
← On Chanting the Hare Kṛṣṇa Mantra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function OchkmChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getOchkmMeta, getOchkmIntroSegments } from "@/lib/ochkm";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function OchkmPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ochkm", name: "On Chanting Hare Kṛṣṇa" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function OwkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/owk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On the Way to Kṛṣṇa" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/owk"
|
||||
@@ -77,12 +81,15 @@ export default async function OwkChapterPage({
|
||||
>
|
||||
← On the Way to Kṛṣṇa
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function OwkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getOwkMeta, getOwkIntroSegments } from "@/lib/owk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function OwkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/owk", name: "On the Way to Kṛṣṇa" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { getAllBooks, type BookMeta } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BooksGrid, type BookInfo } from "./books-grid";
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -65,6 +68,7 @@ export default async function PkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/pk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Prabhupāda-Kṛpā" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/pk"
|
||||
@@ -72,12 +76,15 @@ export default async function PkChapterPage({
|
||||
>
|
||||
← Prayers of King Kulaśekhara
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Verse {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -95,7 +102,7 @@ export default async function PkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPkMeta } from "@/lib/pk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function PkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/pk", name: "Prabhupāda-Kṛpā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function PoyChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/poy/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Perfection of Yoga" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/poy"
|
||||
@@ -77,12 +81,15 @@ export default async function PoyChapterPage({
|
||||
>
|
||||
← The Perfection of Yoga
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function PoyChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPoyMeta, getPoyIntroSegments } from "@/lib/poy";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function PoyPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/poy", name: "The Perfection of Yoga" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function PqpaChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/pqpa/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Perfect Questions, Perfect Answers" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/pqpa"
|
||||
@@ -77,12 +81,15 @@ export default async function PqpaChapterPage({
|
||||
>
|
||||
← Perfect Questions Perfect Answers
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function PqpaChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPqpaMeta, getPqpaIntroSegments } from "@/lib/pqpa";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function PqpaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/pqpa", name: "Perfect Questions, Perfect Answers" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function RvChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/rv/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Rāja-vidyā: The King of Knowledge" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/rv"
|
||||
@@ -77,12 +81,15 @@ export default async function RvChapterPage({
|
||||
>
|
||||
← Rāja-vidyā: The King of Knowledge
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function RvChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getRvMeta, getRvIntroSegments } from "@/lib/rv";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function RvPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/rv", name: "Rāja-vidyā: The King of Knowledge" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -107,9 +108,12 @@ export default async function SbVersePage({
|
||||
>
|
||||
← Canto {cantoInfo.number}: {cantoInfo.title}
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {chapterInfo.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { ExtractedVerseCount } from "../../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
|
||||
@@ -15,6 +16,11 @@ export async function generateMetadata({
|
||||
}): Promise<Metadata> {
|
||||
const { canto } = await params;
|
||||
const title = BOOK_NAMES["sb"] || "Śrīmad-Bhāgavatam";
|
||||
// A non-numeric segment (preface, introduction) is front matter.
|
||||
if (isNaN(parseInt(canto))) {
|
||||
const segment = await getProseChapter("sb", 0, canto);
|
||||
if (segment) return buildOgMeta({ title, detail: segment.title });
|
||||
}
|
||||
return buildOgMeta({ title, detail: `Canto ${canto}` });
|
||||
}
|
||||
|
||||
@@ -38,6 +44,49 @@ export default async function CantoPage({
|
||||
}) {
|
||||
const { canto } = await params;
|
||||
const cantoNum = parseInt(canto);
|
||||
|
||||
// Front matter — a non-numeric param (preface, introduction) resolves to a
|
||||
// prose intro-segment.
|
||||
if (isNaN(cantoNum)) {
|
||||
const segment = await getProseChapter("sb", 0, canto);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/sb"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← Śrīmad-Bhāgavatam
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = await getBookMeta("sb");
|
||||
if (!meta) notFound();
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
@@ -26,7 +28,10 @@ type CantoInfo = {
|
||||
};
|
||||
|
||||
export default async function SbPage() {
|
||||
const meta = await getBookMeta("sb");
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("sb"),
|
||||
getProseChapters("sb", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) notFound();
|
||||
|
||||
const intro = meta as {
|
||||
@@ -39,6 +44,7 @@ export default async function SbPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sb", name: "Śrīmad-Bhāgavatam" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -51,7 +57,33 @@ export default async function SbPage() {
|
||||
</h1>
|
||||
<p className="mt-1 text-muted-foreground">{intro.author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/sb/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="divide-y divide-border">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -104,9 +105,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Śrīmad-Bhāgavatam (1962)
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<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">
|
||||
|
||||
@@ -3,7 +3,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
|
||||
@@ -32,6 +34,7 @@ export default async function Sb1962Page() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sb1962", name: "Śrīmad-Bhāgavatam (1962 Edition)" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -76,6 +79,7 @@ export default async function SsrChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ssr/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Science of Self-Realization" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ssr"
|
||||
@@ -83,12 +87,15 @@ export default async function SsrChapterPage({
|
||||
>
|
||||
← The Science of Self-Realization
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Chapter {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -106,7 +113,7 @@ export default async function SsrChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getSsrMeta, getSsrIntroSegments } from "@/lib/ssr";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function SsrPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ssr", name: "The Science of Self-Realization" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -13,12 +13,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -77,6 +80,7 @@ export default async function SvaChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/sva/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Songs of the Vaiṣṇava Ācāryas" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/sva"
|
||||
@@ -84,12 +88,15 @@ export default async function SvaChapterPage({
|
||||
>
|
||||
← Songs of the Vaiṣṇava Ācāryas
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Part {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -107,7 +114,7 @@ export default async function SvaChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getSvaMeta, getSvaIntroSegments } from "@/lib/sva";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function SvaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sva", name: "Songs of the Vaiṣṇava Ācāryas" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TlcChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tlc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Caitanya" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tlc"
|
||||
@@ -77,12 +81,15 @@ export default async function TlcChapterPage({
|
||||
>
|
||||
← Teachings of Lord Caitanya
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TlcChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTlcMeta, getTlcIntroSegments } from "@/lib/tlc";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -25,6 +27,7 @@ export default async function TlcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tlc", name: "Teachings of Lord Caitanya" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TlkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tlk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tlk"
|
||||
@@ -77,12 +81,15 @@ export default async function TlkChapterPage({
|
||||
>
|
||||
← Teachings of Lord Kapila
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TlkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTlkMeta, getTlkIntroSegments } from "@/lib/tlk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function TlkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tlk", name: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TqkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tqk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Queen Kuntī" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tqk"
|
||||
@@ -77,12 +81,15 @@ export default async function TqkChapterPage({
|
||||
>
|
||||
← Teachings of Queen Kuntī
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TqkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTqkMeta, getTqkIntroSegments } from "@/lib/tqk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function TqkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tqk", name: "Teachings of Queen Kuntī" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TtpChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ttp/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ttp"
|
||||
@@ -77,12 +81,15 @@ export default async function TtpChapterPage({
|
||||
>
|
||||
← Transcendental Teachings of Prahlāda Mahārāja
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TtpChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTtpMeta } from "@/lib/ttp";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function TtpPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ttp", name: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -62,6 +65,7 @@ export default async function VaChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/va/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Vedānta-sūtra Transcript" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/va"
|
||||
@@ -69,9 +73,12 @@ export default async function VaChapterPage({
|
||||
>
|
||||
← Vaiśiṣṭyāṣṭakam
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -85,7 +92,7 @@ export default async function VaChapterPage({
|
||||
currentSlug={slug}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getVaMeta } from "@/lib/va";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function VaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/va", name: "Vedānta-sūtra Transcript" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Client body for the public collection page (/c/[id]). Split out from
|
||||
* page.tsx so the page itself can stay a server component that exports
|
||||
* `generateMetadata` (rich OG card on link shares). Anyone can view a
|
||||
* public collection; private ones return 403 from the BFF, which we treat
|
||||
* as "not found" to avoid leaking existence.
|
||||
*/
|
||||
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Globe, Lock, FolderPlus } from "lucide-react";
|
||||
import {
|
||||
collections,
|
||||
SocialError,
|
||||
type Collection,
|
||||
type Post,
|
||||
} from "@/lib/social";
|
||||
import { PostViews } from "@/components/social/post/post-views";
|
||||
|
||||
export function CollectionContent(props: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = use(props.params);
|
||||
const [c, setC] = useState<Collection | null>(null);
|
||||
const [posts, setPosts] = useState<Post[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [missing, setMissing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
collections
|
||||
.get(id)
|
||||
.then(({ collection, posts }) => {
|
||||
if (cancelled) return;
|
||||
setC(collection);
|
||||
setPosts(posts ?? []);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
err instanceof SocialError &&
|
||||
(err.status === 404 || err.status === 403)
|
||||
) {
|
||||
setMissing(true);
|
||||
}
|
||||
})
|
||||
.finally(() => !cancelled && setLoading(false));
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
if (missing) notFound();
|
||||
if (loading || !c)
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 py-12">
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
</main>
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6 py-10 space-y-8">
|
||||
<header className="rounded-2xl bg-card ring-1 ring-foreground/10 p-6 sm:p-8">
|
||||
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||||
{c.is_public ? (
|
||||
<Globe className="size-3.5" />
|
||||
) : (
|
||||
<Lock className="size-3.5" />
|
||||
)}
|
||||
{c.is_public ? "Public collection" : "Private collection"}
|
||||
</div>
|
||||
<h1 className="mt-3 font-[family-name:var(--font-title)] font-semibold text-3xl sm:text-4xl text-foreground leading-tight">
|
||||
{c.title}
|
||||
</h1>
|
||||
{c.description ? (
|
||||
<p className="mt-3 text-base text-muted-foreground leading-relaxed">
|
||||
{c.description}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-4 text-xs text-muted-foreground tabular-nums">
|
||||
{c.item_count} post{c.item_count === 1 ? "" : "s"}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="space-y-3">
|
||||
{posts.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-border p-8 text-center">
|
||||
<FolderPlus className="size-6 text-muted-foreground mx-auto" />
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
This collection has no items yet.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PostViews posts={posts} />
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+42
-85
@@ -1,94 +1,51 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Public collection at /c/[id]. Anyone can view if `is_public=true`.
|
||||
* Private collections return 403 from the BFF, which we treat as "not
|
||||
* found" to avoid leaking existence.
|
||||
* /c/[id] — public collection.
|
||||
*
|
||||
* Server shell: exports `generateMetadata` so a shared collection link
|
||||
* unfurls with a rich OG card (pointing at /api/og/collection/<id>), then
|
||||
* mounts the client body. The server fetch is unauthenticated, so only
|
||||
* public collections produce a card — private ones fall back to the default
|
||||
* (and never leak).
|
||||
*/
|
||||
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Globe, Lock, FolderPlus } from "lucide-react";
|
||||
import {
|
||||
collections,
|
||||
SocialError,
|
||||
type Collection,
|
||||
type Post,
|
||||
} from "@/lib/social";
|
||||
import { PostViews } from "@/components/social/post-views";
|
||||
import type { Metadata } from "next";
|
||||
import { bff } from "@/lib/bff";
|
||||
import { ogDefault, ogForCollection } from "@/lib/og";
|
||||
import { CollectionContent } from "./collection-content";
|
||||
|
||||
export default function PublicCollectionPage(props: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = use(props.params);
|
||||
const [c, setC] = useState<Collection | null>(null);
|
||||
const [posts, setPosts] = useState<Post[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [missing, setMissing] = useState(false);
|
||||
type Props = { params: Promise<{ id: string }> };
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
collections
|
||||
.get(id)
|
||||
.then(({ collection, posts }) => {
|
||||
if (cancelled) return;
|
||||
setC(collection);
|
||||
setPosts(posts ?? []);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (
|
||||
err instanceof SocialError &&
|
||||
(err.status === 404 || err.status === 403)
|
||||
) {
|
||||
setMissing(true);
|
||||
}
|
||||
})
|
||||
.finally(() => !cancelled && setLoading(false));
|
||||
return () => {
|
||||
cancelled = true;
|
||||
type CollectionEnvelope = {
|
||||
collection: {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
is_public: boolean;
|
||||
item_count: number;
|
||||
};
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
if (missing) notFound();
|
||||
if (loading || !c)
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 py-12">
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
</main>
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6 py-10 space-y-8">
|
||||
<header className="rounded-2xl bg-card ring-1 ring-foreground/10 p-6 sm:p-8">
|
||||
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
||||
{c.is_public ? <Globe className="size-3.5" /> : <Lock className="size-3.5" />}
|
||||
{c.is_public ? "Public collection" : "Private collection"}
|
||||
</div>
|
||||
<h1 className="mt-3 font-[family-name:var(--font-title)] font-semibold text-3xl sm:text-4xl text-foreground leading-tight">
|
||||
{c.title}
|
||||
</h1>
|
||||
{c.description ? (
|
||||
<p className="mt-3 text-base text-muted-foreground leading-relaxed">
|
||||
{c.description}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-4 text-xs text-muted-foreground tabular-nums">
|
||||
{c.item_count} post{c.item_count === 1 ? "" : "s"}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="space-y-3">
|
||||
{posts.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-border p-8 text-center">
|
||||
<FolderPlus className="size-6 text-muted-foreground mx-auto" />
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
This collection has no items yet.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<PostViews posts={posts} />
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
try {
|
||||
const data = await bff<CollectionEnvelope>(
|
||||
`/collections/${encodeURIComponent(id)}`,
|
||||
{ revalidate: 60 },
|
||||
);
|
||||
const c = data?.collection;
|
||||
if (!c || !c.is_public) return ogDefault();
|
||||
return ogForCollection({
|
||||
id: c.id,
|
||||
title: c.title,
|
||||
description: c.description,
|
||||
itemCount: c.item_count,
|
||||
});
|
||||
} catch {
|
||||
// Private (403) / unreachable → fall back to the site card.
|
||||
return ogDefault();
|
||||
}
|
||||
}
|
||||
|
||||
export default function PublicCollectionPage({ params }: Props) {
|
||||
return <CollectionContent params={params} />;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { ogForDisciples } from "@/lib/og";
|
||||
import {
|
||||
getTotalDiscipleCount,
|
||||
|
||||
@@ -4,12 +4,14 @@ import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ogForEssay, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { bff, bffList } from "@/lib/bff";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -119,6 +121,7 @@ export default async function EssayPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/essays/${slug}`, headline: essay.title, description: essay.paragraphs?.[0], isPartOf: "Essays & Talks" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/essays"
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { bffList } from "@/lib/bff";
|
||||
import { EssaysGrid } from "./essays-grid";
|
||||
|
||||
@@ -5,6 +5,8 @@ import { notFound } from "next/navigation";
|
||||
import { getAllGlossaryEntries } from "@/lib/api";
|
||||
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
|
||||
import { GlossaryClient } from "../glossary-client";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Diacritic → base letter mapping
|
||||
@@ -145,6 +147,13 @@ export default async function GlossaryLetterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `/glossary/${rawLetter}`,
|
||||
headline: `Glossary — ${activeLetter}`,
|
||||
isPartOf: "Glossary of Sanskrit & Vedic Terms",
|
||||
})}
|
||||
/>
|
||||
<GlossaryClient
|
||||
entries={clientEntries}
|
||||
activeLetter={activeLetter}
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
type JournalSidebarItem,
|
||||
} from "@/lib/journals";
|
||||
import { ogForJournal, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -74,6 +76,7 @@ export default async function JournalChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/journals/${slug}/${chapterSlug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: meta.title })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={`/journals/${slug}`}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type JournalChapterEntry,
|
||||
} from "@/lib/journals";
|
||||
import { ogForJournal } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export async function generateMetadata({
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import {
|
||||
Card,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Suspense } from "react";
|
||||
import { SiteHeader } from "@/components/site-header";
|
||||
import { SiteSidebar } from "@/components/site-sidebar";
|
||||
import { SiteBottomNav } from "@/components/site-bottom-nav";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { SiteHeader } from "@/components/layout/site-header";
|
||||
import { SiteSidebar } from "@/components/layout/site-sidebar";
|
||||
import { SiteBottomNav } from "@/components/layout/site-bottom-nav";
|
||||
import { SiteFooter } from "@/components/layout/site-footer";
|
||||
import { VerifyEmailBanner } from "@/components/auth/verify-email-banner";
|
||||
import { TextSelectionToolbar } from "@/components/text-selection-toolbar";
|
||||
import { TextHighlighter } from "@/components/text-highlighter";
|
||||
|
||||
@@ -5,11 +5,13 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLetter, getAdjacentLetters } from "@/lib/letters";
|
||||
import { ogForLetter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -54,6 +56,15 @@ export default async function LetterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `/letters/${slug}`,
|
||||
headline: `Letter to ${letter.recipientDisplay}`,
|
||||
description: letter.paragraphs[0],
|
||||
datePublished: letter.year ? String(letter.year) : undefined,
|
||||
isPartOf: "Letters of Śrīla Prabhupāda",
|
||||
})}
|
||||
/>
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={backHref}
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import {
|
||||
getLetterYears,
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
type MagazineSidebarItem,
|
||||
} from "@/lib/magazines";
|
||||
import { ogForMagazine, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -71,6 +74,7 @@ export default async function MagazineIssuePage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/magazines/${slug}/${issueSlug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: meta.title })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={`/magazines/${slug}`}
|
||||
@@ -78,9 +82,12 @@ export default async function MagazineIssuePage({
|
||||
>
|
||||
← {meta.title}
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -101,7 +108,7 @@ export default async function MagazineIssuePage({
|
||||
showMobileNav
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type MagazineChapterEntry,
|
||||
} from "@/lib/magazines";
|
||||
import { ogForMagazine } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export async function generateMetadata({
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Bell } from "lucide-react";
|
||||
import { useAuth } from "@/components/auth/auth-provider";
|
||||
import { useAuthGate } from "@/components/auth/auth-gate";
|
||||
import { useNotifications } from "@/components/notifications/notifications-provider";
|
||||
import { notifications, type AppNotification } from "@/lib/social";
|
||||
import { UserAvatar } from "@/components/ui/user-avatar";
|
||||
import { timeAgo } from "@/lib/time";
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const { user, loading } = useAuth();
|
||||
@@ -114,7 +115,11 @@ function NotificationRow({
|
||||
n: AppNotification;
|
||||
selfHandle: string;
|
||||
}) {
|
||||
const display = n.actor_name || (n.actor_handle ? `@${n.actor_handle}` : "Someone");
|
||||
// admin_message rows have no actor — they're a direct message from the team.
|
||||
const isAdmin = n.kind === "admin_message";
|
||||
const display = isAdmin
|
||||
? "Prabhupāda Says"
|
||||
: n.actor_name || (n.actor_handle ? `@${n.actor_handle}` : "Someone");
|
||||
const message = messageFor(n);
|
||||
const unread = !n.read_at;
|
||||
|
||||
@@ -122,11 +127,18 @@ function NotificationRow({
|
||||
<div
|
||||
className={cnRow(unread)}
|
||||
>
|
||||
<Avatar src={n.actor_avatar_url ?? null} name={display} />
|
||||
<UserAvatar src={isAdmin ? null : n.actor_avatar_url} name={display} />
|
||||
<div className="min-w-0 flex-1">
|
||||
{isAdmin ? (
|
||||
<p className="text-sm text-foreground leading-snug whitespace-pre-line">
|
||||
<span className="font-semibold">{display}</span>
|
||||
{n.body ? <span className="block mt-0.5">{n.body}</span> : null}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-foreground leading-snug">
|
||||
<span className="font-semibold">{display}</span> {message}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{timeAgo(n.created_at)}
|
||||
</p>
|
||||
@@ -137,11 +149,19 @@ function NotificationRow({
|
||||
</div>
|
||||
);
|
||||
|
||||
// A like links to your own post; everything else links to the actor's
|
||||
// profile (when we know their handle).
|
||||
// A like links to your own post; a mention links to the actor's post you
|
||||
// were tagged in; everything else links to the actor's profile.
|
||||
const href =
|
||||
n.kind === "post_like" && n.post_id
|
||||
? `/u/${selfHandle}/posts#post-${n.post_id}`
|
||||
: n.kind === "post_mention" && n.post_id && n.actor_handle
|
||||
? `/u/${n.actor_handle}/posts#post-${n.post_id}`
|
||||
: (n.kind === "post_comment" ||
|
||||
n.kind === "comment_reply" ||
|
||||
n.kind === "comment_mention") &&
|
||||
n.post_id &&
|
||||
n.post_author_handle
|
||||
? `/u/${n.post_author_handle}/posts#post-${n.post_id}`
|
||||
: n.actor_handle
|
||||
? `/u/${n.actor_handle}`
|
||||
: null;
|
||||
@@ -177,39 +197,21 @@ function messageFor(n: AppNotification): string {
|
||||
if (others <= 0) return "liked your post.";
|
||||
return `and ${others} ${others === 1 ? "other" : "others"} liked your post.`;
|
||||
}
|
||||
case "post_mention":
|
||||
return "mentioned you in a post.";
|
||||
case "post_comment":
|
||||
return "commented on your post.";
|
||||
case "comment_reply":
|
||||
return "replied to your comment.";
|
||||
case "comment_mention":
|
||||
return "mentioned you in a comment.";
|
||||
case "admin_message":
|
||||
return n.body ?? "sent you a message.";
|
||||
default:
|
||||
return "sent you a notification.";
|
||||
}
|
||||
}
|
||||
|
||||
function Avatar({ src, name }: { src: string | null; name: string }) {
|
||||
if (src) {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
alt={name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="size-10 rounded-full object-cover ring-1 ring-foreground/10 shrink-0"
|
||||
unoptimized
|
||||
/>
|
||||
);
|
||||
}
|
||||
const initials =
|
||||
name
|
||||
.replace(/^@/, "")
|
||||
.split(/\s+/)
|
||||
.map((p) => p[0])
|
||||
.slice(0, 2)
|
||||
.join("")
|
||||
.toUpperCase() || "?";
|
||||
return (
|
||||
<div className="size-10 shrink-0 rounded-full bg-primary/15 text-primary flex items-center justify-center text-sm font-semibold ring-1 ring-foreground/10">
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Empty() {
|
||||
return (
|
||||
<div className="flex flex-col items-center text-center gap-3 py-16 text-muted-foreground">
|
||||
@@ -219,18 +221,3 @@ function Empty() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Compact relative time ("just now", "5m", "3h", "2d"), falling back to
|
||||
* a date for anything older than a week. */
|
||||
function timeAgo(iso: string): string {
|
||||
const then = new Date(iso).getTime();
|
||||
if (Number.isNaN(then)) return "";
|
||||
const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
|
||||
if (secs < 45) return "just now";
|
||||
const mins = Math.floor(secs / 60);
|
||||
if (mins < 60) return `${mins}m ago`;
|
||||
const hours = Math.floor(mins / 60);
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 7) return `${days}d ago`;
|
||||
return new Date(iso).toLocaleDateString();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user