- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
124 lines
5.9 KiB
TypeScript
124 lines
5.9 KiB
TypeScript
import React from "react";
|
||
import { ChevronRight, Calendar, User, Sparkles } from "lucide-react";
|
||
import Link from 'next/link';
|
||
import type { Metadata } from 'next';
|
||
|
||
async function getBlog(slug: string) {
|
||
try {
|
||
const res = await fetch(`http://localhost:4001/api/blogs/${slug}`, { next: { revalidate: 60 } });
|
||
if (!res.ok) throw new Error('Failed to fetch blog');
|
||
const b = await res.json();
|
||
return {
|
||
id: b.id,
|
||
slug: b.slug,
|
||
title: b.title,
|
||
excerpt: b.metaDescription || b.content.substring(0, 150).replace(/<[^>]+>/g, '') + '...',
|
||
image: b.imageUrl,
|
||
date: new Date(b.createdAt).toLocaleDateString('fa-IR'),
|
||
author: b.author ? `${b.author.firstName} ${b.author.lastName}` : 'نویسنده کانینا',
|
||
content: b.content
|
||
};
|
||
} catch (error) {
|
||
console.error(error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
|
||
const blog = await getBlog(params.slug);
|
||
if (!blog) return { title: 'مقاله یافت نشد' };
|
||
|
||
return {
|
||
title: blog.title,
|
||
description: blog.excerpt,
|
||
};
|
||
}
|
||
|
||
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
|
||
const selectedPost = await getBlog(params.slug);
|
||
|
||
if (!selectedPost) {
|
||
return (
|
||
<div className="min-h-[50vh] flex flex-col items-center justify-center font-vazir text-center" dir="rtl">
|
||
<h1 className="text-3xl font-black text-medical-gray-900 mb-4">مقالهای یافت نشد</h1>
|
||
<Link href="/blog" className="text-canina-blue font-bold flex items-center gap-2">
|
||
<ChevronRight className="w-4 h-4" />
|
||
بازگشت به مجله سلامت
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-medical-gray-50 pt-8 pb-20 px-4 font-vazir" dir="rtl">
|
||
<div className="max-w-7xl mx-auto">
|
||
{/* Breadcrumbs */}
|
||
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
|
||
<Link href="/" className="hover:text-canina-blue transition-colors">خانه</Link>
|
||
<ChevronRight className="w-2.5 h-2.5" />
|
||
<Link href="/blog" className="hover:text-canina-blue transition-colors">مجله سلامت</Link>
|
||
<ChevronRight className="w-2.5 h-2.5" />
|
||
<span className="text-canina-blue">{selectedPost.title}</span>
|
||
</div>
|
||
|
||
<div className="grid lg:grid-cols-12 gap-12">
|
||
<div className="lg:col-span-8">
|
||
<div className="bg-white rounded-[3.5rem] border border-medical-gray-200 shadow-2xl overflow-hidden">
|
||
<div className="aspect-video w-full overflow-hidden">
|
||
<img src={selectedPost.image} alt={selectedPost.title} className="w-full h-full object-cover" />
|
||
</div>
|
||
<div className="p-12">
|
||
<div className="flex items-center gap-4 text-xs font-bold text-canina-blue mb-8">
|
||
<div className="bg-canina-blue/10 px-4 py-1.5 rounded-full uppercase tracking-widest">تحقیق بالینی</div>
|
||
<div className="flex items-center gap-1">
|
||
<Calendar className="w-4 h-4" />
|
||
{selectedPost.date}
|
||
</div>
|
||
</div>
|
||
<h1 className="text-4xl lg:text-5xl font-black text-medical-gray-900 mb-10 leading-tight">
|
||
{selectedPost.title}
|
||
</h1>
|
||
<div className="prose prose-lg max-w-none text-medical-gray-600 leading-[2] text-justify space-y-6">
|
||
<p className="font-bold text-medical-gray-900 italic text-xl border-r-4 border-canina-blue pr-6 py-2">
|
||
{selectedPost.excerpt}
|
||
</p>
|
||
<div className="whitespace-pre-line" dangerouslySetInnerHTML={{ __html: selectedPost.content }} />
|
||
</div>
|
||
|
||
<div className="mt-16 pt-10 border-t border-medical-gray-100 flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-14 h-14 bg-medical-gray-100 rounded-full flex items-center justify-center text-medical-gray-400 border-2 border-white shadow-lg">
|
||
<User className="w-8 h-8" />
|
||
</div>
|
||
<div>
|
||
<div className="font-black text-medical-gray-900 text-lg">{selectedPost.author}</div>
|
||
<div className="font-bold text-medical-gray-400">نویسنده کانینا</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-4">
|
||
<button className="p-4 bg-medical-gray-100 rounded-2xl hover:bg-canina-blue hover:text-white transition-all text-medical-gray-400">
|
||
<Sparkles className="w-6 h-6" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="lg:col-span-4 space-y-10">
|
||
<div className="bg-canina-blue rounded-[3rem] p-10 text-white shadow-2xl relative overflow-hidden">
|
||
<div className="absolute top-0 left-0 p-8 opacity-10">
|
||
<Sparkles className="w-20 h-20" />
|
||
</div>
|
||
<h4 className="text-2xl font-black mb-6 italic relative z-10">ارتباط با کارشناسان</h4>
|
||
<div className="space-y-6 relative z-10 text-white/90">
|
||
برای دریافت مشاوره تخصصی و انتخاب بهترین مکمل برای حیوان خانگی خود، با ما در تماس باشید.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|