Compare commits
140
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
523b6fa7e8 | ||
|
|
3257efb06a | ||
|
|
6083a485de | ||
|
|
baf36d3d67 | ||
|
|
df49e7afcc | ||
|
|
ce373e3e03 | ||
|
|
9b7758da99 | ||
|
|
6ff29db68a | ||
|
|
274c735b4d | ||
|
|
1984797ad3 | ||
|
|
1582df0ed5 | ||
|
|
1683abc2e9 | ||
|
|
12a5c15121 | ||
|
|
8bc27bb8a1 | ||
|
|
89391279bb | ||
|
|
7f716397a0 | ||
|
|
4586f1c22c | ||
|
|
b6471f9430 | ||
|
|
380c23e469 | ||
|
|
0b67a1142f | ||
|
|
be28585294 | ||
|
|
1462844e95 | ||
|
|
9916810d18 | ||
|
|
7b81bbc791 | ||
|
|
f92b41384e | ||
|
|
5cd75c27f9 | ||
|
|
5679f7a639 | ||
|
|
25db9d0eee | ||
|
|
0178f89f2b | ||
|
|
b6dc762984 | ||
|
|
1fe10adb1e | ||
|
|
84cb273941 | ||
|
|
071eb4d58a | ||
|
|
bc1798b31b | ||
|
|
fc2aef0c3d | ||
|
|
89f94c1a6e | ||
|
|
f5abe33c09 | ||
|
|
f96e7dd2f1 | ||
|
|
0cf6df5942 | ||
|
|
5c72416ae8 | ||
|
|
197e13c0ef | ||
|
|
0608e2b6f6 | ||
|
|
cb376f8497 | ||
|
|
eac56b7974 | ||
|
|
be4c2f17fe | ||
|
|
6cc9285bad | ||
|
|
ab35767374 | ||
|
|
c551d2f7a8 | ||
|
|
a4fd63695a | ||
|
|
91621693a6 | ||
|
|
79f0bd9223 | ||
|
|
7585e6cb31 | ||
|
|
c8de98c8de | ||
|
|
72a092b3c9 | ||
|
|
752ad20fed | ||
|
|
0280a81777 | ||
|
|
69e5f2c45c | ||
|
|
45c7cf047e | ||
|
|
a4baf6d215 | ||
|
|
455ed0f7cb | ||
|
|
7c3cf656b5 | ||
|
|
23d06a3db5 | ||
|
|
3accc753d0 | ||
|
|
b04ed1d3aa | ||
|
|
9808dd3033 | ||
|
|
b248f962a1 | ||
|
|
61581b67a7 | ||
|
|
eb0c981d3c | ||
|
|
37305201bd | ||
|
|
4471ef10aa | ||
|
|
397a09e814 | ||
|
|
9e68184750 | ||
|
|
439664a97d | ||
|
|
cc5633c37a | ||
|
|
6a0559e608 | ||
|
|
25a729959f | ||
|
|
af5ab9cf6b | ||
|
|
884a984e50 | ||
|
|
ead8ad02e8 | ||
|
|
4b159cb4d9 | ||
|
|
3903423d06 | ||
|
|
a46e29d998 | ||
|
|
922d76e21f | ||
|
|
909ae67320 | ||
|
|
482a845f37 | ||
|
|
647a803b96 | ||
|
|
e36afdd1a5 | ||
|
|
ef6e280524 | ||
|
|
cf733062f8 | ||
|
|
421b41cade | ||
|
|
deeda40c9d | ||
|
|
90df5bc784 | ||
|
|
0334e03c3c | ||
|
|
ecb32d2e85 | ||
|
|
d5fb350652 | ||
|
|
bcffe9684f | ||
|
|
0db339254d | ||
|
|
684ea2ae97 | ||
|
|
0f433c5708 | ||
|
|
48dd450a15 | ||
|
|
ddc6d3c7bf | ||
|
|
fd333159f6 | ||
|
|
0bcda5a791 | ||
|
|
c89cc8b779 | ||
|
|
ec67ddf852 | ||
|
|
086f1dd8aa | ||
|
|
b61c3df473 | ||
|
|
cb70a84dd5 | ||
|
|
dc01d09f09 | ||
|
|
84d86c45b5 | ||
|
|
4d45e20cb1 | ||
|
|
e72679d1b6 | ||
|
|
91d5d272d2 | ||
|
|
0352cb2003 | ||
|
|
8a8ce24479 | ||
|
|
7d76afe102 | ||
|
|
8c1f3a1b7b | ||
|
|
3624c19d2d | ||
|
|
5988d02373 | ||
|
|
17fb83087f | ||
|
|
489adb0eeb | ||
|
|
d8de0a18e5 | ||
|
|
c20d6bffe8 | ||
|
|
01bcc057fa | ||
|
|
8bd82a0e8a | ||
|
|
5506afa758 | ||
|
|
88f1b08675 | ||
|
|
336f478f8b | ||
|
|
03b8f85e06 | ||
|
|
0ff8005df2 | ||
|
|
0ae3334d98 | ||
|
|
ec20eb996b | ||
|
|
dcdd2383c6 | ||
|
|
7bd2952caa | ||
|
|
6b6ce0813e | ||
|
|
3767a3a9b6 | ||
|
|
a22fcf360d | ||
|
|
804cf1ad9a | ||
|
|
30d08c665d | ||
|
|
cc94296b46 |
@@ -0,0 +1,20 @@
|
||||
NEXT_PUBLIC_SITE_URL=https://prabhupadasays.org
|
||||
|
||||
# BFF API Server (server-side fetches)
|
||||
BFF_URL=http://localhost:8080
|
||||
|
||||
# BFF API Server (browser-side fetches; must be publicly reachable in prod)
|
||||
NEXT_PUBLIC_BFF_URL=http://localhost:8080
|
||||
|
||||
# RTF Viewer (development only)
|
||||
ALLOW_RTF=true
|
||||
NEXT_PUBLIC_OAUTH_ENABLED=true
|
||||
|
||||
# SOKETI
|
||||
NEXT_PUBLIC_SOKETI_KEY=spsays
|
||||
NEXT_PUBLIC_SOKETI_HOST=ws.prabhupadasays.org
|
||||
NEXT_PUBLIC_SOKETI_PORT=443
|
||||
NEXT_PUBLIC_SOKETI_FORCE_TLS=true
|
||||
|
||||
# Analytics
|
||||
NEXT_PUBLIC_RYBBIT_HOST=https://analytics.krispire.cloud
|
||||
+1
-1
@@ -31,7 +31,7 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AuthCard, AuthFooterLink } from "@/components/auth/auth-card";
|
||||
import { auth, AuthError } from "@/lib/auth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export default function ForgotPasswordPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
async function onSubmit(e: React.SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await auth.forgotPassword(email);
|
||||
setSubmitted(true);
|
||||
} catch (err) {
|
||||
setError(err instanceof AuthError ? err.message : "Something went wrong.");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (submitted) {
|
||||
return (
|
||||
<AuthCard
|
||||
title="Check your inbox"
|
||||
description="If an account exists for that email, we've sent a reset link. It will expire in 1 hour."
|
||||
footer={<AuthFooterLink prefix="Back to" href="/login" label="log in" />}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Didn't get an email? Check your spam folder or try again in a minute.
|
||||
</p>
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthCard
|
||||
title="Reset your password"
|
||||
description="Enter your email and we'll send you a reset link."
|
||||
footer={<AuthFooterLink prefix="Remembered it?" href="/login" label="Log in" />}
|
||||
>
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
||||
<Button type="submit" disabled={submitting} className="w-full h-11 text-base">
|
||||
{submitting ? "Sending…" : "Send reset link"}
|
||||
</Button>
|
||||
</form>
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Shared layout for the auth pages (login, signup, forgot-password,
|
||||
* reset-password). Provides the centered card frame so each page only
|
||||
* has to render its own form.
|
||||
*
|
||||
* Also gates the routes: a signed-in user landing on any /login, /signup,
|
||||
* etc. URL is bounced to /u/me (or the `next` query param if present).
|
||||
* The check happens client-side because auth state lives in
|
||||
* AuthProvider — there's no server-side cookie inspection here yet, and
|
||||
* the redirect is purely cosmetic (logged-in users don't gain anything
|
||||
* from re-seeing these forms, and signing in over an existing session
|
||||
* would just no-op).
|
||||
*/
|
||||
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useAuth } from "@/components/auth/auth-provider";
|
||||
|
||||
export default function AuthLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// useSearchParams forces a CSR bailout during prerender, so Next.js
|
||||
// requires a Suspense boundary anywhere it's read. The gate is the
|
||||
// only consumer here — isolate it so the rest of the layout can still
|
||||
// SSR normally.
|
||||
return (
|
||||
<main className="flex items-center justify-center px-4 py-12 min-h-[90vh]">
|
||||
<Suspense fallback={<AuthShell>{children}</AuthShell>}>
|
||||
<AuthGate>{children}</AuthGate>
|
||||
</Suspense>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthShell({ children }: { children: React.ReactNode }) {
|
||||
return <div className="w-full max-w-md">{children}</div>;
|
||||
}
|
||||
|
||||
function AuthGate({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
const params = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || !user) return;
|
||||
const next = params.get("next");
|
||||
router.replace(next && next.startsWith("/") ? next : "/u/me");
|
||||
}, [loading, user, router, params]);
|
||||
|
||||
// Don't flash the form for an instant before the redirect fires.
|
||||
if (!loading && user) {
|
||||
return <p className="text-sm text-muted-foreground">Redirecting…</p>;
|
||||
}
|
||||
|
||||
return <AuthShell>{children}</AuthShell>;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { AuthCard, AuthFooterLink } from "@/components/auth/auth-card";
|
||||
import { LoginForm } from "@/components/auth/login-form";
|
||||
|
||||
function LoginContent() {
|
||||
const router = useRouter();
|
||||
const params = useSearchParams();
|
||||
const next = params.get("next") ?? "/u/me";
|
||||
const oauthError = params.get("oauth_error");
|
||||
|
||||
return (
|
||||
<AuthCard
|
||||
title="Welcome back"
|
||||
description="Log in to access your bookmarks, notes, and collections."
|
||||
footer={
|
||||
<AuthFooterLink prefix="No account?" href="/signup" label="Create one" />
|
||||
}
|
||||
>
|
||||
{oauthError ? (
|
||||
<p className="text-sm text-destructive">
|
||||
{oauthErrorMessage(oauthError)}
|
||||
</p>
|
||||
) : null}
|
||||
<LoginForm
|
||||
oauthNext={next}
|
||||
onSuccess={() => {
|
||||
router.push(next);
|
||||
router.refresh();
|
||||
}}
|
||||
/>
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
|
||||
function oauthErrorMessage(code: string): string {
|
||||
switch (code) {
|
||||
case "invalid_state":
|
||||
case "missing_state":
|
||||
return "Your sign-in link expired. Please try again.";
|
||||
case "exchange_failed":
|
||||
case "profile_failed":
|
||||
return "Sign-in failed while talking to the provider. Please try again.";
|
||||
case "unknown_provider":
|
||||
return "That sign-in provider isn't configured. Please use email or another method.";
|
||||
case "unavailable":
|
||||
return "Social sign-in is temporarily unavailable. Please use email/password.";
|
||||
case "server":
|
||||
return "Something went wrong on our side. Please try again in a moment.";
|
||||
default:
|
||||
return "Sign-in failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<LoginContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense, useState } from "react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { AuthCard, AuthFooterLink } from "@/components/auth/auth-card";
|
||||
import { auth, AuthError } from "@/lib/auth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
function ResetPasswordForm() {
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
const token = params.get("token") ?? "";
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [done, setDone] = useState(false);
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<AuthCard title="Invalid reset link" description="The link is missing a token. Please request a new one.">
|
||||
<AuthFooterLink prefix="Need a new link?" href="/forgot-password" label="Request one" />
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
|
||||
async function onSubmit(e: React.SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
if (password !== confirm) {
|
||||
setError("Passwords do not match.");
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await auth.resetPassword(token, password);
|
||||
setDone(true);
|
||||
setTimeout(() => router.push("/login"), 1500);
|
||||
} catch (err) {
|
||||
setError(err instanceof AuthError ? err.message : "Reset failed.");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
return (
|
||||
<AuthCard
|
||||
title="Password updated"
|
||||
description="Redirecting you to the login page…"
|
||||
>
|
||||
<AuthFooterLink prefix="Take me there now —" href="/login" label="Log in" />
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthCard
|
||||
title="Set a new password"
|
||||
description="Choose a strong password you haven't used before."
|
||||
>
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="password">New password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
minLength={8}
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="confirm">Confirm password</Label>
|
||||
<Input
|
||||
id="confirm"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
minLength={8}
|
||||
required
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
||||
<Button type="submit" disabled={submitting} className="w-full h-11 text-base">
|
||||
{submitting ? "Updating…" : "Update password"}
|
||||
</Button>
|
||||
</form>
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
// useSearchParams must be inside Suspense for Next.js streaming.
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<ResetPasswordForm />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AuthCard, AuthFooterLink } from "@/components/auth/auth-card";
|
||||
import { SignUpForm } from "@/components/auth/signup-form";
|
||||
|
||||
export default function SignUpPage() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<AuthCard
|
||||
title="Create your account"
|
||||
description="Save bookmarks, build collections, and follow Śrīla Prabhupāda's words across devices."
|
||||
footer={
|
||||
<AuthFooterLink prefix="Already registered?" href="/login" label="Log in" />
|
||||
}
|
||||
>
|
||||
<SignUpForm
|
||||
oauthNext="/u/me"
|
||||
onSuccess={() => {
|
||||
router.push("/u/me");
|
||||
router.refresh();
|
||||
}}
|
||||
/>
|
||||
</AuthCard>
|
||||
);
|
||||
}
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function BbdChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/bbd/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Beyond Birth and Death" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bbd"
|
||||
@@ -77,12 +81,15 @@ export default async function BbdChapterPage({
|
||||
>
|
||||
← Beyond Birth and Death
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function BbdChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBbdMeta, getBbdIntroSegments } from "@/lib/bbd";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function BbdPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bbd", name: "Beyond Birth and Death" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+8
-4
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -101,9 +102,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Bhagavad-gītā
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -0,0 +1,86 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { getChapterVerseIndex, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ chapter: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { chapter } = await params;
|
||||
// Non-numeric segments are front matter (dedication, foreword, …).
|
||||
if (isNaN(parseInt(chapter))) {
|
||||
const segment = await getProseChapter("bg", 0, chapter);
|
||||
return buildOgMeta({
|
||||
title: BOOK_NAMES["bg"],
|
||||
detail: segment?.title ?? "Front Matter",
|
||||
});
|
||||
}
|
||||
return buildOgMeta({ title: BOOK_NAMES["bg"], detail: `Chapter ${chapter}` });
|
||||
}
|
||||
|
||||
export default async function ChapterPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ chapter: string }>;
|
||||
}) {
|
||||
const { chapter } = await params;
|
||||
|
||||
// Front matter — non-numeric slugs (dedication, foreword, preface,
|
||||
// introduction, disciplic-succession) resolve to prose intro-segments.
|
||||
if (isNaN(parseInt(chapter))) {
|
||||
const segment = await getProseChapter("bg", 0, chapter);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bg"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← {BOOK_NAMES["bg"]}
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Numeric chapters — redirect to the first verse.
|
||||
const verseIndex = await getChapterVerseIndex("bg", null, parseInt(chapter));
|
||||
if (verseIndex.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
redirect(`/books/bg/${chapter}/${verseIndex[0].verse}`);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return ogForBook("bg");
|
||||
}
|
||||
|
||||
type ChapterInfo = {
|
||||
number: number;
|
||||
title: string;
|
||||
totalVerses: number;
|
||||
};
|
||||
|
||||
export default async function BgPage() {
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("bg"),
|
||||
getProseChapters("bg", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) notFound();
|
||||
|
||||
const intro = meta as unknown as {
|
||||
book: string;
|
||||
title: string;
|
||||
author: string;
|
||||
totalChapters: number;
|
||||
chapters: ChapterInfo[];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bg", name: "Bhagavad-gītā As It Is" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← Books
|
||||
</Link>
|
||||
<h1 className="mt-3 text-3xl font-bold text-foreground">
|
||||
{intro.title}
|
||||
</h1>
|
||||
<p className="mt-1 text-muted-foreground">{intro.author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{/* Front Matter */}
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/bg/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chapters */}
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Chapters
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{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>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -62,6 +65,7 @@ export default async function BkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/bk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Bhagavāner Kathā" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/bk"
|
||||
@@ -69,12 +73,15 @@ export default async function BkChapterPage({
|
||||
>
|
||||
← Bhagavāner Kathā
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -92,7 +99,7 @@ export default async function BkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBkMeta } from "@/lib/bk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function BkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bk", name: "Bhagavāner Kathā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -72,11 +72,16 @@ export function BooksGrid({ books }: { books: BookInfo[] }) {
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!query.trim()) return books;
|
||||
const q = normalize(query.trim());
|
||||
return normalizedBooks
|
||||
.filter((entry) => entry.searchText.includes(q))
|
||||
.map((entry) => entry.book);
|
||||
const base = query.trim()
|
||||
? normalizedBooks
|
||||
.filter((entry) =>
|
||||
entry.searchText.includes(normalize(query.trim())),
|
||||
)
|
||||
.map((entry) => entry.book)
|
||||
: books;
|
||||
return [...base].sort((a, b) =>
|
||||
normalize(a.title).localeCompare(normalize(b.title)),
|
||||
);
|
||||
}, [books, normalizedBooks, query]);
|
||||
|
||||
return (
|
||||
+8
-4
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -11,8 +12,8 @@ import {
|
||||
type VerseIndexEntry,
|
||||
type VerseRecord,
|
||||
} from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -187,9 +188,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Śrī Brahma-saṁhitā
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -59,13 +60,16 @@ export default async function BsChapterPage({
|
||||
>
|
||||
← Śrī Brahma-saṁhitā
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -80,6 +82,7 @@ export default async function BsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/bs", name: "Śrī Brahma-saṁhitā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+11
-4
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -62,6 +65,7 @@ export default async function CbhcChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/cbhc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Śrī Caitanya-bhāgavata Commentary" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/cbhc"
|
||||
@@ -69,12 +73,15 @@ export default async function CbhcChapterPage({
|
||||
>
|
||||
← Śrī Caitanya-bhāgavata Commentary
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -92,7 +99,7 @@ export default async function CbhcChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getCbhcMeta } from "@/lib/cbhc";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function CbhcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/cbhc", name: "Śrī Caitanya-bhāgavata Commentary" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+8
-4
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -111,9 +112,12 @@ export default async function CcVersePage({
|
||||
>
|
||||
← {lilaInfo.name}
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {chapterInfo.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -0,0 +1,227 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { redirect, notFound } from "next/navigation";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { getBookMeta, getProseChapter, getChapterVerseIndex } from "@/lib/api";
|
||||
import { CC_LILAS } from "@/lib/constants";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type ChapterInfo = { number: number; title: string; totalVerses: number };
|
||||
type LilaInfo = {
|
||||
key: string;
|
||||
name: string;
|
||||
totalChapters: number;
|
||||
totalVerses: number;
|
||||
chapters: ChapterInfo[];
|
||||
};
|
||||
type VerseEntry = { verse: number; verseEnd?: number | null; reference: string };
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lila: string; chapter: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { lila, chapter } = await params;
|
||||
const lilaName = CC_LILAS[lila] || lila;
|
||||
return buildOgMeta({
|
||||
title: "Śrī Caitanya-caritāmṛta",
|
||||
detail: `${lilaName}, Chapter ${chapter}`,
|
||||
});
|
||||
}
|
||||
|
||||
function verseLabel(v: VerseEntry): string {
|
||||
return v.verseEnd ? `${v.verse}–${v.verseEnd}` : String(v.verse);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Top/bottom navigation — mirrors the verse pages' VerseNav: a Contents
|
||||
// toggle (mobile) listing this chapter's verses, Previous/Next (chapter), and
|
||||
// a "Chapter X of N" label.
|
||||
// ---------------------------------------------------------------------------
|
||||
function ChapterNav({
|
||||
lila,
|
||||
chapters,
|
||||
current,
|
||||
chapterTitle,
|
||||
verseIndex,
|
||||
}: {
|
||||
lila: string;
|
||||
chapters: ChapterInfo[];
|
||||
current: number;
|
||||
chapterTitle: string;
|
||||
verseIndex: VerseEntry[];
|
||||
}) {
|
||||
const idx = chapters.findIndex((c) => c.number === current);
|
||||
const prev = idx > 0 ? chapters[idx - 1] : null;
|
||||
const next = idx < chapters.length - 1 ? chapters[idx + 1] : null;
|
||||
|
||||
// The contents drawer lists the active chapter's verses (same as the
|
||||
// verse-page sidebar) so the reader can jump straight in.
|
||||
const groups: MobileNavGroup[] = [
|
||||
{
|
||||
title: "Verses",
|
||||
items: verseIndex.map((v) => ({
|
||||
slug: String(v.verse),
|
||||
label: `Verse ${verseLabel(v)}`,
|
||||
href: `/books/cc/${lila}/${current}/${v.verse}`,
|
||||
type: "verse",
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<MobileNav
|
||||
groups={groups}
|
||||
currentSlug=""
|
||||
bookTitle={`Chapter ${current}: ${chapterTitle}`}
|
||||
/>
|
||||
{prev ? (
|
||||
<Link
|
||||
href={`/books/cc/${lila}/${prev.number}`}
|
||||
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
<span className="hidden sm:inline">Previous</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="hidden lg:block" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Chapter {current} of {chapters.length}
|
||||
</span>
|
||||
|
||||
{next ? (
|
||||
<Link
|
||||
href={`/books/cc/${lila}/${next.number}`}
|
||||
className={cn(buttonVariants({ variant: "outline", size: "sm" }), "gap-1.5")}
|
||||
>
|
||||
<span className="hidden sm:inline">Next</span>
|
||||
<ChevronRight className="size-4" />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function CcChapterPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lila: string; chapter: string }>;
|
||||
}) {
|
||||
const { lila, chapter } = await params;
|
||||
const chapterNum = parseInt(chapter);
|
||||
|
||||
const [meta, summary, verseIndex] = await Promise.all([
|
||||
getBookMeta("cc"),
|
||||
getProseChapter("cc", 0, `${lila}-${chapterNum}`),
|
||||
getChapterVerseIndex("cc", lila, chapterNum),
|
||||
]);
|
||||
|
||||
if (!meta) notFound();
|
||||
const lilaInfo = (meta.lilas as LilaInfo[] | undefined)?.find(
|
||||
(l) => l.key === lila,
|
||||
);
|
||||
if (!lilaInfo) notFound();
|
||||
const chapterInfo = lilaInfo.chapters.find((ch) => ch.number === chapterNum);
|
||||
if (!chapterInfo) notFound();
|
||||
if (verseIndex.length === 0) notFound();
|
||||
|
||||
const firstVerse = verseIndex[0].verse;
|
||||
|
||||
// Chapters without a summary (e.g. Ādi 7) go straight to the verses.
|
||||
if (!summary || summary.paragraphs.length === 0) {
|
||||
redirect(`/books/cc/${lila}/${chapterNum}/${firstVerse}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={`/books/cc/${lila}`}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← {lilaInfo.name}
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapterNum}: {chapterInfo.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
{/* Desktop sidebar — verses of the active chapter (matches the
|
||||
verse-page sidebar) so the reader can jump to any verse. */}
|
||||
<aside className="hidden lg:block w-64 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-165px)] overflow-y-auto py-8 pr-4">
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3 px-2">
|
||||
Verses
|
||||
</h2>
|
||||
<nav className="grid grid-cols-3 gap-1">
|
||||
{verseIndex.map((v) => (
|
||||
<Link
|
||||
key={v.verse}
|
||||
href={`/books/cc/${lila}/${chapterNum}/${v.verse}`}
|
||||
className="px-2 py-1 rounded-md text-sm tabular-nums text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
{verseLabel(v)}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* Main — chapter summary */}
|
||||
<main className="flex-1 min-w-0 px-6 py-8 space-y-6">
|
||||
{/* Top navigation */}
|
||||
<ChapterNav
|
||||
lila={lila}
|
||||
chapters={lilaInfo.chapters}
|
||||
current={chapterNum}
|
||||
chapterTitle={chapterInfo.title}
|
||||
verseIndex={verseIndex}
|
||||
/>
|
||||
|
||||
<p className="text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Chapter Summary
|
||||
</p>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{summary.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Bottom navigation */}
|
||||
<ChapterNav
|
||||
lila={lila}
|
||||
chapters={lilaInfo.chapters}
|
||||
current={chapterNum}
|
||||
chapterTitle={chapterInfo.title}
|
||||
verseIndex={verseIndex}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { ExtractedVerseCount } from "../../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES, CC_LILAS } from "@/lib/constants";
|
||||
|
||||
@@ -14,12 +15,15 @@ export async function generateMetadata({
|
||||
params: Promise<{ lila: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { lila } = await params;
|
||||
const lilaName = CC_LILAS[lila] || lila;
|
||||
const bookTitle = BOOK_NAMES["cc"] || "Śrī Caitanya-caritāmṛta";
|
||||
return buildOgMeta({
|
||||
title: bookTitle,
|
||||
detail: lilaName,
|
||||
});
|
||||
// A non-līlā segment (front-flap, preface, introduction) is front matter.
|
||||
if (!CC_LILAS[lila]) {
|
||||
const segment = await getProseChapter("cc", 0, lila);
|
||||
if (segment) {
|
||||
return buildOgMeta({ title: bookTitle, detail: segment.title });
|
||||
}
|
||||
}
|
||||
return buildOgMeta({ title: bookTitle, detail: CC_LILAS[lila] || lila });
|
||||
}
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -47,7 +51,48 @@ export default async function LilaPage({
|
||||
|
||||
const lilas = meta.lilas as LilaInfo[];
|
||||
const lilaInfo = lilas.find((l) => l.key === lila);
|
||||
if (!lilaInfo) notFound();
|
||||
|
||||
// Front matter — the param is not a līlā key, so resolve it as a prose
|
||||
// intro-segment (front-flap, preface, introduction).
|
||||
if (!lilaInfo) {
|
||||
const segment = await getProseChapter("cc", 0, lila);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/cc"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← Śrī Caitanya-caritāmṛta
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const totalVerses = lilaInfo.chapters.reduce(
|
||||
(sum, ch) => sum + ch.totalVerses,
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
@@ -26,7 +28,10 @@ export function generateMetadata(): Metadata {
|
||||
}
|
||||
|
||||
export default async function CcPage() {
|
||||
const meta = await getBookMeta("cc");
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("cc"),
|
||||
getProseChapters("cc", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) {
|
||||
return (
|
||||
<div className="p-8 text-center text-muted-foreground">
|
||||
@@ -41,6 +46,7 @@ export default async function CcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/cc", name: "Śrī Caitanya-caritāmṛta" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -51,7 +57,33 @@ export default async function CcPage() {
|
||||
<h1 className="mt-3 text-3xl font-bold text-foreground">{title}</h1>
|
||||
<p className="mt-1 text-muted-foreground">{author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/cc/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="divide-y divide-border">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
@@ -6,7 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -216,9 +219,17 @@ function VerseCard({
|
||||
Purport
|
||||
</h3>
|
||||
<div className="text-base text-foreground leading-relaxed space-y-3">
|
||||
{verse.purport.map((p, i) => (
|
||||
{verse.purport.map((p, i) =>
|
||||
// Cited ślokas come wrapped in <blockquote class="shloka"> — a
|
||||
// block element that can't live inside a <p>, so render those
|
||||
// in a <div> (styled centered + accent via globals.css); plain
|
||||
// prose paragraphs stay as <p>.
|
||||
/^\s*<blockquote/i.test(p) ? (
|
||||
<div key={i} dangerouslySetInnerHTML={{ __html: p }} />
|
||||
) : (
|
||||
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -389,8 +400,25 @@ export function ChapterViewer({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Structured data for the currently-shown verse (SSR'd for the initial
|
||||
verse, updates on client navigation). */}
|
||||
{currentVerse ? (
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `${basePath}/${currentVerseNum}`,
|
||||
headline: `${BOOK_NAMES[book] ?? book} ${currentVerse.reference}`,
|
||||
description: currentVerse.translation,
|
||||
isPartOf: BOOK_NAMES[book] ?? book,
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* ---- Sidebar: Verse Index ---- */}
|
||||
<aside className="hidden lg:block w-64 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-89px)] overflow-y-auto py-8 pr-4">
|
||||
{/* 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
|
||||
page is scrolled to the bottom. (89 sticky-top + 76 bottom gap
|
||||
= 165, hence calc(100vh - 165px).) */}
|
||||
<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>
|
||||
@@ -436,7 +464,8 @@ export function ChapterViewer({
|
||||
mobileNavBookTitle={mobileNavBookTitle}
|
||||
/>
|
||||
|
||||
{/* Verse content */}
|
||||
{/* Verse content — id targeted by each page header's FindInPage. */}
|
||||
<div id="book-content">
|
||||
{loading && !currentVerse ? (
|
||||
<div className="flex items-center justify-center py-24 text-muted-foreground gap-2">
|
||||
<Loader2 className="size-5 animate-spin" />
|
||||
@@ -449,6 +478,7 @@ export function ChapterViewer({
|
||||
Verse not found.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom navigation */}
|
||||
<VerseNav
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -76,6 +79,7 @@ export default async function DsChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ds/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Dialectic Spiritualism" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ds"
|
||||
@@ -83,12 +87,15 @@ export default async function DsChapterPage({
|
||||
>
|
||||
← Dialectic Spiritualism
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Chapter {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -106,7 +113,7 @@ export default async function DsChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getDsMeta, getDsIntroSegments } from "@/lib/ds";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function DsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ds", name: "Dialectic Spiritualism" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function EjChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ej/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Easy Journey to Other Planets" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ej"
|
||||
@@ -77,12 +81,15 @@ export default async function EjChapterPage({
|
||||
>
|
||||
← Easy Journey to Other Planets
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function EjChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEjMeta, getEjIntroSegments } from "@/lib/ej";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function EjPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ej", name: "Easy Journey to Other Planets" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function EkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ek/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Elevation to Kṛṣṇa Consciousness" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ek"
|
||||
@@ -77,12 +81,15 @@ export default async function EkChapterPage({
|
||||
>
|
||||
← Elevation to Kṛṣṇa Consciousness
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function EkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEkMeta, getEkIntroSegments } from "@/lib/ek";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function EkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ek", name: "Elevation to Kṛṣṇa Consciousness" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -66,6 +69,7 @@ export default async function GgChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/gg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Gītār Gān" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/gg"
|
||||
@@ -73,12 +77,15 @@ export default async function GgChapterPage({
|
||||
>
|
||||
← Gītār Gān
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function GgChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getGgMeta, getGgIntroSegments } from "@/lib/gg";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function GgPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/gg", name: "Gītār Gān" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function HkhChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/hkh/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Kṛṣṇa Consciousness Handbook" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/hkh"
|
||||
@@ -77,12 +81,15 @@ export default async function HkhChapterPage({
|
||||
>
|
||||
← The Kṛṣṇa Consciousness Handbook
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function HkhChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getHkhMeta, getHkhIntroSegments } from "@/lib/hkh";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function HkhPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/hkh", name: "The Kṛṣṇa Consciousness Handbook" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
type IsoSidebarItem,
|
||||
} from "@/lib/iso";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type guards
|
||||
@@ -102,6 +105,7 @@ export default async function IsoMantraPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/iso/${slug}`, headline: content.title ?? "Śrī Īśopaniṣad", isPartOf: "Śrī Īśopaniṣad" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/iso"
|
||||
@@ -109,7 +113,8 @@ export default async function IsoMantraPage({
|
||||
>
|
||||
← Śrī Īśopaniṣad
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{verse && (
|
||||
<>
|
||||
<span className="text-primary">{verse.reference}: </span>
|
||||
@@ -118,6 +123,8 @@ export default async function IsoMantraPage({
|
||||
)}
|
||||
{intro && intro.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -157,7 +164,7 @@ export default async function IsoMantraPage({
|
||||
|
||||
function VerseContent({ verse }: { verse: IsoVerse }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
|
||||
{/* Devanagari */}
|
||||
<section>
|
||||
@@ -233,7 +240,7 @@ function VerseContent({ verse }: { verse: IsoVerse }) {
|
||||
|
||||
function IntroContent({ intro }: { intro: IsoIntroSegment }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{intro.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getIsoMeta, getIsoIntroSegments } from "@/lib/iso";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -28,6 +30,7 @@ export default async function IsoPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/iso", name: "Śrī Īśopaniṣad" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+11
-4
@@ -6,12 +6,15 @@ import { notFound } from "next/navigation";
|
||||
import { getProseChapter, getProseChapters } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -65,6 +68,7 @@ export default async function KbChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/kb/${vol}/${slug}`, headline: chapter.title, description: chapter.paragraphs?.[0], isPartOf: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={`/books/kb/${vol}`}
|
||||
@@ -72,12 +76,15 @@ export default async function KbChapterPage({
|
||||
>
|
||||
← Volume {vol}
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterLabel && (
|
||||
<span className="text-primary">{chapterLabel}: </span>
|
||||
)}
|
||||
{chapter.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function KbChapterPage({
|
||||
/>
|
||||
|
||||
{/* Chapter content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{chapter.paragraphs.map((paragraph, i) => (
|
||||
@@ -4,7 +4,7 @@ import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -3,7 +3,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -27,6 +29,7 @@ export default async function KbPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/kb", name: "Kṛṣṇa, The Supreme Personality of Godhead" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function LclChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/lcl/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Life Comes from Life" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/lcl"
|
||||
@@ -77,12 +81,15 @@ export default async function LclChapterPage({
|
||||
>
|
||||
← Life Comes from Life
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function LclChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLclMeta, getLclIntroSegments } from "@/lib/lcl";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function LclPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/lcl", name: "Life Comes from Life" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -66,6 +69,7 @@ export default async function LobChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/lob/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Light of the Bhāgavata" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/lob"
|
||||
@@ -73,12 +77,15 @@ export default async function LobChapterPage({
|
||||
>
|
||||
← Light of the Bhāgavata
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Text {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function LobChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) =>
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLobMeta, getLobIntroSegments } from "@/lib/lob";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function LobPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/lob", name: "Light of the Bhāgavata" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -66,6 +69,7 @@ export default async function MgChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/mg/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/mg"
|
||||
@@ -73,12 +77,15 @@ export default async function MgChapterPage({
|
||||
>
|
||||
← Kṛṣṇa Consciousness: The Matchless Gift
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -96,7 +103,7 @@ export default async function MgChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMgMeta, getMgIntroSegments } from "@/lib/mg";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function MgPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mg", name: "Kṛṣṇa Consciousness: The Matchless Gift" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+8
-4
@@ -1,7 +1,8 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
type VerseIndexEntry,
|
||||
type VerseRecord,
|
||||
} from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import type { Metadata } from "next";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
@@ -187,9 +188,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Mukunda-mālā-stotra
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -59,13 +60,16 @@ export default async function MmChapterPage({
|
||||
>
|
||||
← Mukunda-mālā-stotra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -79,6 +81,7 @@ export default async function MmPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mm", name: "Mukunda-mālā-stotra" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function MogChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/mog/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Message of Godhead" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/mog"
|
||||
@@ -77,12 +81,15 @@ export default async function MogChapterPage({
|
||||
>
|
||||
← Message of Godhead
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function MogChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMogMeta, getMogIntroSegments } from "@/lib/mog";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function MogPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/mog", name: "Message of Godhead" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+8
-4
@@ -4,7 +4,8 @@ import type { Metadata } from "next";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBookMeta,
|
||||
@@ -14,8 +15,8 @@ import {
|
||||
type VerseRecord,
|
||||
} from "@/lib/api";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
|
||||
type NbsMeta = {
|
||||
book: string;
|
||||
@@ -192,9 +193,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Nārada-bhakti-sūtra
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -7,7 +7,8 @@ import { getChapterVerseIndex } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import Link from "next/link";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -62,13 +63,16 @@ export default async function NbsChapterPage({
|
||||
>
|
||||
← Nārada-bhakti-sūtra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph: string, i: number) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import fs from "fs";
|
||||
@@ -79,6 +81,7 @@ export default async function NbsPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/nbs", name: "Nārada-bhakti-sūtra" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function NodChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/nod/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Nectar of Devotion" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/nod"
|
||||
@@ -77,12 +81,15 @@ export default async function NodChapterPage({
|
||||
>
|
||||
← The Nectar of Devotion
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function NodChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNodMeta, getNodIntroSegments } from "@/lib/nod";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -27,6 +29,7 @@ export default async function NodPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/nod", name: "The Nectar of Devotion" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
type NoiSidebarItem,
|
||||
} from "@/lib/noi";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type guards
|
||||
@@ -93,6 +96,7 @@ export default async function NoiTextPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/noi/${slug}`, headline: content.title ?? "The Nectar of Instruction", isPartOf: "The Nectar of Instruction" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/noi"
|
||||
@@ -100,7 +104,8 @@ export default async function NoiTextPage({
|
||||
>
|
||||
← The Nectar of Instruction
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{verse && (
|
||||
<>
|
||||
<span className="text-primary">Text {verse.text}: </span>
|
||||
@@ -109,6 +114,8 @@ export default async function NoiTextPage({
|
||||
)}
|
||||
{intro && intro.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -148,7 +155,7 @@ export default async function NoiTextPage({
|
||||
|
||||
function VerseContent({ verse }: { verse: NoiVerse }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8 space-y-8">
|
||||
{/* Devanagari */}
|
||||
<section>
|
||||
@@ -224,7 +231,7 @@ function VerseContent({ verse }: { verse: NoiVerse }) {
|
||||
|
||||
function IntroContent({ intro }: { intro: NoiIntroSegment }) {
|
||||
return (
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{intro.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNoiMeta, getNoiIntroSegments } from "@/lib/noi";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function NoiPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/noi", name: "The Nectar of Instruction" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+11
-4
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function OchkmChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ochkm/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On Chanting Hare Kṛṣṇa" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ochkm"
|
||||
@@ -77,12 +81,15 @@ export default async function OchkmChapterPage({
|
||||
>
|
||||
← On Chanting the Hare Kṛṣṇa Mantra
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function OchkmChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getOchkmMeta, getOchkmIntroSegments } from "@/lib/ochkm";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function OchkmPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ochkm", name: "On Chanting Hare Kṛṣṇa" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function OwkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/owk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "On the Way to Kṛṣṇa" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/owk"
|
||||
@@ -77,12 +81,15 @@ export default async function OwkChapterPage({
|
||||
>
|
||||
← On the Way to Kṛṣṇa
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function OwkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getOwkMeta, getOwkIntroSegments } from "@/lib/owk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function OwkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/owk", name: "On the Way to Kṛṣṇa" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { getAllBooks, type BookMeta } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BooksGrid, type BookInfo } from "./books-grid";
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -65,6 +68,7 @@ export default async function PkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/pk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Prabhupāda-Kṛpā" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/pk"
|
||||
@@ -72,12 +76,15 @@ export default async function PkChapterPage({
|
||||
>
|
||||
← Prayers of King Kulaśekhara
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Verse {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -95,7 +102,7 @@ export default async function PkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPkMeta } from "@/lib/pk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function PkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/pk", name: "Prabhupāda-Kṛpā" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function PoyChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/poy/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Perfection of Yoga" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/poy"
|
||||
@@ -77,12 +81,15 @@ export default async function PoyChapterPage({
|
||||
>
|
||||
← The Perfection of Yoga
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function PoyChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPoyMeta, getPoyIntroSegments } from "@/lib/poy";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function PoyPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/poy", name: "The Perfection of Yoga" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+11
-4
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function PqpaChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/pqpa/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Perfect Questions, Perfect Answers" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/pqpa"
|
||||
@@ -77,12 +81,15 @@ export default async function PqpaChapterPage({
|
||||
>
|
||||
← Perfect Questions Perfect Answers
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function PqpaChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPqpaMeta, getPqpaIntroSegments } from "@/lib/pqpa";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function PqpaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/pqpa", name: "Perfect Questions, Perfect Answers" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function RvChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/rv/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Rāja-vidyā: The King of Knowledge" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/rv"
|
||||
@@ -77,12 +81,15 @@ export default async function RvChapterPage({
|
||||
>
|
||||
← Rāja-vidyā: The King of Knowledge
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function RvChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getRvMeta, getRvIntroSegments } from "@/lib/rv";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -20,6 +22,7 @@ export default async function RvPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/rv", name: "Rāja-vidyā: The King of Knowledge" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+8
-4
@@ -2,11 +2,12 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
type ChapterInfo = {
|
||||
@@ -107,9 +108,12 @@ export default async function SbVersePage({
|
||||
>
|
||||
← Canto {cantoInfo.number}: {cantoInfo.title}
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {chapterInfo.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { ExtractedVerseCount } from "../../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapter } from "@/lib/api";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
|
||||
@@ -15,6 +16,11 @@ export async function generateMetadata({
|
||||
}): Promise<Metadata> {
|
||||
const { canto } = await params;
|
||||
const title = BOOK_NAMES["sb"] || "Śrīmad-Bhāgavatam";
|
||||
// A non-numeric segment (preface, introduction) is front matter.
|
||||
if (isNaN(parseInt(canto))) {
|
||||
const segment = await getProseChapter("sb", 0, canto);
|
||||
if (segment) return buildOgMeta({ title, detail: segment.title });
|
||||
}
|
||||
return buildOgMeta({ title, detail: `Canto ${canto}` });
|
||||
}
|
||||
|
||||
@@ -38,6 +44,49 @@ export default async function CantoPage({
|
||||
}) {
|
||||
const { canto } = await params;
|
||||
const cantoNum = parseInt(canto);
|
||||
|
||||
// Front matter — a non-numeric param (preface, introduction) resolves to a
|
||||
// prose intro-segment.
|
||||
if (isNaN(cantoNum)) {
|
||||
const segment = await getProseChapter("sb", 0, canto);
|
||||
if (!segment) notFound();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/sb"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← Śrīmad-Bhāgavatam
|
||||
</Link>
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{segment.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{segment.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = await getBookMeta("sb");
|
||||
if (!meta) notFound();
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { getBookMeta, getProseChapters } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
@@ -26,7 +28,10 @@ type CantoInfo = {
|
||||
};
|
||||
|
||||
export default async function SbPage() {
|
||||
const meta = await getBookMeta("sb");
|
||||
const [meta, introSegments] = await Promise.all([
|
||||
getBookMeta("sb"),
|
||||
getProseChapters("sb", 0, "intro-segment"),
|
||||
]);
|
||||
if (!meta) notFound();
|
||||
|
||||
const intro = meta as {
|
||||
@@ -39,6 +44,7 @@ export default async function SbPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sb", name: "Śrīmad-Bhāgavatam" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -51,7 +57,33 @@ export default async function SbPage() {
|
||||
</h1>
|
||||
<p className="mt-1 text-muted-foreground">{intro.author}</p>
|
||||
</PageHeader>
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{introSegments.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground mb-3">
|
||||
Front Matter
|
||||
</h2>
|
||||
<Card className="divide-y divide-border py-0">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
{introSegments.map((seg) => (
|
||||
<li key={seg.slug}>
|
||||
<Link
|
||||
href={`/books/sb/${seg.slug}`}
|
||||
className="flex px-5 py-4 hover:bg-muted transition-colors"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{seg.title}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card className="divide-y divide-border">
|
||||
<CardContent className="p-0">
|
||||
<ul className="divide-y divide-border">
|
||||
+8
-4
@@ -1,10 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
|
||||
import { ChapterViewer } from "@/app/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { ChapterViewer } from "@/app/(app)/(reader)/books/chapter-viewer";
|
||||
import type { MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { ogForVerse, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -104,9 +105,12 @@ export default async function VersePage({
|
||||
>
|
||||
← Śrīmad-Bhāgavatam (1962)
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-bold text-foreground">
|
||||
<div className="mt-2 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
Chapter {chapter}: {title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -3,7 +3,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getBookMeta } from "@/lib/api";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ExtractedVerseCount } from "../extracted-verse-count";
|
||||
|
||||
@@ -32,6 +34,7 @@ export default async function Sb1962Page() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sb1962", name: "Śrīmad-Bhāgavatam (1962 Edition)" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -76,6 +79,7 @@ export default async function SsrChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ssr/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "The Science of Self-Realization" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ssr"
|
||||
@@ -83,12 +87,15 @@ export default async function SsrChapterPage({
|
||||
>
|
||||
← The Science of Self-Realization
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Chapter {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -106,7 +113,7 @@ export default async function SsrChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getSsrMeta, getSsrIntroSegments } from "@/lib/ssr";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function SsrPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ssr", name: "The Science of Self-Realization" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -13,12 +13,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -77,6 +80,7 @@ export default async function SvaChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/sva/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Songs of the Vaiṣṇava Ācāryas" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/sva"
|
||||
@@ -84,12 +88,15 @@ export default async function SvaChapterPage({
|
||||
>
|
||||
← Songs of the Vaiṣṇava Ācāryas
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{parentChapterNum != null && (
|
||||
<span className="text-primary">Part {parentChapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -107,7 +114,7 @@ export default async function SvaChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getSvaMeta, getSvaIntroSegments } from "@/lib/sva";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function SvaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/sva", name: "Songs of the Vaiṣṇava Ācāryas" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TlcChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tlc/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Caitanya" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tlc"
|
||||
@@ -77,12 +81,15 @@ export default async function TlcChapterPage({
|
||||
>
|
||||
← Teachings of Lord Caitanya
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TlcChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTlcMeta, getTlcIntroSegments } from "@/lib/tlc";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -25,6 +27,7 @@ export default async function TlcPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tlc", name: "Teachings of Lord Caitanya" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TlkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tlk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tlk"
|
||||
@@ -77,12 +81,15 @@ export default async function TlkChapterPage({
|
||||
>
|
||||
← Teachings of Lord Kapila
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TlkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTlkMeta, getTlkIntroSegments } from "@/lib/tlk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function TlkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tlk", name: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TqkChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/tqk/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Queen Kuntī" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/tqk"
|
||||
@@ -77,12 +81,15 @@ export default async function TqkChapterPage({
|
||||
>
|
||||
← Teachings of Queen Kuntī
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TqkChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTqkMeta, getTqkIntroSegments } from "@/lib/tqk";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -24,6 +26,7 @@ export default async function TqkPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/tqk", name: "Teachings of Queen Kuntī" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -70,6 +73,7 @@ export default async function TtpChapterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/ttp/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/ttp"
|
||||
@@ -77,12 +81,15 @@ export default async function TtpChapterPage({
|
||||
>
|
||||
← Transcendental Teachings of Prahlāda Mahārāja
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{chapterNum != null && (
|
||||
<span className="text-primary">Chapter {chapterNum}: </span>
|
||||
)}
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -100,7 +107,7 @@ export default async function TtpChapterPage({
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTtpMeta } from "@/lib/ttp";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function TtpPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/ttp", name: "Teachings of Lord Kapila" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
@@ -6,18 +6,20 @@ import { notFound } from "next/navigation";
|
||||
import {
|
||||
getVaContent,
|
||||
getVaSidebarItems,
|
||||
type VaChapter,
|
||||
type VaSidebarItem,
|
||||
} from "@/lib/va";
|
||||
import { getProseChapter } from "@/lib/api";
|
||||
import { ogForChapter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { BOOK_NAMES } from "@/lib/constants";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { FindInPage } from "@/components/layout/find-in-page";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metadata
|
||||
@@ -61,10 +63,9 @@ export default async function VaChapterPage({
|
||||
const next =
|
||||
currentIdx < sidebarItems.length - 1 ? sidebarItems[currentIdx + 1] : null;
|
||||
|
||||
const chapterNum = (content as VaChapter).chapter;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/books/va/${slug}`, headline: content.title, description: content.paragraphs?.[0], isPartOf: "Vedānta-sūtra Transcript" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/books/va"
|
||||
@@ -72,9 +73,12 @@ export default async function VaChapterPage({
|
||||
>
|
||||
← Vaiśiṣṭyāṣṭakam
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
<div className="mt-3 flex items-start justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{content.title}
|
||||
</h1>
|
||||
<FindInPage targetId="book-content" className="shrink-0 mt-0.5" />
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div className="max-w-5xl mx-auto flex">
|
||||
@@ -88,7 +92,7 @@ export default async function VaChapterPage({
|
||||
currentSlug={slug}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Card id="book-content">
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{content.paragraphs.map((paragraph, i) => (
|
||||
@@ -5,7 +5,9 @@ import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getVaMeta } from "@/lib/va";
|
||||
import { ogForBook } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { bookJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
@@ -18,6 +20,7 @@ export default async function VaPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={bookJsonLd({ path: "/books/va", name: "Vedānta-sūtra Transcript" })} />
|
||||
<PageHeader>
|
||||
<Link
|
||||
href="/books"
|
||||
+117
-34
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { UserAvatar } from "@/components/ui/user-avatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Search,
|
||||
@@ -32,6 +33,14 @@ export type DiscipleSummary = {
|
||||
isVerified: boolean;
|
||||
};
|
||||
|
||||
/** A disciple who has a platform account (linked by an admin). */
|
||||
export type DiscipleLink = {
|
||||
discipleId: string;
|
||||
handle: string;
|
||||
name: string;
|
||||
avatarURL: string | null;
|
||||
};
|
||||
|
||||
export type LetterCount = { letter: string; count: number };
|
||||
export type PlaceGroup = { name: string; count: number };
|
||||
export type YearGroup = { year: number; count: number };
|
||||
@@ -49,6 +58,9 @@ type Props = {
|
||||
years: YearGroup[];
|
||||
totalCount: number;
|
||||
filteredCount: number;
|
||||
/** Disciples who have joined the platform — their cards are highlighted and
|
||||
* link through to the profile. Small set (dozens at most). */
|
||||
links: DiscipleLink[];
|
||||
};
|
||||
|
||||
type SortMode = "name-asc" | "name-desc" | "date-asc" | "date-desc" | "place";
|
||||
@@ -306,6 +318,97 @@ function Pagination({
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Disciple card
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* One disciple in the grid.
|
||||
*
|
||||
* When `link` is set the disciple has joined the platform, and the card is
|
||||
* highlighted and becomes a link to their profile. The highlight is drawn
|
||||
* entirely from theme tokens (`--primary`, `--ring`) rather than fixed colours,
|
||||
* so it picks up whichever palette the reader has chosen — peacock green under
|
||||
* Tulasī Garden, deep indigo under Yamunā Mist, and so on.
|
||||
*/
|
||||
function DiscipleCard({
|
||||
disciple,
|
||||
link,
|
||||
}: {
|
||||
disciple: DiscipleSummary;
|
||||
link?: DiscipleLink;
|
||||
}) {
|
||||
const locationYear = formatLocationYear(
|
||||
disciple.initiationPlace,
|
||||
disciple.initiationDate,
|
||||
);
|
||||
|
||||
const card = (
|
||||
<Card
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-full transition-all",
|
||||
link
|
||||
? // On the platform: tinted surface + primary ring so it reads as
|
||||
// "one of us" at a glance across the grid.
|
||||
"bg-primary/[0.06] ring-2 ring-primary/40 hover:ring-primary/70 hover:shadow-md"
|
||||
: "hover:shadow-md hover:ring-foreground/20",
|
||||
)}
|
||||
>
|
||||
<CardContent className="py-3 px-4 space-y-1.5">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p
|
||||
className={cn(
|
||||
"font-semibold leading-snug truncate",
|
||||
link ? "text-primary" : "text-foreground",
|
||||
)}
|
||||
>
|
||||
{disciple.initiatedName}
|
||||
</p>
|
||||
{disciple.suffix && (
|
||||
<Badge
|
||||
variant={link ? "default" : "secondary"}
|
||||
className="shrink-0 text-[10px]"
|
||||
>
|
||||
{disciple.suffix}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{locationYear && (
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{locationYear}
|
||||
</p>
|
||||
)}
|
||||
{link && (
|
||||
<div className="flex items-center gap-1.5 pt-0.5">
|
||||
<UserAvatar
|
||||
src={link.avatarURL}
|
||||
name={link.name}
|
||||
className="size-5"
|
||||
textClassName="text-[9px]"
|
||||
/>
|
||||
<span className="truncate text-xs font-medium text-primary">
|
||||
@{link.handle}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
if (!link) return card;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/u/${link.handle}`}
|
||||
aria-label={`${disciple.initiatedName} — view @${link.handle}'s profile`}
|
||||
className="block rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
{card}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -318,6 +421,7 @@ export function DisciplesBrowser({
|
||||
suffixCounts,
|
||||
totalCount,
|
||||
filteredCount,
|
||||
links,
|
||||
}: Props) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [sort, setSort] = useState<SortMode>("name-asc");
|
||||
@@ -334,6 +438,13 @@ export function DisciplesBrowser({
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
// Disciple id → their platform account, for the highlight + profile link.
|
||||
const linkByDiscipleId = useMemo(() => {
|
||||
const map = new Map<string, DiscipleLink>();
|
||||
for (const l of links) map.set(l.discipleId, l);
|
||||
return map;
|
||||
}, [links]);
|
||||
|
||||
// Build a lookup map for letter counts
|
||||
const countByLetter = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
@@ -539,41 +650,13 @@ export function DisciplesBrowser({
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{pageData.map((disciple) => {
|
||||
const locationYear = formatLocationYear(
|
||||
disciple.initiationPlace,
|
||||
disciple.initiationDate,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
{pageData.map((disciple) => (
|
||||
<DiscipleCard
|
||||
key={disciple.id}
|
||||
size="sm"
|
||||
className="transition-all hover:shadow-md hover:ring-foreground/20"
|
||||
>
|
||||
<CardContent className="py-3 px-4 space-y-1.5">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="font-semibold text-foreground leading-snug truncate">
|
||||
{disciple.initiatedName}
|
||||
</p>
|
||||
{disciple.suffix && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="shrink-0 text-[10px]"
|
||||
>
|
||||
{disciple.suffix}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{locationYear && (
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{locationYear}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
disciple={disciple}
|
||||
link={linkByDiscipleId.get(disciple.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { ogForDisciples } from "@/lib/og";
|
||||
import {
|
||||
getTotalDiscipleCount,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getDiscipleYears,
|
||||
getDiscipleSuffixCounts,
|
||||
getFilteredDiscipleCount,
|
||||
getDiscipleLinks,
|
||||
} from "@/lib/disciples";
|
||||
import type { SuffixFilter } from "@/lib/disciples";
|
||||
import { DisciplesBrowser } from "./disciples-browser";
|
||||
@@ -62,6 +63,7 @@ export default async function DisciplesPage({
|
||||
places,
|
||||
years,
|
||||
suffixCounts,
|
||||
links,
|
||||
] = await Promise.all([
|
||||
getTotalDiscipleCount(),
|
||||
getFilteredDisciples(letterFilter, activeSuffix),
|
||||
@@ -70,6 +72,7 @@ export default async function DisciplesPage({
|
||||
getDisciplePlaces(),
|
||||
getDiscipleYears(),
|
||||
getDiscipleSuffixCounts(),
|
||||
getDiscipleLinks(),
|
||||
]);
|
||||
|
||||
// Serialize dates to ISO strings for the client component
|
||||
@@ -105,6 +108,7 @@ export default async function DisciplesPage({
|
||||
years={years}
|
||||
totalCount={totalCount}
|
||||
filteredCount={filteredCount}
|
||||
links={links}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
@@ -4,12 +4,14 @@ import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ogForEssay, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
|
||||
import { MobileNav, type MobileNavGroup } from "@/components/layout/mobile-nav";
|
||||
import { bff, bffList } from "@/lib/bff";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -119,6 +121,7 @@ export default async function EssayPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd data={articleJsonLd({ path: `/essays/${slug}`, headline: essay.title, description: essay.paragraphs?.[0], isPartOf: "Essays & Talks" })} />
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href="/essays"
|
||||
@@ -2,7 +2,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { buildOgMeta } from "@/lib/og";
|
||||
import { bffList } from "@/lib/bff";
|
||||
import { EssaysGrid } from "./essays-grid";
|
||||
@@ -5,6 +5,8 @@ import { notFound } from "next/navigation";
|
||||
import { getAllGlossaryEntries } from "@/lib/api";
|
||||
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
|
||||
import { GlossaryClient } from "../glossary-client";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Diacritic → base letter mapping
|
||||
@@ -145,6 +147,13 @@ export default async function GlossaryLetterPage({
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `/glossary/${rawLetter}`,
|
||||
headline: `Glossary — ${activeLetter}`,
|
||||
isPartOf: "Glossary of Sanskrit & Vedic Terms",
|
||||
})}
|
||||
/>
|
||||
<GlossaryClient
|
||||
entries={clientEntries}
|
||||
activeLetter={activeLetter}
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Search, X, Menu, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ReadingSettings } from "@/components/reading-settings";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
@@ -290,7 +289,6 @@ export function GlossaryClient({
|
||||
letters,
|
||||
letterCounts,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
@@ -389,8 +387,7 @@ export function GlossaryClient({
|
||||
<>
|
||||
{/* Sticky header */}
|
||||
<header className="border-b border-border bg-card px-6 py-4 sticky top-0 z-20">
|
||||
<div className="max-w-5xl mx-auto flex items-start justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
@@ -402,10 +399,6 @@ export function GlossaryClient({
|
||||
{activeEntry?.term || activeLetter}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="shrink-0 pt-1">
|
||||
<ReadingSettings />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Letter navigation row */}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user