81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
import Link from 'next/link'
|
|
import { notFound } from 'next/navigation'
|
|
import type { Metadata } from 'next'
|
|
import { ArrowLeft } from 'lucide-react'
|
|
import type { Article } from '@homaroma/types'
|
|
|
|
import { API_BASE } from '../../../lib/api'
|
|
|
|
async function getArticle(slug: string): Promise<Article | null> {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/articles/${slug}`, { next: { revalidate: 300 } })
|
|
return res.ok ? res.json() : null
|
|
} catch { return null }
|
|
}
|
|
|
|
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
|
|
const { slug } = await params
|
|
const article = await getArticle(slug)
|
|
if (!article) return { title: 'Article Not Found | HomAroma' }
|
|
return {
|
|
title: `${article.title} | HomAroma Scent Guide`,
|
|
description: article.content.replace(/<[^>]+>/g, '').slice(0, 160),
|
|
openGraph: {
|
|
title: article.title,
|
|
images: article.coverImage ? [article.coverImage] : [],
|
|
type: 'article',
|
|
publishedTime: article.createdAt,
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function ArticleReaderPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params
|
|
const article = await getArticle(slug)
|
|
|
|
if (!article) notFound()
|
|
|
|
return (
|
|
<div className="min-h-screen flex flex-col bg-night-950">
|
|
<main className="flex-1 pt-24 pb-20">
|
|
<article className="max-w-4xl mx-auto w-full animate-fade-in relative mt-8">
|
|
|
|
{/* Desktop back button */}
|
|
<Link
|
|
href="/scent-guide"
|
|
className="absolute -left-16 xl:-left-24 top-0 mt-8 hidden lg:flex p-4 glass border border-white/5 rounded-full hover:bg-white/10 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-5 h-5 text-slate-300" />
|
|
</Link>
|
|
|
|
{/* Header */}
|
|
<div className="text-center px-6 py-12 space-y-6">
|
|
<Link href="/scent-guide" className="lg:hidden text-slate-400 text-sm hover:text-white inline-flex items-center gap-2 mb-4">
|
|
<ArrowLeft className="w-4 h-4" /> Go Back
|
|
</Link>
|
|
<p className="text-cyan-glow uppercase tracking-[0.3em] font-bold text-xs">
|
|
{new Date(article.createdAt).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
|
|
</p>
|
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight max-w-3xl mx-auto font-serif">
|
|
{article.title}
|
|
</h1>
|
|
</div>
|
|
|
|
{/* Cover Image */}
|
|
{article.coverImage && (
|
|
<div className="w-full aspect-[16/7] bg-night-900 md:rounded-[2rem] overflow-hidden shadow-2xl mb-16 border border-white/5">
|
|
<img src={article.coverImage} alt={article.title} className="w-full h-full object-cover" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Content Body */}
|
|
<div className="px-6 md:px-12 prose prose-invert prose-lg prose-cyan mx-auto max-w-3xl pb-16">
|
|
<div dangerouslySetInnerHTML={{ __html: article.content }} className="space-y-6" />
|
|
</div>
|
|
|
|
</article>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|