canina/frontend/application/components/IngredientWiki.tsx
parsa aghaei 8d1b786a24
All checks were successful
Deploy Canina / deploy (push) Successful in 1m50s
chore(brand): rename all Persian occurrences of کانینا to کنینا across the project
2026-08-16 16:40:08 +03:30

301 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { Product, IngredientInfo } from "../lib/data/products";
import { motion } from "motion/react";
import { FlaskConical, CheckCircle2, ChevronRight, ChevronLeft, Beaker, Search } from "lucide-react";
import { productService } from "../lib/services/productService";
import Link from "next/link";
import Image from "next/image";
import { useRouter, useSearchParams } from 'next/navigation';
import api from "../lib/services/api";
import { Ingredient } from "../lib/types";
export default function IngredientWiki() {
const router = useRouter();
const searchParams = useSearchParams();
const termParam = searchParams.get('term');
const [ingredientsWiki, setIngredientsWiki] = useState<IngredientInfo[]>([]);
const [products, setProducts] = useState<Product[]>([]);
const [searchQuery, setSearchQuery] = useState("");
useEffect(() => {
productService.getProducts({ limit: 999 }).then(res => setProducts(res.data));
}, []);
useEffect(() => {
api.get('/ingredients')
.then(res => {
const rawList = Array.isArray(res.data) ? res.data : (res.data?.data || []);
const apiList: IngredientInfo[] = rawList.map((item: Ingredient) => ({
id: item.slug || item.id,
name: item.nameFa ? (item.scientificName ? `${item.nameFa} (${item.scientificName})` : item.nameFa) : (item.nameEn || item.slug),
description: item.description || '',
benefits: Array.isArray(item.benefits) ? item.benefits : []
}));
setIngredientsWiki(apiList);
})
.catch((err) => {
console.error('[IngredientWiki] Failed to fetch ingredients from API:', err);
setIngredientsWiki([]);
});
}, []);
useEffect(() => {
if (termParam && ingredientsWiki.length > 0) {
const el = document.getElementById(`wiki-${termParam}`);
if (el) {
setTimeout(() => {
const yOffset = -120;
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: 'smooth' });
}, 200);
}
}
}, [termParam, ingredientsWiki]);
return (
<div className="bg-white py-12 px-4 overflow-hidden font-vazir" dir="rtl">
<div className="max-w-7xl mx-auto">
{/* Standard Breadcrumb Navigation & Integrated Back Button */}
<div className="flex items-center justify-between mb-8 pb-4 border-b border-medical-gray-200/60 font-vazir" dir="rtl">
<div className="flex items-center gap-2 text-xs font-bold text-medical-gray-400 whitespace-nowrap">
<span className="cursor-pointer hover:text-canina-blue transition-colors" onClick={() => router.push('/')}>خانه</span>
<ChevronLeft className="w-3 h-3 text-medical-gray-300" />
<span className="text-canina-blue font-black">دانشنامه مواد نایاب و ارگانیک</span>
</div>
<button
onClick={() => router.push('/')}
className="px-3.5 py-1.5 bg-white border border-medical-gray-200 rounded-lg text-medical-gray-600 shadow-xs flex items-center gap-1.5 font-bold text-xs hover:border-canina-blue hover:text-canina-blue transition-all whitespace-nowrap"
>
<ChevronRight className="w-3.5 h-3.5" />
<span>بازگشت</span>
</button>
</div>
<div className="flex flex-col items-center text-center mb-20">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 border border-canina-blue/10 rounded-full text-canina-blue text-xs font-black uppercase tracking-widest mb-6">
<FlaskConical className="w-4 h-4" />
علم در خدمت کیفیت
</div>
<h2 className="text-4xl lg:text-6xl font-black text-medical-gray-900 leading-tight md:max-w-4xl">
دانشنامه مواد <span className="text-canina-blue italic">نایاب و ارگانیک</span> کنینا
</h2>
<p className="mt-6 text-lg text-medical-gray-500 max-w-2xl leading-relaxed">
ما از مواد اولیهای استفاده میکنیم که در سطح دارویی فرآوری شدهاند. در اینجا با قدرت بیولوژیک این ترکیبات آشنا شوید.
</p>
<div className="relative w-full max-w-md mt-8">
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
<input
type="text"
placeholder="جستجوی ماده مؤثره..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-full py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
/>
</div>
{/* Sugar-Free & Gluten-Free Medical Standards Banner */}
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<div className="px-4 py-2 bg-emerald-50 border border-emerald-200 text-emerald-800 rounded-2xl text-xs font-black flex items-center gap-1.5 shadow-xs">
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
<span>۱۰۰٪ فاقد قند افزوده (Sugar-Free)</span>
</div>
<div className="px-4 py-2 bg-blue-50 border border-blue-200 text-blue-800 rounded-2xl text-xs font-black flex items-center gap-1.5 shadow-xs">
<CheckCircle2 className="w-4 h-4 text-blue-600" />
<span>مناسب رژیمهای حساس و فاقد گلوتن</span>
</div>
</div>
</div>
{(() => {
const filtered = ingredientsWiki.filter(ing => {
if (!searchQuery.trim()) return true;
const q = searchQuery.trim().toLowerCase();
return ing.name.toLowerCase().includes(q) || ing.description.toLowerCase().includes(q);
});
if (filtered.length === 0) {
return (
<div className="text-center py-20">
<Search className="w-12 h-12 text-medical-gray-300 mx-auto mb-4" />
<h3 className="text-xl font-black text-medical-gray-900 mb-2">مادهای یافت نشد</h3>
<p className="text-medical-gray-400 font-bold">با عبارت جستجو شده ماده مؤثرهای مطابقت ندارد.</p>
</div>
);
}
return (
<div className="space-y-32">
{filtered.map((ing, idx) => {
const hasIngredient = products.filter(p => {
const ingNameClean = ing.name.split('(')[0].trim().toLowerCase();
const ingIdClean = ing.id.replace(/-/g, ' ').toLowerCase();
return p.main_ingredients?.some(mi => {
const miClean = mi.toLowerCase();
return miClean.includes(ingNameClean) ||
ingNameClean.includes(miClean) ||
miClean.includes(ingIdClean) ||
(ing.id === 'green-mussel' && (miClean.includes('صدف') || miClean.includes('mussel'))) ||
(ing.id === 'collagen' && miClean.includes('کلاژن')) ||
(ing.id === 'silver' && (miClean.includes('نقره') || miClean.includes('silver'))) ||
(ing.id === 'peat-extract' && (miClean.includes('پیت') || miClean.includes('moor') || miClean.includes('عصاره'))) ||
(ing.id === 'colostrum' && (miClean.includes('آغوز') || miClean.includes('colostrum'))) ||
(ing.id === 'biotin' && (miClean.includes('بیوتین') || miClean.includes('biotin'))) ||
(ing.id === 'taurin' && (miClean.includes('تورین') || miClean.includes('taurine'))) ||
(ing.id === 'salmon-oil' && (miClean.includes('سالمون') || miClean.includes('salmon') || miClean.includes('ماهی'))) ||
(ing.id === 'black-cumin' && (miClean.includes('سیاه دانه') || miClean.includes('زیره') || miClean.includes('cumin'))) ||
(ing.id === 'eggshell' && (miClean.includes('پوسته تخم مرغ') || miClean.includes('تخم‌مرغ') || miClean.includes('eggshell'))) ||
(ing.id === 'hawthorn' && (miClean.includes('زالزالک') || miClean.includes('hawthorn'))) ||
(ing.id === 'l-carnitine' && (miClean.includes('کارنیتین') || miClean.includes('carnitine'))) ||
(ing.id === 'seaweed' && (miClean.includes('جلبک') || miClean.includes('seaweed'))) ||
(ing.id === 'hyaluronic' && (miClean.includes('هیالورونات') || miClean.includes('hyaluron'))) ||
(ing.id === 'calendula-sea-buckthorn' && (miClean.includes('کالاندولا') || miClean.includes('سنجد') || miClean.includes('خولان') || miClean.includes('calendula'))) ||
(ing.id === 'colloidal-silver' && miClean.includes('کلوئیدی')) ||
(ing.id === 'retinol-palmitate' && (miClean.includes('رتینول') || miClean.includes('پالمیتات'))) ||
(ing.id === 'grapefruit-seed' && miClean.includes('گریپ')) ||
(ing.id === 'vaseline' && miClean.includes('وازلین')) ||
(ing.id === 'panthenol' && miClean.includes('پانتنول')) ||
(ing.id === 'diatomaceous' && miClean.includes('دیاتومه')) ||
(ing.id === 'artichoke' && miClean.includes('کنگر')) ||
(ing.id === 'brewers-yeast' && (miClean.includes('مخمر') || miClean.includes('yeast'))) ||
(ing.id === 'willow-bark' && miClean.includes('بید')) ||
(ing.id === 'ginger' && miClean.includes('زنجبیل')) ||
(ing.id === 'bovine-serum' && miClean.includes('سرم')) ||
(ing.id === 'bovine-blood' && miClean.includes('خون')) ||
(ing.id === 'beef-fat' && miClean.includes('چربی')) ||
(ing.id === 'argan-oil' && miClean.includes('آرگان')) ||
(ing.id === 'rosehip-oil' && miClean.includes('گل رز')) ||
(ing.id === 'walnut-oil' && miClean.includes('گردو')) ||
(ing.id === 'polyglyceryl-caprate' && (miClean.includes('پلی گلیسریل') || miClean.includes('کاپرات'))) ||
(ing.id === 'carbomer' && miClean.includes('کربومر')) ||
(ing.id === 'dodecanoic' && (miClean.includes('دودکانوئیک') || miClean.includes('مارگوسا') || miClean.includes('insect')));
});
});
const isTargeted = termParam === ing.id;
return (
<div
key={ing.id}
id={`wiki-${ing.id}`}
className={`flex flex-col lg:flex-row items-center gap-16 p-6 rounded-[3rem] transition-all duration-500 ${isTargeted ? 'bg-canina-blue/5 border-2 border-canina-blue ring-4 ring-canina-blue/10 shadow-2xl' : ''} ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}
>
{/* Content */}
<div className="lg:w-1/2 space-y-8">
<motion.div
initial={{ opacity: 0, x: idx % 2 === 1 ? 40 : -40 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<div className="flex items-center gap-4 mb-6">
<div className="w-14 h-14 rounded-3xl bg-medical-gray-900 flex items-center justify-center text-white shadow-xl">
<Beaker className="w-6 h-6" />
</div>
<h3 className="text-3xl font-black text-medical-gray-900">{ing.name}</h3>
</div>
<p className="text-lg text-medical-gray-600 leading-relaxed mb-8">
{ing.description}
</p>
<div className="grid grid-cols-1 gap-4 mb-10">
{ing.benefits.map((benefit, bIdx) => (
<div key={bIdx} className="flex items-start gap-4 p-4 bg-medical-gray-50 rounded-2xl border border-medical-gray-100">
<CheckCircle2 className="w-6 h-6 text-canina-blue flex-shrink-0" />
<span className="text-sm font-bold text-medical-gray-700">{benefit}</span>
</div>
))}
</div>
</motion.div>
</div>
{/* Products Linking */}
<div className="lg:w-1/2 relative">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
className="bg-medical-gray-100/80 rounded-[3rem] p-8 sm:p-10 border border-medical-gray-200/60 relative overflow-hidden"
>
<div className="flex items-center justify-between mb-8 pb-4 border-b border-medical-gray-200/60">
<div className="flex items-center gap-2">
<span className="w-2.5 h-2.5 rounded-full bg-canina-blue animate-pulse" />
<h4 className="text-xs sm:text-sm font-black text-medical-gray-900 font-vazir">موجود در محصولات کاتالوگ</h4>
</div>
<span className="text-[10px] font-black text-canina-blue bg-canina-blue/10 px-3 py-1 rounded-full font-vazir">
{hasIngredient.length} محصول
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5 relative z-10">
{hasIngredient.length > 0 ? hasIngredient.map(p => (
<Link
key={p.id}
href={`/shop/${p.slug || p.id}`}
className="bg-white rounded-[2rem] p-5 shadow-lg border border-medical-gray-200/70 hover:border-canina-blue/40 hover:shadow-xl hover:scale-[1.03] transition-all cursor-pointer group flex flex-col justify-between"
>
<div className="aspect-square bg-medical-gray-50 rounded-2xl p-4 mb-4 flex items-center justify-center relative overflow-hidden">
<Image src={p.image} alt={p.name} width={96} height={96} className="max-h-24 w-auto object-contain group-hover:scale-110 transition-transform duration-500" unoptimized />
</div>
<h4 className="text-xs font-black text-center text-medical-gray-900 group-hover:text-canina-blue transition-colors px-1 leading-snug font-vazir">{p.name}</h4>
</Link>
)) : (
<div className="col-span-full py-8 text-center">
<p className="text-medical-gray-400 font-bold italic mb-3">محصولی با این ماده مؤثره یافت نشد.</p>
<button
onClick={() => router.push('/shop')}
className="text-canina-blue font-black text-sm hover:underline"
>
مشاهده همه محصولات
</button>
</div>
)}
</div>
{/* Decorative backdrop */}
<div className="absolute -bottom-20 -left-20 w-80 h-80 bg-canina-blue rounded-full blur-[100px] opacity-10" />
</motion.div>
</div>
</div>
);
})}
</div>
);
})()}
{/* Quality Badge */}
<div className="mt-32 p-12 bg-medical-gray-900 rounded-[4rem] text-center text-white relative overflow-hidden">
<div className="absolute inset-0 opacity-10 mix-blend-overlay">
<div className="grid grid-cols-12 h-full w-full">
{Array.from({length: 120}).map((_, i) => (
<div key={i} className="border border-white/20" />
))}
</div>
</div>
<div className="relative z-10 max-w-3xl mx-auto">
<h3 className="text-3xl lg:text-5xl font-black mb-8 leading-tight italic">تمام مواد اولیه ما دارای گواهینامه <br /> <span className="text-canina-blue">گرید دارویی اختصاصی</span> هستند.</h3>
<p className="text-white/60 text-lg mb-10">این یعنی استانداردی فراتر از مکملهای معمولی، مشابه استانداردهای تولید دارو در اتحادیه اروپا.</p>
<div className="flex justify-center gap-12">
<div className="flex flex-col items-center">
<div className="text-2xl font-black font-vazir text-canina-blue">۱۰۰٪</div>
<div className="text-[10px] font-bold uppercase tracking-widest text-white/40 mt-1">تست آزمایشگاهی</div>
</div>
<div className="flex flex-col items-center">
<div className="text-2xl font-black font-vazir">GMP</div>
<div className="text-[10px] font-bold uppercase tracking-widest text-white/40 mt-1">استاندارد تولید</div>
</div>
<div className="flex flex-col items-center">
<div className="text-2xl font-black font-vazir">ISO</div>
<div className="text-[10px] font-bold uppercase tracking-widest text-white/40 mt-1">کنترل کیفیت</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}