Files
fullstack-portfolio/src/app/(portfolio)/blog/[slug]/page.tsx

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>
);
}