Files
community-rule/app/blog/[slug]/page.js
T

148 lines
4.6 KiB
JavaScript

import { notFound } from "next/navigation";
import Link from "next/link";
import { getBlogPostBySlug, getAllPosts } from "../../../lib/contentProcessor";
import ContentThumbnailTemplate from "../../components/ContentThumbnailTemplate";
import ContentBanner from "../../components/ContentBanner";
import { getAssetPath, ASSETS } from "../../../lib/assetUtils";
/**
* Generate static params for all blog posts
* This enables static generation for all blog posts at build time
*/
export async function generateStaticParams() {
try {
const posts = getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
} catch (error) {
console.error("Error generating static params:", error);
return [];
}
}
/**
* Generate metadata for each blog post
*/
export async function generateMetadata({ params }) {
try {
const { slug } = await params;
const post = getBlogPostBySlug(slug);
if (!post) {
return {
title: "Post Not Found",
description: "The requested blog post could not be found.",
};
}
return {
title: post.frontmatter.title,
description: post.frontmatter.description,
authors: [{ name: post.frontmatter.author }],
openGraph: {
title: post.frontmatter.title,
description: post.frontmatter.description,
type: "article",
publishedTime: post.frontmatter.date,
authors: [post.frontmatter.author],
},
};
} catch (error) {
console.error("Error generating metadata:", error);
return {
title: "Blog Post",
description: "A blog post from our community.",
};
}
}
/**
* Dynamic blog post page
*/
export default async function BlogPostPage({ params }) {
// Get the blog post data
const { slug } = await params;
const post = getBlogPostBySlug(slug);
// If post doesn't exist, show 404
if (!post) {
notFound();
}
// Get related posts (for now, just get other posts)
const allPosts = getAllPosts();
const relatedPosts = allPosts.filter((p) => p.slug !== post.slug).slice(0, 3); // Show up to 3 related posts
return (
<div className="min-h-screen bg-[#F4F3F1] relative overflow-hidden">
{/* Content Banner */}
<ContentBanner post={post} />
{/* Decorative Shapes */}
{/* Right Side Shape (3/4 up the page) */}
<div
className="hidden md:block absolute top-1/4 right-0 pointer-events-none z-10"
style={{ transform: "translateX(40%)" }}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={getAssetPath(ASSETS.CONTENT_SHAPE_1)}
alt=""
className="w-auto h-auto max-w-none"
style={{
width: "clamp(120px, 15vw, 200px)",
height: "auto",
}}
/>
</div>
{/* Left Side Shape (3/4 down the page) */}
<div
className="hidden md:block absolute top-1/2 left-0 pointer-events-none z-10"
style={{ transform: "translateX(-10%)" }}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={getAssetPath(ASSETS.CONTENT_SHAPE_2)}
alt=""
className="w-auto h-auto max-w-none"
style={{
width: "clamp(100px, 12vw, 180px)",
height: "auto",
}}
/>
</div>
{/* Main Content */}
<article className="p-[var(--spacing-scale-024)] sm:py-[var(--spacing-scale-032)]">
{/* Article Content */}
<div className="post-body -mt-[var(--spacing-scale-048)] text-[var(--color-content-inverse-primary)] text-[16px] leading-[24px] sm:text-[18px] sm:leading-[130%] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] sm:mx-auto sm:max-w-[390px] md:max-w-[472px] lg:max-w-[700px] xl:max-w-[904px]">
<div dangerouslySetInnerHTML={{ __html: post.htmlContent }} />
</div>
</article>
{/* Related Posts Section */}
{relatedPosts.length > 0 && (
<section className="bg-white border-t border-gray-200">
<div className="max-w-6xl mx-auto px-4 py-12">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center">
Related Articles
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{relatedPosts.map((relatedPost) => (
<ContentThumbnailTemplate
key={relatedPost.slug}
post={relatedPost}
variant="vertical"
/>
))}
</div>
</div>
</section>
)}
</div>
);
}