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>
This commit is contained in:
174
app/posts/[slug]/page.tsx
Normal file
174
app/posts/[slug]/page.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user