90 lines
3.9 KiB
TypeScript
90 lines
3.9 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import { notFound } from "next/navigation";
|
|
import { format } from "date-fns";
|
|
import Link from "next/link";
|
|
import { ChevronLeft, Calendar, Clock, Tag as TagIcon, Share2 } from "lucide-react";
|
|
|
|
export default async function BlogDetailPage({
|
|
params
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const { slug } = await params;
|
|
const blog = await prisma.blog.findUnique({
|
|
where: { slug },
|
|
include: { tags: true },
|
|
});
|
|
|
|
if (!blog || blog.status !== "PUBLISHED") {
|
|
notFound();
|
|
}
|
|
|
|
return (
|
|
<article className="min-h-screen pt-32 pb-20 bg-background overflow-hidden relative">
|
|
{/* Decorative Background Elements */}
|
|
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-primary/3 rounded-full blur-[120px] -z-10" />
|
|
|
|
<div className="max-w-3xl mx-auto px-6">
|
|
<Link
|
|
href="/blog"
|
|
className="inline-flex items-center gap-2 text-sm text-foreground-muted hover:text-primary mb-8 transition-colors group"
|
|
>
|
|
<ChevronLeft size={16} className="group-hover:-translate-x-1 transition-transform" />
|
|
Back to all posts
|
|
</Link>
|
|
|
|
<div className="space-y-6 mb-12">
|
|
<div className="flex flex-wrap items-center gap-4">
|
|
<div className="flex items-center gap-1.5 text-xs text-foreground-muted bg-surface-elevated/50 px-3 py-1.5 rounded-full border border-border/50">
|
|
<Calendar size={12} className="text-primary/60" />
|
|
{blog.publishedAt ? format(new Date(blog.publishedAt), "MMMM dd, yyyy") : "Published"}
|
|
</div>
|
|
<div className="flex items-center gap-1.5 text-xs text-foreground-muted bg-surface-elevated/50 px-3 py-1.5 rounded-full border border-border/50">
|
|
<Clock size={12} className="text-primary/60" />
|
|
6 min read
|
|
</div>
|
|
</div>
|
|
|
|
<h1 className="text-4xl md:text-5xl font-display font-bold text-foreground leading-tight tracking-tight">
|
|
{blog.title}
|
|
</h1>
|
|
|
|
<div className="flex items-center justify-between py-6 border-y border-border/50">
|
|
<div className="flex flex-wrap gap-2">
|
|
{blog.tags.map((tag) => (
|
|
<span key={tag.id} className="text-[10px] font-bold uppercase tracking-widest text-primary bg-primary/5 px-2.5 py-1 rounded-md border border-primary/10">
|
|
{tag.name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<button className="p-2 text-foreground-muted hover:text-primary transition-colors">
|
|
<Share2 size={18} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content Section */}
|
|
<div className="prose prose-slate dark:prose-invert max-w-none prose-pre:bg-surface prose-pre:border prose-pre:border-border/50 prose-pre:rounded-2xl prose-code:text-primary prose-a:text-primary hover:prose-a:underline font-serif text-lg leading-relaxed text-foreground/90">
|
|
{/* Using a simple div for content here. In a real app we'd use a markdown renderer like react-markdown */}
|
|
<div className="whitespace-pre-wrap">
|
|
{blog.content}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Author Footer */}
|
|
<div className="mt-20 p-8 bg-surface/50 border border-border/50 rounded-3xl flex items-center gap-6">
|
|
<div className="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center shrink-0 border border-primary/20">
|
|
<TagIcon size={24} className="text-primary" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-semibold text-foreground">Written by Fikri</p>
|
|
<p className="text-xs text-foreground-muted mt-1 leading-relaxed">
|
|
Fullstack engineer passionate about building beautiful, functional, and user-centric web applications.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|