Files
chorus-ping-blog/app/posts/[slug]/page.tsx
anthonyrawlins 6e13451dc4 Initial commit: CHORUS PING! blog
- Next.js 14 blog application with theme support
- Docker containerization with volume bindings
- Traefik integration with Let's Encrypt SSL
- MDX support for blog posts
- Theme toggle with localStorage persistence
- Scheduled posts directory structure
- Brand guidelines compliance with CHORUS colors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 14:46:26 +10:00

174 lines
5.7 KiB
TypeScript

import { notFound } from 'next/navigation'
import { MDXRemote } from 'next-mdx-remote/rsc'
import { getPostData, getAllPostSlugs } from '@/lib/blog'
import BlogHeader from '@/components/BlogHeader'
import BlogFooter from '@/components/BlogFooter'
import Link from 'next/link'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
interface PostPageProps {
params: Promise<{
slug: string
}>
}
export async function generateStaticParams() {
const slugs = getAllPostSlugs()
return slugs.map(({ params }) => ({ slug: params.slug }))
}
export async function generateMetadata({ params }: PostPageProps) {
const { slug } = await params
const post = getPostData(slug)
if (!post) {
return {
title: 'Post Not Found - CHORUS PING!'
}
}
return {
title: `${post.title} - CHORUS PING!`,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: 'article',
publishedTime: post.date,
authors: [post.author?.name || 'CHORUS Team'],
tags: post.tags,
},
}
}
export default async function PostPage({ params }: PostPageProps) {
const { slug } = await params
const post = getPostData(slug)
if (!post) {
notFound()
}
const formattedDate = new Date(post.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
const mdxOptions = {
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeHighlight,
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: 'wrap' }]
],
}
}
return (
<main className="min-h-screen">
<BlogHeader />
<article className="blog-container py-12">
{/* Back link */}
<div className="mb-8">
<Link
href="/"
className="inline-flex items-center text-carbon-600 dark:text-carbon-400 hover:text-carbon-950 dark:hover:text-carbon-200 transition-colors text-sm group"
>
<svg
className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Back to all posts
</Link>
</div>
{/* Article header */}
<header className="mb-12 max-w-4xl">
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.map((tag) => (
<span key={tag} className="blog-tag">
{tag}
</span>
))}
</div>
<h1 className="text-h1 font-logo text-carbon-950 dark:text-mulberry-100 mb-6">
{post.title}
</h1>
<p className="text-xl text-carbon-700 dark:text-carbon-300 leading-relaxed mb-8">
{post.description}
</p>
<div className="flex items-center justify-between border-b border-carbon-300 dark:border-carbon-800 pb-8">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 bg-gradient-to-br from-mulberry-400 to-ocean-500 rounded-full flex items-center justify-center">
<span className="text-carbon-950 font-semibold">
{post.author.name.charAt(0)}
</span>
</div>
<div>
<p className="text-carbon-950 dark:text-carbon-200 font-medium">
{post.author.name}
</p>
{post.author.role && (
<p className="text-carbon-600 dark:text-carbon-500 text-sm">
{post.author.role}
</p>
)}
</div>
</div>
<div className="blog-meta text-right">
<time dateTime={post.date} className="block">
{formattedDate}
</time>
<span className="text-carbon-600 dark:text-carbon-600">
{post.readingTime} min read
</span>
</div>
</div>
</header>
{/* Article content */}
<div className="prose prose-lg max-w-none">
<MDXRemote source={post.content} options={mdxOptions} />
</div>
{/* Article footer */}
<footer className="mt-16 pt-8 border-t border-carbon-300 dark:border-carbon-800">
<div className="text-center">
<h3 className="text-h5 font-logo text-carbon-950 dark:text-carbon-100 mb-4">
Join the CHORUS Community
</h3>
<p className="text-carbon-600 dark:text-carbon-400 mb-6 max-w-2xl mx-auto">
Stay updated with the latest insights on contextual AI and agent orchestration.
Join our waitlist to get early access to the CHORUS platform.
</p>
<Link
href="https://chorus.services"
className="inline-flex items-center px-6 py-3 bg-carbon-900 dark:bg-mulberry-700 hover:bg-carbon-800 dark:hover:bg-mulberry-600 text-white dark:text-mulberry-100 rounded-lg font-medium transition-colors"
>
Join Waitlist
<svg className="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
</div>
</footer>
</article>
<BlogFooter />
</main>
)
}