218 lines
9.2 KiB
TypeScript
218 lines
9.2 KiB
TypeScript
import Link from 'next/link';
|
||
import { ChevronRight, Sparkles, Package, ArrowLeft, ShieldCheck } from 'lucide-react';
|
||
import type { Metadata } from 'next';
|
||
import sanitizeHtml from 'sanitize-html';
|
||
import { getSeoConfig, formatPageTitle } from "../../../lib/seo";
|
||
import { generateMedicalWebPageSchema, generateBreadcrumbSchema, safeJsonLd } from "../../../lib/schema";
|
||
import SafeImage from "../../../components/SafeImage";
|
||
|
||
const API_URL = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4001/api';
|
||
const apiBase = API_URL.endsWith('/api') ? API_URL : `${API_URL.replace(/\/$/, '')}/api`;
|
||
|
||
async function getWikiTerm(key: string) {
|
||
try {
|
||
const res = await fetch(`${apiBase}/wiki/${encodeURIComponent(key)}`, {
|
||
next: { revalidate: 3600, tags: ['wiki', `wiki-${key}`] }
|
||
});
|
||
if (!res.ok) return null;
|
||
return await res.json();
|
||
} catch (error) {
|
||
console.error('[Wiki] Error fetching term:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function getRelatedProducts(termName: string) {
|
||
try {
|
||
const res = await fetch(`${apiBase}/products?search=${encodeURIComponent(termName)}&limit=4`, {
|
||
next: { revalidate: 3600, tags: ['products'] },
|
||
});
|
||
if (!res.ok) return [];
|
||
const json = await res.json();
|
||
return json.data || json || [];
|
||
} catch (error) {
|
||
console.error('[Wiki] Error fetching related products for ingredient:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
|
||
const { slug } = await params;
|
||
const term = await getWikiTerm(slug);
|
||
const config = await getSeoConfig();
|
||
|
||
if (!term) {
|
||
return {
|
||
title: formatPageTitle('ترکیب علمی یافت نشد', config.brandNameFa, config.titleSeparator, config.titlePosition),
|
||
};
|
||
}
|
||
|
||
const rawTitle = term.metaTitle || `ترکیب دارویی و خواص ${term.term}`;
|
||
const title = formatPageTitle(rawTitle, config.brandNameFa, config.titleSeparator, config.titlePosition, false);
|
||
const description =
|
||
term.metaDescription ||
|
||
(term.definition ? term.definition.replace(/<[^>]+>/g, '').substring(0, 155).trim() : '') ||
|
||
`بررسی خواص بالینی، فواید و مکملهای دارویی حاوی ${term.term} در دامپزشکی کنینا آلمان.`;
|
||
const canonicalUrl = `${config.canonicalBaseUrl.replace(/\/$/, '')}/wiki/${slug}`;
|
||
|
||
return {
|
||
title,
|
||
description,
|
||
alternates: {
|
||
canonical: canonicalUrl,
|
||
},
|
||
openGraph: {
|
||
title,
|
||
description,
|
||
url: canonicalUrl,
|
||
siteName: config.brandNameFa,
|
||
type: "article",
|
||
},
|
||
twitter: {
|
||
card: "summary",
|
||
title,
|
||
description,
|
||
},
|
||
};
|
||
}
|
||
|
||
export default async function WikiTermPage({ params }: { params: Promise<{ slug: string }> }) {
|
||
const { slug } = await params;
|
||
const term = await getWikiTerm(slug);
|
||
|
||
if (!term) {
|
||
return (
|
||
<div className="min-h-[50vh] flex flex-col items-center justify-center font-vazir text-center px-4" dir="rtl">
|
||
<h1 className="text-3xl font-black text-medical-gray-900 mb-4">ترکیب علمی یافت نشد</h1>
|
||
<Link href="/wiki" className="text-canina-blue font-bold flex items-center gap-2">
|
||
<ChevronRight className="w-4 h-4" />
|
||
بازگشت به دانشنامه
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const relatedProducts = await getRelatedProducts(term.term);
|
||
|
||
const medicalSchema = generateMedicalWebPageSchema({
|
||
title: `ترکیب درمانی ${term.term} - دانشنامه تخصصی کنینا`,
|
||
description: term.metaDescription || (term.definition ? term.definition.replace(/<[^>]+>/g, '').substring(0, 160) : ''),
|
||
url: `https://canina.ir/wiki/${slug}`,
|
||
aspects: [term.term, 'Veterinary Supplements', 'Pet Nutrition'],
|
||
});
|
||
|
||
const breadcrumbs = generateBreadcrumbSchema([
|
||
{ name: 'صفحه اصلی', url: 'https://canina.ir' },
|
||
{ name: 'دانشنامه ترکیبات', url: 'https://canina.ir/wiki' },
|
||
{ name: term.term, url: `https://canina.ir/wiki/${slug}` },
|
||
]);
|
||
|
||
return (
|
||
<>
|
||
<script
|
||
type="application/ld+json"
|
||
dangerouslySetInnerHTML={{ __html: safeJsonLd(medicalSchema) }}
|
||
/>
|
||
<script
|
||
type="application/ld+json"
|
||
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbs) }}
|
||
/>
|
||
|
||
<div className="bg-medical-gray-50 min-h-screen py-12 px-4 sm:px-6 lg:px-8 font-vazir" dir="rtl">
|
||
<div className="max-w-4xl mx-auto space-y-8">
|
||
|
||
{/* Breadcrumb back link */}
|
||
<nav className="flex items-center gap-2 text-xs font-bold text-medical-gray-500">
|
||
<Link href="/" className="hover:text-canina-blue transition-colors">صفحه اصلی</Link>
|
||
<ChevronRight className="w-3.5 h-3.5 text-medical-gray-300" />
|
||
<Link href="/wiki" className="hover:text-canina-blue transition-colors">دانشنامه ترکیبات</Link>
|
||
<ChevronRight className="w-3.5 h-3.5 text-medical-gray-300" />
|
||
<span className="text-canina-blue">{term.term}</span>
|
||
</nav>
|
||
|
||
{/* Main Article Container */}
|
||
<article className="bg-white border border-medical-gray-200 rounded-[3rem] p-8 md:p-12 shadow-xl space-y-8">
|
||
<div className="space-y-3 border-b border-medical-gray-100 pb-6">
|
||
<span className="inline-flex items-center gap-1.5 px-3 py-1 bg-canina-blue/5 border border-canina-blue/10 rounded-full text-canina-blue text-xs font-black">
|
||
<Sparkles className="w-3.5 h-3.5" />
|
||
ماده موثره و ترکیب بالینی
|
||
</span>
|
||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-black text-medical-gray-900 leading-tight">
|
||
{term.term}
|
||
</h1>
|
||
</div>
|
||
|
||
<div
|
||
className="prose prose-lg max-w-none text-medical-gray-700 leading-relaxed prose-headings:font-black prose-headings:text-medical-gray-900 prose-p:leading-loose"
|
||
dangerouslySetInnerHTML={{
|
||
__html: sanitizeHtml(term.definition || '', {
|
||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2', 'span']),
|
||
allowedAttributes: {
|
||
...sanitizeHtml.defaults.allowedAttributes,
|
||
img: ['src', 'alt', 'title', 'width', 'height', 'loading'],
|
||
span: ['class', 'style'],
|
||
div: ['class'],
|
||
p: ['class'],
|
||
},
|
||
}),
|
||
}}
|
||
/>
|
||
</article>
|
||
|
||
{/* Related Products: Bidirectional Internal Linking */}
|
||
{relatedProducts && relatedProducts.length > 0 && (
|
||
<section className="bg-gradient-to-br from-white to-blue-50/50 border border-medical-gray-200 rounded-[2.5rem] p-8 shadow-lg space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<span className="text-xs font-black text-canina-blue uppercase tracking-wider">
|
||
مکملهای فرموله شده با این ماده
|
||
</span>
|
||
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 mt-1">
|
||
محصولات کنینا حاوی {term.term}
|
||
</h2>
|
||
</div>
|
||
<Link
|
||
href={`/shop?search=${encodeURIComponent(term.term)}`}
|
||
className="text-xs font-bold text-canina-blue hover:underline flex items-center gap-1"
|
||
>
|
||
مشاهده همه در فروشگاه
|
||
<ArrowLeft className="w-3.5 h-3.5" />
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="grid sm:grid-cols-2 gap-4">
|
||
{relatedProducts.map((p: any) => (
|
||
<Link
|
||
key={p.id}
|
||
href={`/shop/${p.slug || p.artNo}`}
|
||
className="bg-white border border-medical-gray-200 rounded-2xl p-4 flex items-center gap-4 hover:shadow-md hover:border-canina-blue/40 transition-all group"
|
||
>
|
||
<div className="w-16 h-16 relative bg-medical-gray-50 rounded-xl overflow-hidden shrink-0 border border-medical-gray-100">
|
||
<SafeImage
|
||
src={p.imageUrl || '/assets/images/placeholder-product.png'}
|
||
alt={p.nameFa || p.name}
|
||
width={64}
|
||
height={64}
|
||
className="w-full h-full object-contain p-1 group-hover:scale-105 transition-transform"
|
||
/>
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<h3 className="text-sm font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors truncate">
|
||
{p.nameFa || p.name}
|
||
</h3>
|
||
<p className="text-xs text-medical-gray-500 truncate mt-1">
|
||
{p.scientificTagline || p.categorySlug || 'مکمل دارویی استاندارد آلمان'}
|
||
</p>
|
||
</div>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|