chore: refactor settings route
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getLetter, getAdjacentLetters } from "@/lib/letters";
|
||||
import { ogForLetter, extractQuoteFromHl, buildOgMeta } from "@/lib/og";
|
||||
import { PageHeader } from "@/components/layout/page-header";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { JsonLd } from "@/components/seo/json-ld";
|
||||
import { articleJsonLd } from "@/lib/jsonld";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const sp = await searchParams;
|
||||
const letter = await getLetter(slug);
|
||||
if (!letter) return {};
|
||||
|
||||
const meta = [letter.date, letter.location].filter(Boolean).join(" · ");
|
||||
const hlQuote = extractQuoteFromHl(sp, letter.paragraphs);
|
||||
|
||||
if (hlQuote) {
|
||||
return buildOgMeta({
|
||||
title: `Letter to ${letter.recipientDisplay}`,
|
||||
detail: meta || undefined,
|
||||
quote: hlQuote,
|
||||
});
|
||||
}
|
||||
|
||||
return ogForLetter(letter.recipientDisplay, meta || undefined);
|
||||
}
|
||||
|
||||
export default async function LetterPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
|
||||
const letter = await getLetter(slug);
|
||||
if (!letter) notFound();
|
||||
|
||||
const { prev, next } = await getAdjacentLetters(slug, letter.year);
|
||||
|
||||
const backHref = letter.year ? `/letters/year/${letter.year}` : "/letters";
|
||||
const backLabel = letter.year ? `Letters from ${letter.year}` : "Letters";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
path: `/letters/${slug}`,
|
||||
headline: `Letter to ${letter.recipientDisplay}`,
|
||||
description: letter.paragraphs[0],
|
||||
datePublished: letter.year ? String(letter.year) : undefined,
|
||||
isPartOf: "Letters of Śrīla Prabhupāda",
|
||||
})}
|
||||
/>
|
||||
<PageHeader sticky>
|
||||
<Link
|
||||
href={backHref}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
← {backLabel}
|
||||
</Link>
|
||||
<h1 className="mt-3 text-2xl font-bold text-foreground">
|
||||
Letter to {letter.recipientDisplay}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{letter.date}
|
||||
{letter.location ? ` · ${letter.location}` : ""}
|
||||
</p>
|
||||
</PageHeader>
|
||||
|
||||
<main className="max-w-5xl mx-auto px-6 py-8 space-y-8">
|
||||
{/* Top nav */}
|
||||
<LetterNav prev={prev} next={next} />
|
||||
|
||||
{/* Letter content */}
|
||||
<Card>
|
||||
<CardContent className="py-8 px-6 sm:px-8">
|
||||
<div className="space-y-4">
|
||||
{letter.paragraphs.map((paragraph, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-base text-foreground leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: paragraph }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Bottom nav */}
|
||||
<LetterNav prev={prev} next={next} />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetterNav({
|
||||
prev,
|
||||
next,
|
||||
}: {
|
||||
prev: { slug: string; recipientDisplay: string } | null;
|
||||
next: { slug: string; recipientDisplay: string } | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{prev ? (
|
||||
<Link
|
||||
href={`/letters/${prev.slug}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline", size: "sm" }),
|
||||
"gap-1.5",
|
||||
)}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
<span className="hidden sm:inline truncate max-w-[180px]">
|
||||
{prev.recipientDisplay}
|
||||
</span>
|
||||
<span className="sm:hidden">Previous</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{next ? (
|
||||
<Link
|
||||
href={`/letters/${next.slug}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline", size: "sm" }),
|
||||
"gap-1.5",
|
||||
)}
|
||||
>
|
||||
<span className="hidden sm:inline truncate max-w-[180px]">
|
||||
{next.recipientDisplay}
|
||||
</span>
|
||||
<span className="sm:hidden">Next</span>
|
||||
<ChevronRight className="size-4" />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user