- 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>
87 lines
3.3 KiB
TypeScript
87 lines
3.3 KiB
TypeScript
import Link from 'next/link'
|
|
import { BlogPost } from '@/types/blog'
|
|
|
|
interface PostCardProps {
|
|
post: BlogPost
|
|
featured?: boolean
|
|
}
|
|
|
|
export default function PostCard({ post, featured = false }: PostCardProps) {
|
|
const formattedDate = new Date(post.date).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
})
|
|
|
|
return (
|
|
<article className={`group ${
|
|
featured
|
|
? 'bg-carbon-50 dark:bg-carbon-900 border border-carbon-200 dark:border-carbon-800 rounded-xl p-8 hover:border-carbon-400 dark:hover:border-mulberry-700 transition-all duration-300'
|
|
: 'bg-carbon-100/50 dark:bg-carbon-900/50 border border-carbon-200/50 dark:border-carbon-800/50 rounded-lg p-6 hover:bg-carbon-50 dark:hover:bg-carbon-900 hover:border-carbon-300 dark:hover:border-carbon-700 transition-all duration-300'
|
|
}`}>
|
|
<Link href={`/posts/${post.slug}`} className="block">
|
|
<div className="mb-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="blog-meta">
|
|
<time dateTime={post.date} className="text-carbon-600 dark:text-carbon-500">
|
|
{formattedDate}
|
|
</time>
|
|
<span className="text-carbon-500 dark:text-carbon-600">•</span>
|
|
<span className="text-carbon-600 dark:text-carbon-500">
|
|
{post.readingTime} min read
|
|
</span>
|
|
</div>
|
|
{post.featured && (
|
|
<span className="blog-tag bg-mulberry-700 text-mulberry-200">
|
|
Featured
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<h2 className={`${
|
|
featured ? 'text-h3' : 'text-h4'
|
|
} font-logo text-carbon-950 dark:text-carbon-100 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors mb-3`}>
|
|
{post.title}
|
|
</h2>
|
|
|
|
<p className="text-carbon-700 dark:text-carbon-300 leading-relaxed mb-4 line-clamp-3">
|
|
{post.description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-8 h-8 bg-gradient-to-br from-mulberry-400 to-ocean-500 rounded-full flex items-center justify-center">
|
|
<span className="text-carbon-950 font-semibold text-xs">
|
|
{post.author?.name?.charAt(0) || 'C'}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<p className="text-carbon-950 dark:text-carbon-200 font-medium text-sm">
|
|
{post.author?.name || 'CHORUS Team'}
|
|
</p>
|
|
{post.author?.role && (
|
|
<p className="text-carbon-600 dark:text-carbon-500 text-xs">
|
|
{post.author.role}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
{post.tags?.slice(0, 3).map((tag) => (
|
|
<span key={tag} className="blog-tag">
|
|
{tag}
|
|
</span>
|
|
)) || []}
|
|
{(post.tags?.length || 0) > 3 && (
|
|
<span className="text-carbon-600 dark:text-carbon-500 text-xs">
|
|
+{(post.tags?.length || 0) - 3} more
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</article>
|
|
)
|
|
} |