Author SHA1 Message Date
gosangam f5abe33c09 feat: add book transcripts categorisation 2026-06-17 12:40:40 +05:30
gosangam f96e7dd2f1 feat: admin notifications 2026-06-16 14:48:29 +05:30
gosangam 0cf6df5942 fix: ui fix 2026-06-16 01:30:41 +05:30
gosangam 5c72416ae8 fix: hidden post 2026-06-16 00:05:07 +05:30
gosangam 197e13c0ef feat: add edit profile page 2026-06-15 23:54:34 +05:30
gosangam 0608e2b6f6 fix: ui fix 2026-06-15 17:15:38 +05:30
gosangam cb376f8497 fix: ui fix 2026-06-15 15:24:49 +05:30
gosangam eac56b7974 fix(seo): fix sitemap URL 2026-06-15 14:04:00 +05:30
gosangam be4c2f17fe feat(seo): add sitemap generator 2026-06-15 13:52:12 +05:30
gosangam 6cc9285bad fix: ui fixes 2026-06-15 12:17:39 +05:30
gosangam ab35767374 fix: ui fixes 2026-06-15 10:13:00 +05:30
gosangam c551d2f7a8 fix: ui fixes 2026-06-15 10:04:11 +05:30
gosangam a4fd63695a fix: ui fixes 2026-06-15 09:53:01 +05:30
gosangam 91621693a6 feat: hide post 2026-06-14 20:11:04 +05:30
gosangam 79f0bd9223 feat: hide post 2026-06-14 19:52:16 +05:30
gosangam 7585e6cb31 fix: ui fixes 2026-06-14 18:04:11 +05:30
gosangam c8de98c8de fix: ui fixes 2026-06-14 16:11:56 +05:30
gosangam 72a092b3c9 fix: ui fixes 2026-06-14 16:06:00 +05:30
gosangam 752ad20fed fix: ui fixes 2026-06-14 15:56:58 +05:30
gosangam 0280a81777 feat: add sb front matter 2026-06-14 15:32:33 +05:30
gosangam 69e5f2c45c feat: add sb front matter 2026-06-14 14:49:25 +05:30
gosangam 45c7cf047e feat: add cc front matter 2026-06-14 13:57:38 +05:30
gosangam a4baf6d215 feat: add bg front matter 2026-06-14 13:28:29 +05:30
gosangam 455ed0f7cb fix: copy behaviour 2026-06-14 12:51:15 +05:30
gosangam 7c3cf656b5 feat: add search in page for books, magazines and other docs 2026-06-14 02:59:42 +05:30
gosangam 23d06a3db5 fix: og image fix for collections 2026-06-14 01:49:44 +05:30
gosangam 3accc753d0 feat: share collection URL 2026-06-14 01:29:08 +05:30
gosangam b04ed1d3aa fix: fix feed component size 2026-06-14 00:43:33 +05:30
gosangam 9808dd3033 fix: fix feed component 2026-06-14 00:35:45 +05:30
gosangam b248f962a1 fix: fix feed component 2026-06-14 00:30:37 +05:30
gosangam 61581b67a7 feat: add feed 2026-06-14 00:18:31 +05:30
gosangam eb0c981d3c feat: add inline mentions in comments 2026-06-13 13:10:34 +05:30
gosangam 37305201bd fix: delete comment modal 2026-06-13 03:15:51 +05:30
gosangam 4471ef10aa fix: delete comment modal 2026-06-13 03:04:46 +05:30
gosangam 397a09e814 fix: ui fixes in emoji picker 2026-06-13 02:53:25 +05:30
gosangam 9e68184750 fix: ui fixes 2026-06-13 02:42:07 +05:30
gosangam 439664a97d chore: code refactor 2026-06-13 02:24:42 +05:30
gosangam cc5633c37a feat: add comments 2026-06-13 01:30:18 +05:30
gosangam 6a0559e608 fix: hide blocked user content 2026-06-13 00:47:11 +05:30
gosangam 25a729959f chore: theme update 2026-06-11 16:14:53 +05:30
gosangam af5ab9cf6b fix: copy clipboard fix 2026-06-11 11:47:31 +05:30
gosangam 884a984e50 fix: search in transcript page ui fixes 2026-06-11 00:39:39 +05:30
gosangam ead8ad02e8 fix: ui fixes 2026-06-11 00:32:04 +05:30
gosangam 4b159cb4d9 fix: drawer fix 2026-06-10 20:02:23 +05:30
gosangam 3903423d06 fix: mention input zoom behaviour 2026-06-10 19:21:32 +05:30
gosangam a46e29d998 fix: mention input zoom behaviour 2026-06-10 16:56:20 +05:30
gosangam 922d76e21f fix: mention user position in post compose drawer 2026-06-10 16:52:21 +05:30
gosangam 909ae67320 feat: mention 2026-06-10 16:29:44 +05:30
gosangam 482a845f37 feat: add email notif for post like 2026-06-09 16:57:37 +05:30
209 changed files with 7667 additions and 1458 deletions
+2
View File
@@ -1,3 +1,5 @@
NEXT_PUBLIC_SITE_URL=https://prabhupadasays.org
# BFF API Server (server-side fetches) # BFF API Server (server-side fetches)
BFF_URL=http://localhost:8080 BFF_URL=http://localhost:8080
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { import {
BookOpen, BookOpen,
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function BbdChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/bbd" href="/books/bbd"
@@ -77,12 +81,15 @@ export default async function BbdChapterPage({
> >
&larr; Beyond Birth and Death &larr; Beyond Birth and Death
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function BbdChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBbdMeta, getBbdIntroSegments } from "@/lib/bbd"; import { getBbdMeta, getBbdIntroSegments } from "@/lib/bbd";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function BbdPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bbd", name: "Beyond Birth and Death" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -1,10 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api"; import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -101,9 +102,12 @@ export default async function VersePage({
> >
&larr; Bhagavad-gītā &larr; Bhagavad-gītā
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+57 -1
View File
@@ -3,9 +3,13 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { notFound } 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 { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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({ export async function generateMetadata({
params, params,
@@ -13,6 +17,14 @@ export async function generateMetadata({
params: Promise<{ chapter: string }>; params: Promise<{ chapter: string }>;
}): Promise<Metadata> { }): Promise<Metadata> {
const { chapter } = await params; 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}` }); return buildOgMeta({ title: BOOK_NAMES["bg"], detail: `Chapter ${chapter}` });
} }
@@ -22,6 +34,50 @@ export default async function ChapterPage({
params: Promise<{ chapter: string }>; params: Promise<{ chapter: string }>;
}) { }) {
const { chapter } = await params; 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"
>
&larr; {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)); const verseIndex = await getChapterVerseIndex("bg", null, parseInt(chapter));
if (verseIndex.length === 0) { if (verseIndex.length === 0) {
notFound(); notFound();
+68 -27
View File
@@ -1,9 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
@@ -18,7 +20,10 @@ type ChapterInfo = {
}; };
export default async function BgPage() { 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(); if (!meta) notFound();
const intro = meta as unknown as { const intro = meta as unknown as {
@@ -31,6 +36,7 @@ export default async function BgPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bg", name: "Bhagavad-gītā As It Is" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -43,31 +49,66 @@ export default async function BgPage() {
</h1> </h1>
<p className="mt-1 text-muted-foreground">{intro.author}</p> <p className="mt-1 text-muted-foreground">{intro.author}</p>
</PageHeader> </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">
<Card className="divide-y divide-border py-0"> {/* Front Matter */}
<CardContent className="p-0"> {introSegments.length > 0 && (
<ul className="divide-y divide-border"> <div>
{intro.chapters.map((ch) => ( <h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
<li key={ch.number}> Front Matter
<Link </h2>
href={`/books/bg/${ch.number}`} <Card className="divide-y divide-border py-0">
className="flex flex-col sm:flex-row sm:items-center sm:justify-between px-5 py-4 gap-1 sm:gap-0 hover:bg-muted transition-colors" <CardContent className="p-0">
> <ul className="divide-y divide-border">
<div> {introSegments.map((seg) => (
<span className="text-sm text-primary font-medium"> <li key={seg.slug}>
Chapter {ch.number} <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">
{intro.chapters.map((ch) => (
<li key={ch.number}>
<Link
href={`/books/bg/${ch.number}`}
className="flex flex-col sm:flex-row sm:items-center sm:justify-between px-5 py-4 gap-1 sm:gap-0 hover:bg-muted transition-colors"
>
<div>
<span className="text-sm text-primary font-medium">
Chapter {ch.number}
</span>
<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} />
</span> </span>
<p className="font-medium text-foreground">{ch.title}</p> </Link>
</div> </li>
<span className="text-sm text-muted-foreground sm:shrink-0 sm:ml-4"> ))}
<ExtractedVerseCount count={ch.totalVerses} /> </ul>
</span> </CardContent>
</Link> </Card>
</li> </div>
))}
</ul>
</CardContent>
</Card>
</main> </main>
</div> </div>
); );
+16 -9
View File
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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"; export const dynamic = "force-dynamic";
@@ -62,6 +65,7 @@ export default async function BkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/bk" href="/books/bk"
@@ -69,12 +73,15 @@ export default async function BkChapterPage({
> >
&larr; Bhagavāner Kathā &larr; Bhagavāner Kathā
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -92,7 +99,7 @@ export default async function BkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBkMeta } from "@/lib/bk"; import { getBkMeta } from "@/lib/bk";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function BkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bk", name: "Bhagavāner Kathā" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { import {
getBookMeta, getBookMeta,
@@ -12,7 +13,7 @@ import {
type VerseRecord, type VerseRecord,
} from "@/lib/api"; } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -187,9 +188,12 @@ export default async function VersePage({
> >
&larr; Śrī Brahma-saṁhitā &larr; Śrī Brahma-saṁhitā
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+9 -5
View File
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card"; 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 Link from "next/link";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@@ -59,13 +60,16 @@ export default async function BsChapterPage({
> >
&larr; Śrī Brahma-saṁhitā &larr; Śrī Brahma-saṁhitā
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{segment.title} <h1 className="text-2xl font-bold text-foreground">
</h1> {segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8"> <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"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => ( {segment.paragraphs.map((paragraph: string, i: number) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs"; import fs from "fs";
@@ -80,6 +82,7 @@ export default async function BsPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/bs", name: "Śrī Brahma-saṁhitā" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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"; export const dynamic = "force-dynamic";
@@ -62,6 +65,7 @@ export default async function CbhcChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/cbhc" href="/books/cbhc"
@@ -69,12 +73,15 @@ export default async function CbhcChapterPage({
> >
&larr; Śrī Caitanya-bhāgavata Commentary &larr; Śrī Caitanya-bhāgavata Commentary
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -92,7 +99,7 @@ export default async function CbhcChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getCbhcMeta } from "@/lib/cbhc"; import { getCbhcMeta } from "@/lib/cbhc";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function CbhcPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/cbhc", name: "Śrī Caitanya-bhāgavata Commentary" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api"; import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
type ChapterInfo = { type ChapterInfo = {
@@ -111,9 +112,12 @@ export default async function CcVersePage({
> >
&larr; {lilaInfo.name} &larr; {lilaInfo.name}
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {chapterInfo.title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {chapterInfo.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+202 -7
View File
@@ -1,9 +1,28 @@
export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { redirect } from "next/navigation"; import Link from "next/link";
import { notFound } from "next/navigation"; import { redirect, notFound } from "next/navigation";
import { getChapterVerseIndex } from "@/lib/api"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { getBookMeta, getProseChapter, getChapterVerseIndex } from "@/lib/api";
import { CC_LILAS } from "@/lib/constants"; import { CC_LILAS } from "@/lib/constants";
import { buildOgMeta } from "@/lib/og"; 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({ export async function generateMetadata({
params, 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({ export default async function CcChapterPage({
params, params,
}: { }: {
params: Promise<{ lila: string; chapter: string }>; params: Promise<{ lila: string; chapter: string }>;
}) { }) {
const { lila, chapter } = await params; const { lila, chapter } = await params;
const verseIndex = await getChapterVerseIndex("cc", lila, parseInt(chapter)); const chapterNum = parseInt(chapter);
if (verseIndex.length === 0) {
notFound(); 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"
>
&larr; {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>
);
} }
+53 -8
View File
@@ -1,10 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { FindInPage } from "@/components/layout/find-in-page";
import { ExtractedVerseCount } from "../../extracted-verse-count"; import { ExtractedVerseCount } from "../../extracted-verse-count";
import { getBookMeta } from "@/lib/api"; import { getBookMeta, getProseChapter } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES, CC_LILAS } from "@/lib/constants"; import { BOOK_NAMES, CC_LILAS } from "@/lib/constants";
@@ -14,12 +15,15 @@ export async function generateMetadata({
params: Promise<{ lila: string }>; params: Promise<{ lila: string }>;
}): Promise<Metadata> { }): Promise<Metadata> {
const { lila } = await params; const { lila } = await params;
const lilaName = CC_LILAS[lila] || lila;
const bookTitle = BOOK_NAMES["cc"] || "Śrī Caitanya-caritāmṛta"; const bookTitle = BOOK_NAMES["cc"] || "Śrī Caitanya-caritāmṛta";
return buildOgMeta({ // A non-līlā segment (front-flap, preface, introduction) is front matter.
title: bookTitle, if (!CC_LILAS[lila]) {
detail: lilaName, 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 = { type ChapterInfo = {
@@ -47,7 +51,48 @@ export default async function LilaPage({
const lilas = meta.lilas as LilaInfo[]; const lilas = meta.lilas as LilaInfo[];
const lilaInfo = lilas.find((l) => l.key === lila); 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"
>
&larr; Ś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( const totalVerses = lilaInfo.chapters.reduce(
(sum, ch) => sum + ch.totalVerses, (sum, ch) => sum + ch.totalVerses,
+36 -4
View File
@@ -1,8 +1,10 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { getBookMeta } from "@/lib/api"; import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
@@ -26,7 +28,10 @@ export function generateMetadata(): Metadata {
} }
export default async function CcPage() { 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) { if (!meta) {
return ( return (
<div className="p-8 text-center text-muted-foreground"> <div className="p-8 text-center text-muted-foreground">
@@ -41,6 +46,7 @@ export default async function CcPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/cc", name: "Śrī Caitanya-caritāmṛta" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -51,7 +57,33 @@ export default async function CcPage() {
<h1 className="mt-3 text-3xl font-bold text-foreground">{title}</h1> <h1 className="mt-3 text-3xl font-bold text-foreground">{title}</h1>
<p className="mt-1 text-muted-foreground">{author}</p> <p className="mt-1 text-muted-foreground">{author}</p>
</PageHeader> </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"> <Card className="divide-y divide-border">
<CardContent className="p-0"> <CardContent className="p-0">
<ul className="divide-y divide-border"> <ul className="divide-y divide-border">
+20 -2
View File
@@ -6,7 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react"; 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 // Types
@@ -389,6 +392,19 @@ export function ChapterViewer({
return ( 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 ---- */} {/* ---- Sidebar: Verse Index ---- */}
{/* Sticky sidebar. The max-h leaves 76px at the viewport bottom for {/* 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 the site footer so the sidebar doesn't slide behind it when the
@@ -440,7 +456,8 @@ export function ChapterViewer({
mobileNavBookTitle={mobileNavBookTitle} mobileNavBookTitle={mobileNavBookTitle}
/> />
{/* Verse content */} {/* Verse content — id targeted by each page header's FindInPage. */}
<div id="book-content">
{loading && !currentVerse ? ( {loading && !currentVerse ? (
<div className="flex items-center justify-center py-24 text-muted-foreground gap-2"> <div className="flex items-center justify-center py-24 text-muted-foreground gap-2">
<Loader2 className="size-5 animate-spin" /> <Loader2 className="size-5 animate-spin" />
@@ -453,6 +470,7 @@ export function ChapterViewer({
Verse not found. Verse not found.
</div> </div>
)} )}
</div>
{/* Bottom navigation */} {/* Bottom navigation */}
<VerseNav <VerseNav
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -76,6 +79,7 @@ export default async function DsChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ds" href="/books/ds"
@@ -83,12 +87,15 @@ export default async function DsChapterPage({
> >
&larr; Dialectic Spiritualism &larr; Dialectic Spiritualism
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{parentChapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {parentChapterNum}: </span> {parentChapterNum != null && (
)} <span className="text-primary">Chapter {parentChapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -106,7 +113,7 @@ export default async function DsChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getDsMeta, getDsIntroSegments } from "@/lib/ds"; import { getDsMeta, getDsIntroSegments } from "@/lib/ds";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function DsPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ds", name: "Dialectic Spiritualism" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function EjChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ej" href="/books/ej"
@@ -77,12 +81,15 @@ export default async function EjChapterPage({
> >
&larr; Easy Journey to Other Planets &larr; Easy Journey to Other Planets
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function EjChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getEjMeta, getEjIntroSegments } from "@/lib/ej"; import { getEjMeta, getEjIntroSegments } from "@/lib/ej";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function EjPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ej", name: "Easy Journey to Other Planets" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function EkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ek" href="/books/ek"
@@ -77,12 +81,15 @@ export default async function EkChapterPage({
> >
&larr; Elevation to Kṛṣṇa Consciousness &larr; Elevation to Kṛṣṇa Consciousness
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function EkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getEkMeta, getEkIntroSegments } from "@/lib/ek"; import { getEkMeta, getEkIntroSegments } from "@/lib/ek";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function EkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ek", name: "Elevation to Kṛṣṇa Consciousness" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -10,12 +10,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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"; export const dynamic = "force-dynamic";
@@ -66,6 +69,7 @@ export default async function GgChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/gg" href="/books/gg"
@@ -73,12 +77,15 @@ export default async function GgChapterPage({
> >
&larr; Gītār Gān &larr; Gītār Gān
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function GgChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getGgMeta, getGgIntroSegments } from "@/lib/gg"; import { getGgMeta, getGgIntroSegments } from "@/lib/gg";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function GgPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/gg", name: "Gītār Gān" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function HkhChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/hkh" href="/books/hkh"
@@ -77,12 +81,15 @@ export default async function HkhChapterPage({
> >
&larr; The Kṛṣṇa Consciousness Handbook &larr; The Kṛṣṇa Consciousness Handbook
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function HkhChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getHkhMeta, getHkhIntroSegments } from "@/lib/hkh"; import { getHkhMeta, getHkhIntroSegments } from "@/lib/hkh";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function HkhPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/hkh", name: "The Kṛṣṇa Consciousness Handbook" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+20 -13
View File
@@ -11,12 +11,15 @@ import {
type IsoSidebarItem, type IsoSidebarItem,
} from "@/lib/iso"; } from "@/lib/iso";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Type guards
@@ -102,6 +105,7 @@ export default async function IsoMantraPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/iso" href="/books/iso"
@@ -109,15 +113,18 @@ export default async function IsoMantraPage({
> >
&larr; Śrī Īśopaniṣad &larr; Śrī Īśopaniṣad
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{verse && ( <h1 className="text-2xl font-bold text-foreground">
<> {verse && (
<span className="text-primary">{verse.reference}: </span> <>
{verse.bookTitle} <span className="text-primary">{verse.reference}: </span>
</> {verse.bookTitle}
)} </>
{intro && intro.title} )}
</h1> {intro && intro.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -157,7 +164,7 @@ export default async function IsoMantraPage({
function VerseContent({ verse }: { verse: IsoVerse }) { function VerseContent({ verse }: { verse: IsoVerse }) {
return ( return (
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8 space-y-8"> <CardContent className="py-8 px-6 sm:px-8 space-y-8">
{/* Devanagari */} {/* Devanagari */}
<section> <section>
@@ -233,7 +240,7 @@ function VerseContent({ verse }: { verse: IsoVerse }) {
function IntroContent({ intro }: { intro: IsoIntroSegment }) { function IntroContent({ intro }: { intro: IsoIntroSegment }) {
return ( return (
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{intro.paragraphs.map((paragraph, i) => ( {intro.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getIsoMeta, getIsoIntroSegments } from "@/lib/iso"; import { getIsoMeta, getIsoIntroSegments } from "@/lib/iso";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -28,6 +30,7 @@ export default async function IsoPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/iso", name: "Śrī Īśopaniṣad" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -6,12 +6,15 @@ import { notFound } from "next/navigation";
import { getProseChapter, getProseChapters } from "@/lib/api"; import { getProseChapter, getProseChapters } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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({ export async function generateMetadata({
params, params,
@@ -65,6 +68,7 @@ export default async function KbChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href={`/books/kb/${vol}`} href={`/books/kb/${vol}`}
@@ -72,12 +76,15 @@ export default async function KbChapterPage({
> >
&larr; Volume {vol} &larr; Volume {vol}
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterLabel && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">{chapterLabel}: </span> {chapterLabel && (
)} <span className="text-primary">{chapterLabel}: </span>
{chapter.title} )}
</h1> {chapter.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function KbChapterPage({
/> />
{/* Chapter content */} {/* Chapter content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{chapter.paragraphs.map((paragraph, i) => ( {chapter.paragraphs.map((paragraph, i) => (
+1 -1
View File
@@ -4,7 +4,7 @@ import { notFound } from "next/navigation";
import { getBookMeta, getProseChapters } from "@/lib/api"; import { getBookMeta, getProseChapters } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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"; import { Card, CardContent } from "@/components/ui/card";
export async function generateMetadata({ export async function generateMetadata({
+4 -1
View File
@@ -3,7 +3,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -27,6 +29,7 @@ export default async function KbPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/kb", name: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function LclChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/lcl" href="/books/lcl"
@@ -77,12 +81,15 @@ export default async function LclChapterPage({
> >
&larr; Life Comes from Life &larr; Life Comes from Life
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function LclChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getLclMeta, getLclIntroSegments } from "@/lib/lcl"; import { getLclMeta, getLclIntroSegments } from "@/lib/lcl";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function LclPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/lcl", name: "Life Comes from Life" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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({ export async function generateMetadata({
params, params,
@@ -66,6 +69,7 @@ export default async function LobChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/lob" href="/books/lob"
@@ -73,12 +77,15 @@ export default async function LobChapterPage({
> >
&larr; Light of the Bh&#257;gavata &larr; Light of the Bh&#257;gavata
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Text {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Text {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function LobChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => {content.paragraphs.map((paragraph, i) =>
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getLobMeta, getLobIntroSegments } from "@/lib/lob"; import { getLobMeta, getLobIntroSegments } from "@/lib/lob";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function LobPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/lob", name: "Light of the Bhāgavata" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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({ export async function generateMetadata({
params, params,
@@ -66,6 +69,7 @@ export default async function MgChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/mg" href="/books/mg"
@@ -73,12 +77,15 @@ export default async function MgChapterPage({
> >
&larr; Kṛṣṇa Consciousness: The Matchless Gift &larr; Kṛṣṇa Consciousness: The Matchless Gift
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -96,7 +103,7 @@ export default async function MgChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getMgMeta, getMgIntroSegments } from "@/lib/mg"; import { getMgMeta, getMgIntroSegments } from "@/lib/mg";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function MgPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mg", name: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -1,7 +1,8 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { import {
getBookMeta, getBookMeta,
@@ -11,7 +12,7 @@ import {
type VerseRecord, type VerseRecord,
} from "@/lib/api"; } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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 type { Metadata } from "next";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
@@ -187,9 +188,12 @@ export default async function VersePage({
> >
&larr; Mukunda-mālā-stotra &larr; Mukunda-mālā-stotra
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+9 -5
View File
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card"; 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 Link from "next/link";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@@ -59,13 +60,16 @@ export default async function MmChapterPage({
> >
&larr; Mukunda-mālā-stotra &larr; Mukunda-mālā-stotra
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{segment.title} <h1 className="text-2xl font-bold text-foreground">
</h1> {segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8"> <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"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => ( {segment.paragraphs.map((paragraph: string, i: number) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs"; import fs from "fs";
@@ -79,6 +81,7 @@ export default async function MmPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mm", name: "Mukunda-mālā-stotra" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function MogChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/mog" href="/books/mog"
@@ -77,12 +81,15 @@ export default async function MogChapterPage({
> >
&larr; Message of Godhead &larr; Message of Godhead
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function MogChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getMogMeta, getMogIntroSegments } from "@/lib/mog"; import { getMogMeta, getMogIntroSegments } from "@/lib/mog";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function MogPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/mog", name: "Message of Godhead" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -4,7 +4,8 @@ import type { Metadata } from "next";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { import {
getBookMeta, getBookMeta,
@@ -15,7 +16,7 @@ import {
} from "@/lib/api"; } from "@/lib/api";
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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 = { type NbsMeta = {
book: string; book: string;
@@ -192,9 +193,12 @@ export default async function VersePage({
> >
&larr; Nārada-bhakti-sūtra &larr; Nārada-bhakti-sūtra
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+9 -5
View File
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; import { BOOK_NAMES } from "@/lib/constants";
import { Card, CardContent } from "@/components/ui/card"; 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 Link from "next/link";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@@ -62,13 +63,16 @@ export default async function NbsChapterPage({
> >
&larr; Nārada-bhakti-sūtra &larr; Nārada-bhakti-sūtra
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{segment.title} <h1 className="text-2xl font-bold text-foreground">
</h1> {segment.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<main className="max-w-5xl mx-auto px-6 py-8"> <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"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => ( {segment.paragraphs.map((paragraph: string, i: number) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
import fs from "fs"; import fs from "fs";
@@ -79,6 +81,7 @@ export default async function NbsPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/nbs", name: "Nārada-bhakti-sūtra" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function NodChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/nod" href="/books/nod"
@@ -77,12 +81,15 @@ export default async function NodChapterPage({
> >
&larr; The Nectar of Devotion &larr; The Nectar of Devotion
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function NodChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getNodMeta, getNodIntroSegments } from "@/lib/nod"; import { getNodMeta, getNodIntroSegments } from "@/lib/nod";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -27,6 +29,7 @@ export default async function NodPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/nod", name: "The Nectar of Devotion" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+20 -13
View File
@@ -11,12 +11,15 @@ import {
type NoiSidebarItem, type NoiSidebarItem,
} from "@/lib/noi"; } from "@/lib/noi";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Type guards
@@ -93,6 +96,7 @@ export default async function NoiTextPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/noi" href="/books/noi"
@@ -100,15 +104,18 @@ export default async function NoiTextPage({
> >
&larr; The Nectar of Instruction &larr; The Nectar of Instruction
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{verse && ( <h1 className="text-2xl font-bold text-foreground">
<> {verse && (
<span className="text-primary">Text {verse.text}: </span> <>
{verse.title} <span className="text-primary">Text {verse.text}: </span>
</> {verse.title}
)} </>
{intro && intro.title} )}
</h1> {intro && intro.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -148,7 +155,7 @@ export default async function NoiTextPage({
function VerseContent({ verse }: { verse: NoiVerse }) { function VerseContent({ verse }: { verse: NoiVerse }) {
return ( return (
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8 space-y-8"> <CardContent className="py-8 px-6 sm:px-8 space-y-8">
{/* Devanagari */} {/* Devanagari */}
<section> <section>
@@ -224,7 +231,7 @@ function VerseContent({ verse }: { verse: NoiVerse }) {
function IntroContent({ intro }: { intro: NoiIntroSegment }) { function IntroContent({ intro }: { intro: NoiIntroSegment }) {
return ( return (
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{intro.paragraphs.map((paragraph, i) => ( {intro.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getNoiMeta, getNoiIntroSegments } from "@/lib/noi"; import { getNoiMeta, getNoiIntroSegments } from "@/lib/noi";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function NoiPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/noi", name: "The Nectar of Instruction" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function OchkmChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ochkm" href="/books/ochkm"
@@ -77,12 +81,15 @@ export default async function OchkmChapterPage({
> >
&larr; On Chanting the Hare Kṛṣṇa Mantra &larr; On Chanting the Hare Kṛṣṇa Mantra
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function OchkmChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getOchkmMeta, getOchkmIntroSegments } from "@/lib/ochkm"; import { getOchkmMeta, getOchkmIntroSegments } from "@/lib/ochkm";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function OchkmPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ochkm", name: "On Chanting Hare Kṛṣṇa" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function OwkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/owk" href="/books/owk"
@@ -77,12 +81,15 @@ export default async function OwkChapterPage({
> >
&larr; On the Way to Kṛṣṇa &larr; On the Way to Kṛṣṇa
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function OwkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getOwkMeta, getOwkIntroSegments } from "@/lib/owk"; import { getOwkMeta, getOwkIntroSegments } from "@/lib/owk";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function OwkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/owk", name: "On the Way to Kṛṣṇa" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { getAllBooks, type BookMeta } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BooksGrid, type BookInfo } from "./books-grid"; import { BooksGrid, type BookInfo } from "./books-grid";
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -65,6 +68,7 @@ export default async function PkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/pk" href="/books/pk"
@@ -72,12 +76,15 @@ export default async function PkChapterPage({
> >
&larr; Prayers of King Kulaśekhara &larr; Prayers of King Kulaśekhara
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Verse {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Verse {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -95,7 +102,7 @@ export default async function PkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getPkMeta } from "@/lib/pk"; import { getPkMeta } from "@/lib/pk";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function PkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/pk", name: "Prabhupāda-Kṛpā" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function PoyChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/poy" href="/books/poy"
@@ -77,12 +81,15 @@ export default async function PoyChapterPage({
> >
&larr; The Perfection of Yoga &larr; The Perfection of Yoga
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function PoyChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getPoyMeta, getPoyIntroSegments } from "@/lib/poy"; import { getPoyMeta, getPoyIntroSegments } from "@/lib/poy";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function PoyPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/poy", name: "The Perfection of Yoga" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function PqpaChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/pqpa" href="/books/pqpa"
@@ -77,12 +81,15 @@ export default async function PqpaChapterPage({
> >
&larr; Perfect Questions Perfect Answers &larr; Perfect Questions Perfect Answers
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function PqpaChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getPqpaMeta, getPqpaIntroSegments } from "@/lib/pqpa"; import { getPqpaMeta, getPqpaIntroSegments } from "@/lib/pqpa";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function PqpaPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/pqpa", name: "Perfect Questions, Perfect Answers" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function RvChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/rv" href="/books/rv"
@@ -77,12 +81,15 @@ export default async function RvChapterPage({
> >
&larr; Rāja-vidyā: The King of Knowledge &larr; Rāja-vidyā: The King of Knowledge
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function RvChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getRvMeta, getRvIntroSegments } from "@/lib/rv"; import { getRvMeta, getRvIntroSegments } from "@/lib/rv";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -20,6 +22,7 @@ export default async function RvPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/rv", name: "Rāja-vidyā: The King of Knowledge" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api"; import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
type ChapterInfo = { type ChapterInfo = {
@@ -107,9 +108,12 @@ export default async function SbVersePage({
> >
&larr; Canto {cantoInfo.number}: {cantoInfo.title} &larr; Canto {cantoInfo.number}: {cantoInfo.title}
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {chapterInfo.title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {chapterInfo.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+51 -2
View File
@@ -1,10 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { FindInPage } from "@/components/layout/find-in-page";
import { ExtractedVerseCount } from "../../extracted-verse-count"; import { ExtractedVerseCount } from "../../extracted-verse-count";
import { getBookMeta } from "@/lib/api"; import { getBookMeta, getProseChapter } from "@/lib/api";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; import { BOOK_NAMES } from "@/lib/constants";
@@ -15,6 +16,11 @@ export async function generateMetadata({
}): Promise<Metadata> { }): Promise<Metadata> {
const { canto } = await params; const { canto } = await params;
const title = BOOK_NAMES["sb"] || "Śrīmad-Bhāgavatam"; 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}` }); return buildOgMeta({ title, detail: `Canto ${canto}` });
} }
@@ -38,6 +44,49 @@ export default async function CantoPage({
}) { }) {
const { canto } = await params; const { canto } = await params;
const cantoNum = parseInt(canto); 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"
>
&larr; Ś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"); const meta = await getBookMeta("sb");
if (!meta) notFound(); if (!meta) notFound();
+36 -4
View File
@@ -1,10 +1,12 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
import { getBookMeta } from "@/lib/api"; import { getBookMeta, getProseChapters } from "@/lib/api";
import { ogForBook } from "@/lib/og"; import { ogForBook } from "@/lib/og";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
@@ -26,7 +28,10 @@ type CantoInfo = {
}; };
export default async function SbPage() { 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(); if (!meta) notFound();
const intro = meta as { const intro = meta as {
@@ -39,6 +44,7 @@ export default async function SbPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sb", name: "Śrīmad-Bhāgavatam" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
@@ -51,7 +57,33 @@ export default async function SbPage() {
</h1> </h1>
<p className="mt-1 text-muted-foreground">{intro.author}</p> <p className="mt-1 text-muted-foreground">{intro.author}</p>
</PageHeader> </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"> <Card className="divide-y divide-border">
<CardContent className="p-0"> <CardContent className="p-0">
<ul className="divide-y divide-border"> <ul className="divide-y divide-border">
@@ -1,10 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; 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 { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api"; import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/(app)/books/chapter-viewer"; 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"; import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -104,9 +105,12 @@ export default async function VersePage({
> >
&larr; Śrīmad-Bhāgavatam (1962) &larr; Śrīmad-Bhāgavatam (1962)
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-foreground"> <div className="mt-2 flex items-start justify-between gap-3">
Chapter {chapter}: {title} <h1 className="text-2xl font-bold text-foreground">
</h1> Chapter {chapter}: {title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
+4 -1
View File
@@ -3,7 +3,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getBookMeta } from "@/lib/api"; import { getBookMeta } from "@/lib/api";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { ExtractedVerseCount } from "../extracted-verse-count"; import { ExtractedVerseCount } from "../extracted-verse-count";
@@ -32,6 +34,7 @@ export default async function Sb1962Page() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sb1962", name: "Śrīmad-Bhāgavatam (1962 Edition)" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -76,6 +79,7 @@ export default async function SsrChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ssr" href="/books/ssr"
@@ -83,12 +87,15 @@ export default async function SsrChapterPage({
> >
&larr; The Science of Self-Realization &larr; The Science of Self-Realization
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{parentChapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {parentChapterNum}: </span> {parentChapterNum != null && (
)} <span className="text-primary">Chapter {parentChapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -106,7 +113,7 @@ export default async function SsrChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getSsrMeta, getSsrIntroSegments } from "@/lib/ssr"; import { getSsrMeta, getSsrIntroSegments } from "@/lib/ssr";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function SsrPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ssr", name: "The Science of Self-Realization" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -13,12 +13,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -77,6 +80,7 @@ export default async function SvaChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/sva" href="/books/sva"
@@ -84,12 +88,15 @@ export default async function SvaChapterPage({
> >
&larr; Songs of the Vaiṣṇava Ācāryas &larr; Songs of the Vaiṣṇava Ācāryas
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{parentChapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Part {parentChapterNum}: </span> {parentChapterNum != null && (
)} <span className="text-primary">Part {parentChapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -107,7 +114,7 @@ export default async function SvaChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getSvaMeta, getSvaIntroSegments } from "@/lib/sva"; import { getSvaMeta, getSvaIntroSegments } from "@/lib/sva";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function SvaPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/sva", name: "Songs of the Vaiṣṇava Ācāryas" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function TlcChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/tlc" href="/books/tlc"
@@ -77,12 +81,15 @@ export default async function TlcChapterPage({
> >
&larr; Teachings of Lord Caitanya &larr; Teachings of Lord Caitanya
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TlcChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getTlcMeta, getTlcIntroSegments } from "@/lib/tlc"; import { getTlcMeta, getTlcIntroSegments } from "@/lib/tlc";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -25,6 +27,7 @@ export default async function TlcPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tlc", name: "Teachings of Lord Caitanya" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function TlkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/tlk" href="/books/tlk"
@@ -77,12 +81,15 @@ export default async function TlkChapterPage({
> >
&larr; Teachings of Lord Kapila &larr; Teachings of Lord Kapila
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TlkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getTlkMeta, getTlkIntroSegments } from "@/lib/tlk"; import { getTlkMeta, getTlkIntroSegments } from "@/lib/tlk";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function TlkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tlk", name: "Teachings of Lord Kapila" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function TqkChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/tqk" href="/books/tqk"
@@ -77,12 +81,15 @@ export default async function TqkChapterPage({
> >
&larr; Teachings of Queen Kuntī &larr; Teachings of Queen Kuntī
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TqkChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getTqkMeta, getTqkIntroSegments } from "@/lib/tqk"; import { getTqkMeta, getTqkIntroSegments } from "@/lib/tqk";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -24,6 +26,7 @@ export default async function TqkPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/tqk", name: "Teachings of Queen Kuntī" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+16 -9
View File
@@ -12,12 +12,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -70,6 +73,7 @@ export default async function TtpChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/ttp" href="/books/ttp"
@@ -77,12 +81,15 @@ export default async function TtpChapterPage({
> >
&larr; Transcendental Teachings of Prahlāda Mahārāja &larr; Transcendental Teachings of Prahlāda Mahārāja
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{chapterNum != null && ( <h1 className="text-2xl font-bold text-foreground">
<span className="text-primary">Chapter {chapterNum}: </span> {chapterNum != null && (
)} <span className="text-primary">Chapter {chapterNum}: </span>
{content.title} )}
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -100,7 +107,7 @@ export default async function TtpChapterPage({
/> />
{/* Content */} {/* Content */}
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getTtpMeta } from "@/lib/ttp"; import { getTtpMeta } from "@/lib/ttp";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function TtpPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/ttp", name: "Teachings of Lord Kapila" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+13 -6
View File
@@ -11,12 +11,15 @@ import {
import { getProseChapter } from "@/lib/api"; import { getProseChapter } from "@/lib/api";
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
import { BOOK_NAMES } from "@/lib/constants"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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 // Metadata
@@ -62,6 +65,7 @@ export default async function VaChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/books/va" href="/books/va"
@@ -69,9 +73,12 @@ export default async function VaChapterPage({
> >
&larr; Vaiśiṣṭyāṣṭakam &larr; Vaiśiṣṭyāṣṭakam
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{content.title} <h1 className="text-2xl font-bold text-foreground">
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -85,7 +92,7 @@ export default async function VaChapterPage({
currentSlug={slug} currentSlug={slug}
/> />
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+4 -1
View File
@@ -5,7 +5,9 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getVaMeta } from "@/lib/va"; import { getVaMeta } from "@/lib/va";
import { ogForBook } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
export function generateMetadata(): Metadata { export function generateMetadata(): Metadata {
@@ -18,6 +20,7 @@ export default async function VaPage() {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd data={bookJsonLd({ path: "/books/va", name: "Vedānta-sūtra Transcript" })} />
<PageHeader> <PageHeader>
<Link <Link
href="/books" href="/books"
+100
View File
@@ -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
View File
@@ -1,94 +1,51 @@
"use client";
/** /**
* Public collection at /c/[id]. Anyone can view if `is_public=true`. * /c/[id] — public collection.
* Private collections return 403 from the BFF, which we treat as "not *
* found" to avoid leaking existence. * 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 type { Metadata } from "next";
import { notFound } from "next/navigation"; import { bff } from "@/lib/bff";
import { Globe, Lock, FolderPlus } from "lucide-react"; import { ogDefault, ogForCollection } from "@/lib/og";
import { import { CollectionContent } from "./collection-content";
collections,
SocialError,
type Collection,
type Post,
} from "@/lib/social";
import { PostViews } from "@/components/social/post-views";
export default function PublicCollectionPage(props: { type Props = { params: Promise<{ id: string }> };
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(() => { type CollectionEnvelope = {
let cancelled = false; collection: {
collections id: string;
.get(id) title: string;
.then(({ collection, posts }) => { description: string | null;
if (cancelled) return; is_public: boolean;
setC(collection); item_count: number;
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(); export async function generateMetadata({ params }: Props): Promise<Metadata> {
if (loading || !c) const { id } = await params;
return ( try {
<main className="flex-1 max-w-3xl w-full mx-auto px-4 py-12"> const data = await bff<CollectionEnvelope>(
<p className="text-sm text-muted-foreground">Loading…</p> `/collections/${encodeURIComponent(id)}`,
</main> { 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();
}
}
return ( export default function PublicCollectionPage({ params }: Props) {
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6 py-10 space-y-8"> return <CollectionContent params={params} />;
<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>
);
} }
+1 -1
View File
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { ogForDisciples } from "@/lib/og"; import { ogForDisciples } from "@/lib/og";
import { import {
getTotalDiscipleCount, getTotalDiscipleCount,
+5 -2
View File
@@ -4,12 +4,14 @@ import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { ogForEssay, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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"; import { bff, bffList } from "@/lib/bff";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -119,6 +121,7 @@ export default async function EssayPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href="/essays" href="/essays"
+1 -1
View File
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { bffList } from "@/lib/bff"; import { bffList } from "@/lib/bff";
import { EssaysGrid } from "./essays-grid"; import { EssaysGrid } from "./essays-grid";
+9
View File
@@ -5,6 +5,8 @@ import { notFound } from "next/navigation";
import { getAllGlossaryEntries } from "@/lib/api"; import { getAllGlossaryEntries } from "@/lib/api";
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og"; import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
import { GlossaryClient } from "../glossary-client"; import { GlossaryClient } from "../glossary-client";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Diacritic → base letter mapping // Diacritic → base letter mapping
@@ -145,6 +147,13 @@ export default async function GlossaryLetterPage({
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<JsonLd
data={articleJsonLd({
path: `/glossary/${rawLetter}`,
headline: `Glossary — ${activeLetter}`,
isPartOf: "Glossary of Sanskrit & Vedic Terms",
})}
/>
<GlossaryClient <GlossaryClient
entries={clientEntries} entries={clientEntries}
activeLetter={activeLetter} activeLetter={activeLetter}
+5 -2
View File
@@ -11,12 +11,14 @@ import {
type JournalSidebarItem, type JournalSidebarItem,
} from "@/lib/journals"; } from "@/lib/journals";
import { ogForJournal, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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({ export async function generateMetadata({
params, params,
@@ -74,6 +76,7 @@ export default async function JournalChapterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href={`/journals/${slug}`} href={`/journals/${slug}`}
+1 -1
View File
@@ -9,7 +9,7 @@ import {
type JournalChapterEntry, type JournalChapterEntry,
} from "@/lib/journals"; } from "@/lib/journals";
import { ogForJournal } from "@/lib/og"; 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"; import { Card, CardContent } from "@/components/ui/card";
export async function generateMetadata({ export async function generateMetadata({
+1 -1
View File
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { import {
Card, Card,
+4 -4
View File
@@ -1,8 +1,8 @@
import { Suspense } from "react"; import { Suspense } from "react";
import { SiteHeader } from "@/components/site-header"; import { SiteHeader } from "@/components/layout/site-header";
import { SiteSidebar } from "@/components/site-sidebar"; import { SiteSidebar } from "@/components/layout/site-sidebar";
import { SiteBottomNav } from "@/components/site-bottom-nav"; import { SiteBottomNav } from "@/components/layout/site-bottom-nav";
import { SiteFooter } from "@/components/site-footer"; import { SiteFooter } from "@/components/layout/site-footer";
import { VerifyEmailBanner } from "@/components/auth/verify-email-banner"; import { VerifyEmailBanner } from "@/components/auth/verify-email-banner";
import { TextSelectionToolbar } from "@/components/text-selection-toolbar"; import { TextSelectionToolbar } from "@/components/text-selection-toolbar";
import { TextHighlighter } from "@/components/text-highlighter"; import { TextHighlighter } from "@/components/text-highlighter";
+12 -1
View File
@@ -5,11 +5,13 @@ import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getLetter, getAdjacentLetters } from "@/lib/letters"; import { getLetter, getAdjacentLetters } from "@/lib/letters";
import { ogForLetter, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { JsonLd } from "@/components/seo/json-ld";
import { articleJsonLd } from "@/lib/jsonld";
export async function generateMetadata({ export async function generateMetadata({
params, params,
@@ -54,6 +56,15 @@ export default async function LetterPage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href={backHref} href={backHref}
+1 -1
View File
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { buildOgMeta } from "@/lib/og"; import { buildOgMeta } from "@/lib/og";
import { import {
getLetterYears, getLetterYears,
+13 -6
View File
@@ -11,12 +11,15 @@ import {
type MagazineSidebarItem, type MagazineSidebarItem,
} from "@/lib/magazines"; } from "@/lib/magazines";
import { ogForMagazine, extractQuoteFromHl, buildOgMeta } from "@/lib/og"; 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 { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; 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({ export async function generateMetadata({
params, params,
@@ -71,6 +74,7 @@ export default async function MagazineIssuePage({
return ( return (
<div className="min-h-screen bg-background"> <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> <PageHeader sticky>
<Link <Link
href={`/magazines/${slug}`} href={`/magazines/${slug}`}
@@ -78,9 +82,12 @@ export default async function MagazineIssuePage({
> >
&larr; {meta.title} &larr; {meta.title}
</Link> </Link>
<h1 className="mt-3 text-2xl font-bold text-foreground"> <div className="mt-3 flex items-start justify-between gap-3">
{content.title} <h1 className="text-2xl font-bold text-foreground">
</h1> {content.title}
</h1>
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
</div>
</PageHeader> </PageHeader>
<div className="max-w-5xl mx-auto flex"> <div className="max-w-5xl mx-auto flex">
@@ -101,7 +108,7 @@ export default async function MagazineIssuePage({
showMobileNav showMobileNav
/> />
<Card> <Card id="book-content">
<CardContent className="py-8 px-6 sm:px-8"> <CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4"> <div className="space-y-4">
{content.paragraphs.map((paragraph, i) => ( {content.paragraphs.map((paragraph, i) => (
+1 -1
View File
@@ -9,7 +9,7 @@ import {
type MagazineChapterEntry, type MagazineChapterEntry,
} from "@/lib/magazines"; } from "@/lib/magazines";
import { ogForMagazine } from "@/lib/og"; 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"; import { Card, CardContent } from "@/components/ui/card";
export async function generateMetadata({ export async function generateMetadata({
+1 -1
View File
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { PageHeader } from "@/components/page-header"; import { PageHeader } from "@/components/layout/page-header";
import { import {
Card, Card,
CardHeader, CardHeader,
+41 -54
View File
@@ -14,13 +14,14 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Bell } from "lucide-react"; import { Bell } from "lucide-react";
import { useAuth } from "@/components/auth/auth-provider"; import { useAuth } from "@/components/auth/auth-provider";
import { useAuthGate } from "@/components/auth/auth-gate"; import { useAuthGate } from "@/components/auth/auth-gate";
import { useNotifications } from "@/components/notifications/notifications-provider"; import { useNotifications } from "@/components/notifications/notifications-provider";
import { notifications, type AppNotification } from "@/lib/social"; import { notifications, type AppNotification } from "@/lib/social";
import { UserAvatar } from "@/components/ui/user-avatar";
import { timeAgo } from "@/lib/time";
export default function NotificationsPage() { export default function NotificationsPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
@@ -114,7 +115,11 @@ function NotificationRow({
n: AppNotification; n: AppNotification;
selfHandle: string; 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 message = messageFor(n);
const unread = !n.read_at; const unread = !n.read_at;
@@ -122,11 +127,18 @@ function NotificationRow({
<div <div
className={cnRow(unread)} 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"> <div className="min-w-0 flex-1">
<p className="text-sm text-foreground leading-snug"> {isAdmin ? (
<span className="font-semibold">{display}</span> {message} <p className="text-sm text-foreground leading-snug whitespace-pre-line">
</p> <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"> <p className="text-xs text-muted-foreground mt-0.5">
{timeAgo(n.created_at)} {timeAgo(n.created_at)}
</p> </p>
@@ -137,14 +149,22 @@ function NotificationRow({
</div> </div>
); );
// A like links to your own post; everything else links to the actor's // A like links to your own post; a mention links to the actor's post you
// profile (when we know their handle). // were tagged in; everything else links to the actor's profile.
const href = const href =
n.kind === "post_like" && n.post_id n.kind === "post_like" && n.post_id
? `/u/${selfHandle}/posts#post-${n.post_id}` ? `/u/${selfHandle}/posts#post-${n.post_id}`
: n.actor_handle : n.kind === "post_mention" && n.post_id && n.actor_handle
? `/u/${n.actor_handle}` ? `/u/${n.actor_handle}/posts#post-${n.post_id}`
: null; : (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;
return href ? ( return href ? (
<li> <li>
@@ -177,39 +197,21 @@ function messageFor(n: AppNotification): string {
if (others <= 0) return "liked your post."; if (others <= 0) return "liked your post.";
return `and ${others} ${others === 1 ? "other" : "others"} 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: default:
return "sent you a notification."; 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() { function Empty() {
return ( return (
<div className="flex flex-col items-center text-center gap-3 py-16 text-muted-foreground"> <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