This repository has been archived on 2026-09-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
confessions-of-grace/app/posts/[id]/page.tsx
T
2025-08-19 07:16:53 -05:00

163 lines
4.9 KiB
TypeScript

import ShareButtons from "@/components/ShareButtons";
import { getPostData, getSortedPostsData } from "@/lib/markdown";
import { PostData, PostMetadata } from "@/types";
import { format } from "date-fns";
import Image from "next/image";
import Link from "next/link";
import React from "react";
import CommentSection from "@/components/CommentSection";
import { generateMetadata as createMetadata } from "@/components/Metadata";
import type { Metadata } from "next";
interface PageProps {
params: Promise<{ id: string }>;
}
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { id } = await params;
const post = await getPostData(id);
const postUrl = `https://confessionsofgrace.com/posts/${post.id}`;
return createMetadata({
title: post.title,
description: post.excerpt,
keywords: post.tags.join(", "),
image: post.coverImage,
url: postUrl,
type: "article",
});
}
async function getPostAndMorePosts(
id: string,
): Promise<{ post: PostData; morePosts: PostMetadata[] }> {
const post = await getPostData(id);
const allPosts = getSortedPostsData();
// Filter out the current post and get a few related posts (by tags)
const otherPosts = allPosts.filter((p) => p.id !== post.id);
// Find posts with matching tags
const relatedPosts = otherPosts
.filter((p) => p.tags.some((tag) => post.tags.includes(tag)))
.slice(0, 2);
// If we don't have enough related posts, add recent posts
const morePosts =
relatedPosts.length < 2
? [
...relatedPosts,
...otherPosts.filter((p) => !relatedPosts.includes(p)),
].slice(0, 2)
: relatedPosts;
return { post, morePosts };
}
export default async function PostPage({ params }: PageProps) {
const { id } = await params;
const { post, morePosts } = await getPostAndMorePosts(id);
const postUrl = `https://confessionsofgrace.com/posts/${post.id}`;
return (
<article className="max-w-3xl mx-auto">
<header className="mb-8">
<h1 className="text-3xl md:text-4xl font-bold mb-2">{post.title}</h1>
<div className="flex items-center text-primary-500 mb-6">
<span>{post.author}</span>
<span className="mx-2"></span>
<time dateTime={post.date}>
{format(new Date(post.date), "MMMM d, yyyy")}
</time>
</div>
{post.coverImage && (
<div className="relative h-64 md:h-96 w-full mb-8 rounded-lg overflow-hidden">
<Image
src={post.coverImage}
alt={post.title}
fill
className="object-cover"
priority
/>
</div>
)}
<div className="flex flex-wrap gap-2 mb-6">
{post.tags.map((tag) => (
<Link
key={tag}
href={`/tags/${tag}`}
className="text-sm bg-primary-100 text-primary-600 px-3 py-1 rounded-md hover:bg-primary-200"
>
{tag}
</Link>
))}
</div>
</header>
<div
className="blog-post prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
<div className="mt-8 pt-6 border-t border-primary-200">
<ShareButtons
url={postUrl}
title={post.title}
description={post.excerpt}
/>
</div>
<div className="mt-12 pt-6 border-t border-primary-200">
<h2 className="text-2xl font-bold mb-6">More Posts</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{morePosts.slice(0, 2).map((post) => (
<div
key={post.id}
className="bg-white p-6 rounded-md border border-primary-200 shadow-sm"
>
<h3 className="font-bold mb-2">
<Link
href={`/posts/${post.id}`}
className="hover:text-accent-dark"
>
{post.title}
</Link>
</h3>
<p className="text-primary-600 text-sm mb-2">
{format(new Date(post.date), "MMMM d, yyyy")}
</p>
<p className="text-primary-700">{post.excerpt}</p>
</div>
))}
</div>
</div>
<CommentSection postId={post.id} />
<div className="mt-12 pt-6 border-t border-primary-200">
<Link
href="/public"
className="text-accent-dark hover:text-accent inline-flex items-center"
>
<svg
className="mr-2 h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M10 19l-7-7m0 0l7-7m-7 7h18"
/>
</svg>
Back to all posts
</Link>
</div>
</article>
);
}