Compare commits

...

23 Commits

Author SHA1 Message Date
parsa aghaei
0d805b0eb9 fix(cart): add missing toast import from sonner in CartDrawer
All checks were successful
Deploy Canina / deploy (push) Successful in 1m48s
2026-07-21 14:28:03 +03:30
parsa aghaei
b0c9e9e88c feat(cart): require authentication for Refill subscription and add explanatory subtext in CartDrawer
- Require guest users to log in via AuthModal when clicking the Refill subscription card
- Add clear descriptive label: 'ویژه اعضا - محاسبه هوشمند دوره مصرف و یادآوری پیامکی قبل از اتمام'
2026-07-21 14:27:21 +03:30
parsa aghaei
320ddad1e5 fix(profile): remove redundant top value proposition banner from PetProfile component 2026-07-21 14:21:49 +03:30
parsa aghaei
d560eb79a3 fix(header): restore getText and store hooks in Header component scope to fix ReferenceError 2026-07-21 14:20:38 +03:30
parsa aghaei
3e3372d01b fix(header): attach dropdown mega menu to sub-navbar button & provide static categories fallback
- Ensure fallback categories & symptoms are populated immediately in state so menuItems is never empty
- Attach mega menu popup panel directly to the sub-navbar button for 800px-1224px screen range
2026-07-21 14:19:17 +03:30
parsa aghaei
99ebfea8d0 fix(header): introduce sub-nav bar for 800px-1224px range and move main nav to center above 1224px
- Below 800px: Hamburger drawer menu is used
- Between 800px and 1224px: Clean horizontal sub-navbar appears below main header row (no hamburger icon)
- Above 1224px (xl): Main navigation items return inline inside main header container centered perfectly
2026-07-21 14:17:38 +03:30
parsa aghaei
587407c39a fix(header): update responsive breakpoints (2-row below 470px, 1-row above 470px, desktop nav at 1224px/xl)
- Show 2-row layout below 470px width, and automatically switch to single-row layout for viewports 470px and wider
- Shift desktop navigation menu items display breakpoint from 1024px (lg) to 1280px (xl / ~1224px) to prevent action button overflow on medium laptop screens
2026-07-21 14:13:58 +03:30
parsa aghaei
6618a81f33 fix(header-footer): re-structure mobile header into 2 rows and clean footer back-to-top button
- Place logo & brand title on row 1 and action buttons (search, login, cart) on row 2 for 100% width mobile clearance
- Remove duplicate inline back-to-top text link in footer and adjust floating button z-index & bottom offset
- Add whitespace-nowrap to footer links (درباره ما، تماس با ما، حریم خصوصی) to prevent line breaks
2026-07-21 14:10:12 +03:30
parsa aghaei
88b2fdbb14 fix(responsive): resolve header mobile overlap, stat box alignment, quality badge placement, and add back-to-top button
- Fix Header mobile logo/button overflow on 320px-375px screens by making logo text truncate gracefully & action buttons compact
- Make 3 hero stat boxes stay side-by-side in a 3-column grid with centered icons/text on mobile screens
- Adjust 'کیفیت / استانداردهای فوق‌دارویی' label position so it stays contained within the image boundaries without overflowing the screen
- Add interactive Back to Top button (both floating and footer button) for quick navigation
2026-07-21 14:06:37 +03:30
parsa aghaei
56515f24c6 style(responsive): enforce global overflow prevention and responsive layout integrity
- Add global overflow-x-hidden and max-w-[100vw] to prevent any unwanted horizontal scrollbars across all viewports
- Verify mobile drawer, grid cards, header, and calculator responsiveness from 320px to 1440px+
2026-07-21 14:01:03 +03:30
parsa aghaei
67444aa251 fix(calculator): implement dynamic weight-based dosage calculation for Welpenmilch & Welpenbrei
- Calculate dosage in grams per kg of puppy weight for Canina Welpenmilch (puppy milk powder) and Canina Welpenbrei (puppy porridge)
- Display exact water mixing ratio (e.g. 15g powder mixed with 45ml warm water) and calculate package duration reactively
2026-07-21 13:56:19 +03:30
parsa aghaei
fb23831640 fix(product): conditionally replace dosage calculator with topical usage notice for external care items
- Hide weight-based dosage calculator for topical/external care products (eye drops, paw balm, sprays, lotions)
- Show dedicated topical usage guide card explaining that dosage is independent of body weight
- Retain active dynamic weight calculator for all oral supplements (tablets, oils, gels, powders)
2026-07-21 13:52:59 +03:30
parsa aghaei
24ce3a44bf fix(calculator): enhance dosage calculator reactivity for logged-in and guest users
- Automatically prefill pet weight and age if user has a registered pet profile
- Provide dynamic weight-based dosage calculation fallback for all products when browsing as guest
- Update daily dose, duration, and package longevity reactively upon slider and age button interactions
2026-07-21 12:52:05 +03:30
parsa aghaei
57cab6cbc2 feat(advisor): add 3-card value proposition section above SmartAdvisor on HomePage
- Explain why registering a pet profile is essential (dosage safety, clinical symptom matching, and refill reminders)
- Upgrade visual presentation with premium cards, micro-animations, and clear benefits on HomePage and /profile
2026-07-21 12:45:36 +03:30
parsa aghaei
53d239b2f2 feat(profile): integrate SmartAdvisor & clear value proposition banner on empty pet profile page
- Display informative header banner explaining why registering a pet profile is crucial for accurate supplement dosage, symptom matching, and refill tracking
- Directly embed the 4-step SmartAdvisor on /profile when no pets have been registered yet
2026-07-21 12:43:35 +03:30
parsa aghaei
78f0727e92 feat(advisor): refactor SmartAdvisor to a 4-step wizard separating symptoms and medical history
- Step 3: Current clinical symptoms (pain/limping, hair loss/itching, diarrhea/digestion, appetite loss, dental tartar, eye discharge, etc.)
- Step 4: Medical & surgical history (joint surgery, pregnancy/lactation, post-illness recovery, heart failure, parasite history)
- Merge symptoms and history into pet profile for hyper-accurate supplement recommendations
2026-07-21 12:39:30 +03:30
parsa aghaei
6d568d56ae feat(symptoms): integrate full symptoms dictionary across SmartAdvisor, PetProfile, and Product Badges
- Expand SmartAdvisor options to include all clinical symptoms (joint pain, limp, diarrhea, hair loss, tartar, eye care, etc.)
- Dynamically match active pet symptoms against product symptoms in PetProfile recommended products engine
- Display specific symptom match badges (e.g. توصیه شده برای درد مفاصل لوسی) directly on product cards
2026-07-21 12:36:23 +03:30
parsa aghaei
ffa6da7978 fix(shop): load all filtered products automatically without manual pagination button
- Remove pagination limit cut-off so all matching products are displayed immediately
- Remove loadMore button for seamless full catalog browsing
2026-07-21 12:32:48 +03:30
parsa aghaei
22dc217534 fix(shop): update symptom filtering to OR logic instead of AND logic
- Allow users to select multiple symptoms and view products matching ANY of the selected symptoms
2026-07-21 12:29:03 +03:30
parsa aghaei
dc8a1bb079 fix(navigation): replace div click handlers with semantic Next.js Link components
- Convert product cards in ArchivePage, IngredientWiki, and SearchResultsPage to Next.js <Link>
- Support right-click / middle-click to open products in new tabs
- Display native target URL preview in browser status bar on hover
2026-07-21 12:27:58 +03:30
parsa aghaei
1b914d25a5 feat(wiki): add 100% complete ingredients coverage for all products
- Add polyglyceryl-4 caprate, carbomer, dextrose, lactose, vitamins A/C/D3/E/B-complex, minerals, excipients, and all plant/animal extracts across all 20+ products to scientificTerms.ts
- Fully support tooltips and wiki links for every ingredient item without exception
2026-07-21 12:23:37 +03:30
parsa aghaei
fcc738f25c feat(wiki): add complete scientific terms dictionary for all product ingredients
- Add colloidal silver, retinol palmitate, grapefruit seed extract, bovine blood/serum, argan/walnut/rosehip oils, etc.
- Map all product ingredients across all categories to Wiki scientific terms
- Update IngredientWiki matching rules for 100% ingredient coverage
2026-07-21 12:19:56 +03:30
parsa aghaei
f762105f09 fix(wiki): enhance wiki ingredient deep-linking and tooltip legibility
- Add term query param handling to IngredientWiki with smooth scroll and target highlight
- Fix Tooltip dark box contrast and click event propagation for study full article button
- Improve ingredient fuzzy matching in ProductPage for complete wiki term linking
2026-07-21 12:09:31 +03:30
15 changed files with 1029 additions and 426 deletions

View File

@ -75,18 +75,18 @@ export default function HomeClient() {
{/* About Section Teaser */} {/* About Section Teaser */}
<section className="py-16 bg-white overflow-hidden font-sans"> <section className="py-16 bg-white overflow-hidden font-sans">
<div className="max-w-7xl mx-auto px-4 grid lg:grid-cols-2 gap-16 lg:gap-24 items-center"> <div className="max-w-7xl mx-auto px-4 grid lg:grid-cols-2 gap-16 lg:gap-24 items-center">
<div className="relative"> <div className="relative overflow-hidden rounded-[3rem]">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-canina-blue/5 rounded-full blur-3xl" /> <div className="absolute -top-10 -right-10 w-40 h-40 bg-canina-blue/5 rounded-full blur-3xl" />
<img <img
src="/assets/images/regenerated_image_1779109861747.png" src="/assets/images/regenerated_image_1779109861747.png"
alt="آزمایشگاه تخصصی تولید مکمل‌های دارویی کانینا آلمان" alt="آزمایشگاه تخصصی تولید مکمل‌های دارویی کانینا آلمان"
className="rounded-[3rem] shadow-2xl relative z-10 grayscale-[10%]" className="rounded-[3rem] shadow-2xl relative z-10 grayscale-[10%] w-full"
/> />
<div className="absolute -bottom-6 -left-6 bg-canina-blue text-white p-8 rounded-[2rem] shadow-xl z-20"> <div className="absolute bottom-4 left-4 sm:bottom-6 sm:left-6 bg-canina-blue text-white p-4 sm:p-6 md:p-8 rounded-[1.5rem] sm:rounded-[2rem] shadow-2xl z-20 max-w-[80%] border border-white/10">
<div className="text-4xl font-black mb-1 font-lalezar tracking-wide">{getText('quality', "کیفیت")}</div> <div className="text-2xl sm:text-4xl font-black mb-1 font-lalezar tracking-wide">{getText('quality', "کیفیت")}</div>
<div className="text-xs uppercase tracking-widest font-bold opacity-80 font-vazir">{getText('ultra_pharmaceutical', "استانداردهای فوق‌دارویی")}</div> <div className="text-[10px] sm:text-xs uppercase tracking-widest font-bold opacity-90 font-vazir">{getText('ultra_pharmaceutical', "استانداردهای فوق‌دارویی")}</div>
</div> </div>
</div> </div>
<div> <div>
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-4 font-vazir"> <div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-4 font-vazir">
{getText('about_label', "میراث ما از آلمان")} {getText('about_label', "میراث ما از آلمان")}

View File

@ -104,7 +104,7 @@
html, body, #root { html, body, #root {
font-family: "Vazirmatn" !important; font-family: "Vazirmatn" !important;
@apply bg-medical-gray-50 text-medical-gray-900 antialiased; @apply bg-medical-gray-50 text-medical-gray-900 antialiased overflow-x-hidden max-w-[100vw];
font-feature-settings: "ss01", "ss02", "cv01", "cv11"; font-feature-settings: "ss01", "ss02", "cv01", "cv11";
} }

View File

@ -3,6 +3,7 @@ import React, { useState, useMemo, useEffect } from "react";
import { Product, PetType } from "../lib/data/products"; import { Product, PetType } from "../lib/data/products";
import { usePetStore } from "../lib/store/usePetStore"; import { usePetStore } from "../lib/store/usePetStore";
import { productService } from "../lib/services/productService"; import { productService } from "../lib/services/productService";
import Link from "next/link";
import { motion, AnimatePresence } from "motion/react"; import { motion, AnimatePresence } from "motion/react";
import { ProductCardSkeleton } from "./Skeleton"; import { ProductCardSkeleton } from "./Skeleton";
import { toast } from "sonner"; import { toast } from "sonner";
@ -66,12 +67,14 @@ const CATEGORY_MAP: Record<string, { category?: string; query?: string; symptoms
"شستشوی چشم": { category: "special-care", query: "چشم" }, "شستشوی چشم": { category: "special-care", query: "چشم" },
}; };
const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) => void }> = ({ product, onClick }) => { const ArchiveProductCard: React.FC<{ product: Product }> = ({ product }) => {
const { addItem } = useCartStore(); const { addItem } = useCartStore();
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const activePet = usePetStore(state => state.pets.find(p => p.id === state.activePetId) || null); const activePet = usePetStore(state => state.pets.find(p => p.id === state.activePetId) || null);
const productUrl = `/shop/${product.slug || product.id}`;
const handleAddToCart = (e: React.MouseEvent) => { const handleAddToCart = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsAdding(true); setIsAdding(true);
addItem(product, 1); addItem(product, 1);
@ -83,10 +86,12 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
if (!activePet) return null; if (!activePet) return null;
const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو"; const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو";
const helpsSymptom = (activePet.medicalConditions || []).some(s => product.symptoms.includes(s)); const matchedSymptom = (activePet.medicalConditions || []).find(mc =>
(product.symptoms || []).some(s => mc.includes(s) || s.includes(mc))
);
if (!sameSpecies) return { type: 'alert', text: `مخصوص ${product.suitableFor}` }; if (!sameSpecies) return { type: 'alert', text: `مخصوص ${product.suitableFor}` };
if (helpsSymptom) return { type: 'success', text: `توصیه شده برای راهکار ${activePet.name}` }; if (matchedSymptom) return { type: 'success', text: `توصیه شده برای ${matchedSymptom} ${activePet.name}` };
if (sameSpecies) return { type: 'neutral', text: `مناسب برای ${activePet.name}` }; if (sameSpecies) return { type: 'neutral', text: `مناسب برای ${activePet.name}` };
return null; return null;
@ -97,75 +102,79 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
layout layout
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
onClick={() => onClick(product)} className="h-full"
className="group bg-white rounded-[2.5rem] border border-medical-gray-200 overflow-hidden hover:shadow-2xl transition-all cursor-pointer flex flex-col h-full relative"
> >
{product.specialBadge && ( <Link
<div className="absolute top-4 right-4 z-10 bg-canina-blue text-white text-[9px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-lg"> href={productUrl}
{product.specialBadge} className="group bg-white rounded-[2.5rem] border border-medical-gray-200 overflow-hidden hover:shadow-2xl transition-all flex flex-col h-full relative"
</div> >
)} {product.specialBadge && (
<div className="absolute top-4 right-4 z-10 bg-canina-blue text-white text-[9px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-lg">
{product.specialBadge}
</div>
)}
{compatibility && ( {compatibility && (
<div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' : <div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' :
compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600' compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600'
}`}> }`}>
{compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />} {compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />}
{compatibility.text} {compatibility.text}
</div> </div>
)} )}
<div className="aspect-square bg-medical-gray-50 flex items-center justify-center p-8 overflow-hidden relative"> <div className="aspect-square bg-medical-gray-50 flex items-center justify-center p-8 overflow-hidden relative">
<SafeImage src={product.image} alt={product.name} className="w-full h-full group-hover:scale-110 transition-transform duration-700" imgClassName="object-contain" /> <SafeImage src={product.image} alt={product.name} className="w-full h-full group-hover:scale-110 transition-transform duration-700" imgClassName="object-contain" />
<div className="absolute bottom-4 left-4 flex gap-2"> <div className="absolute bottom-4 left-4 flex gap-2">
{(product.suitableFor === "سگ" || product.suitableFor === "هر دو") && ( {(product.suitableFor === "سگ" || product.suitableFor === "هر دو") && (
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm"> <div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
<Dog className="w-4 h-4" /> <Dog className="w-4 h-4" />
</div> </div>
)} )}
{(product.suitableFor === "گربه" || product.suitableFor === "هر دو") && ( {(product.suitableFor === "گربه" || product.suitableFor === "هر دو") && (
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm"> <div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
<Cat className="w-4 h-4" /> <Cat className="w-4 h-4" />
</div> </div>
)} )}
</div> </div>
</div>
<div className="p-6 flex flex-col flex-1">
<div className="text-[10px] font-bold text-canina-blue uppercase tracking-widest mb-2">{product.category}</div>
<div className="flex flex-col gap-1 mb-3">
<h3 dir="rtl" className="text-base font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-tight text-right font-vazir">
{product.nameFa || product.name}
</h3>
{product.nameEn && (
<span dir="ltr" className="text-[11px] font-bold text-slate-400 font-sans text-left block">
{product.nameEn}
</span>
)}
</div> </div>
<div className="flex flex-wrap gap-1"> <div className="p-6 flex flex-col flex-1">
{product.symptoms.slice(0, 2).map((s, i) => ( <div className="text-[10px] font-bold text-canina-blue uppercase tracking-widest mb-2">{product.category}</div>
<span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md"> <div className="flex flex-col gap-1 mb-3">
{s} <h3 dir="rtl" className="text-base font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-tight text-right font-vazir">
</span> {product.nameFa || product.name}
))} </h3>
</div> {product.nameEn && (
<span dir="ltr" className="text-[11px] font-bold text-slate-400 font-sans text-left block">
{product.nameEn}
</span>
)}
</div>
<div className="mt-auto flex items-center justify-between pt-4 border-t border-medical-gray-50"> <div className="flex flex-wrap gap-1">
<span className="font-black text-medical-gray-900 font-vazir">{product.price}</span> {product.symptoms.slice(0, 2).map((s, i) => (
<button <span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md">
onClick={handleAddToCart} {s}
disabled={isAdding} </span>
className="w-10 h-10 rounded-xl bg-medical-gray-50 text-canina-blue flex items-center justify-center group-hover:bg-canina-blue group-hover:text-white transition-all disabled:opacity-50" ))}
> </div>
{isAdding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
</button> <div className="mt-auto flex items-center justify-between pt-4 border-t border-medical-gray-50">
<span className="font-black text-medical-gray-900 font-vazir">{product.price}</span>
<button
onClick={handleAddToCart}
disabled={isAdding}
className="w-10 h-10 rounded-xl bg-medical-gray-50 text-canina-blue flex items-center justify-center group-hover:bg-canina-blue group-hover:text-white transition-all disabled:opacity-50"
>
{isAdding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
</button>
</div>
</div> </div>
</div> </Link>
</motion.div> </motion.div>
); );
} };
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@ -277,28 +286,24 @@ export default function ArchivePage({
router.push(newUrl, { scroll: false }); router.push(newUrl, { scroll: false });
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]); }, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
const fetchProducts = async (resetPage = true) => { const fetchProducts = async () => {
setIsUpdating(true); setIsUpdating(true);
try { try {
const res = await productService.getProducts({ const res = await productService.getProducts({
category: selectedCategory, category: selectedCategory,
petType: selectedPet, petType: selectedPet,
query: searchQuery, query: searchQuery,
// Send the first active symptom to the backend for server-side filtering limit: 999,
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
page: resetPage ? 1 : page,
limit: 20,
}); });
let result = res.data; let result = res.data;
// If multiple symptoms selected, do client-side intersection (rare case) // Filter with OR logic (union of symptoms) if symptoms are active
if (activeSymptoms.length > 1) { if (activeSymptoms.length > 0) {
result = result.filter(p => activeSymptoms.every(s => p.symptoms.includes(s))); result = result.filter(p => p.symptoms && p.symptoms.some(s => activeSymptoms.includes(s)));
} }
setFilteredProducts(result); setFilteredProducts(result);
setMeta(res.meta); setMeta(res.meta);
if (resetPage) setPage(1);
} catch (error) { } catch (error) {
console.error("Failed to fetch products:", error); console.error("Failed to fetch products:", error);
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید."); toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
@ -308,32 +313,9 @@ export default function ArchivePage({
}; };
useEffect(() => { useEffect(() => {
fetchProducts(true); fetchProducts();
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]); }, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
const loadMore = async () => {
if (page >= meta.lastPage) return;
try {
const res = await productService.getProducts({
category: selectedCategory,
petType: selectedPet,
query: searchQuery,
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
page: page + 1,
limit: 20,
});
let newItems = res.data;
if (activeSymptoms.length > 0) {
newItems = newItems.filter(p => p.symptoms.some(s => activeSymptoms.includes(s)));
}
setFilteredProducts(prev => [...prev, ...newItems]);
setMeta(res.meta);
setPage(prev => prev + 1);
} catch (error) {
console.error("Failed to load more:", error);
}
};
const toggleSymptom = (s: string) => { const toggleSymptom = (s: string) => {
setActiveSymptoms(prev => prev.includes(s) ? prev.filter(item => item !== s) : [...prev, s]); setActiveSymptoms(prev => prev.includes(s) ? prev.filter(item => item !== s) : [...prev, s]);
}; };
@ -491,20 +473,10 @@ export default function ArchivePage({
<div key="grid" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6"> <div key="grid" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{filteredProducts.map((p: Product) => ( {filteredProducts.map((p: Product) => (
<ArchiveProductCard key={p.id} product={p} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} /> <ArchiveProductCard key={p.id} product={p} />
))} ))}
</AnimatePresence> </AnimatePresence>
</div> </div>
{page < meta.lastPage && (
<div className="flex justify-center mt-10">
<button
onClick={loadMore}
className="bg-canina-blue text-white px-8 py-3 rounded-xl font-black text-sm hover:scale-105 transition-transform shadow-lg shadow-canina-blue/20"
>
بارگذاری محصولات بیشتر
</button>
</div>
)}
</> </>
) : ( ) : (
<div key="empty" className="bg-white rounded-[3rem] p-20 text-center border-2 border-dashed border-medical-gray-200 flex flex-col items-center"> <div key="empty" className="bg-white rounded-[3rem] p-20 text-center border-2 border-dashed border-medical-gray-200 flex flex-col items-center">

View File

@ -7,6 +7,8 @@ import { useCartStore } from "../lib/store/cartStore";
import { Product } from "../lib/data/products"; import { Product } from "../lib/data/products";
import { productService } from "../lib/services/productService"; import { productService } from "../lib/services/productService";
import SafeImage from "./SafeImage"; import SafeImage from "./SafeImage";
import { useUserStore } from "../lib/store/userStore";
import AuthModal from "./AuthModal";
import { toast } from "sonner"; import { toast } from "sonner";
export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate }: { export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate }: {
@ -16,6 +18,8 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
onShopNavigate?: () => void; onShopNavigate?: () => void;
}) { }) {
const { items, updateQuantity, removeItem, isSubscribed, toggleSubscription, getTotal, getSubtotal, getDiscount, coupon, applyCoupon, addItem } = useCartStore(); const { items, updateQuantity, removeItem, isSubscribed, toggleSubscription, getTotal, getSubtotal, getDiscount, coupon, applyCoupon, addItem } = useCartStore();
const { isLoggedIn } = useUserStore();
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [couponCode, setCouponCode] = useState(""); const [couponCode, setCouponCode] = useState("");
const [couponStatus, setCouponStatus] = useState<"idle" | "success" | "error">("idle"); const [couponStatus, setCouponStatus] = useState<"idle" | "success" | "error">("idle");
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null); const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
@ -256,7 +260,14 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
{/* Subscription Toggle */} {/* Subscription Toggle */}
<div <div
onClick={toggleSubscription} onClick={() => {
if (!isLoggedIn && !isSubscribed) {
toast.info("برای فعال‌سازی سرویس تمدید خودکار و دریافت یادآورها، لطفاً ابتدا وارد حساب کاربری خود شوید.");
setIsAuthModalOpen(true);
return;
}
toggleSubscription();
}}
className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`} className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@ -265,7 +276,8 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h5 className="text-xs font-black text-medical-gray-900">فعال‌سازی سرویس تمدید خودکار (Refill)</h5> <h5 className="text-xs font-black text-medical-gray-900">فعال‌سازی سرویس تمدید خودکار (Refill)</h5>
<p className="text-[10px] text-medical-gray-500 font-medium">۵٪ تخفیف روی تمام فاکتور + ارسال اولویت‌دار</p> <p className="text-[10px] text-canina-blue font-black mt-0.5">۵٪ تخفیف روی تمام فاکتور + ارسال اولویت‌دار</p>
<p className="text-[9px] text-medical-gray-400 font-bold mt-1">ویژه اعضا - محاسبه هوشمند دوره مصرف و یادآوری پیامکی قبل از اتمام</p>
</div> </div>
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${isSubscribed ? 'bg-canina-blue border-canina-blue' : 'border-medical-gray-200'}`}> <div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${isSubscribed ? 'bg-canina-blue border-canina-blue' : 'border-medical-gray-200'}`}>
{isSubscribed && <X className="w-3 h-3 text-white" />} {isSubscribed && <X className="w-3 h-3 text-white" />}
@ -273,6 +285,8 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
</div> </div>
</div> </div>
<AuthModal isOpen={isAuthModalOpen} onClose={() => setIsAuthModalOpen(false)} />
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between text-sm px-2"> <div className="flex items-center justify-between text-sm px-2">
<span className="text-medical-gray-500 font-bold">مجموع اقلام</span> <span className="text-medical-gray-500 font-bold">مجموع اقلام</span>

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import React from "react"; import React, { useState, useEffect } from "react";
import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe } from "lucide-react"; import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe, ArrowUp } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import Link from "next/link"; import Link from "next/link";
@ -10,8 +10,38 @@ export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
onB2BOpen?: () => void onB2BOpen?: () => void
}) { }) {
const getText = useSettingsStore(state => state.getText); const getText = useSettingsStore(state => state.getText);
const [showScroll, setShowScroll] = useState(false);
useEffect(() => {
const checkScroll = () => {
if (window.scrollY > 300) {
setShowScroll(true);
} else {
setShowScroll(false);
}
};
window.addEventListener('scroll', checkScroll);
return () => window.removeEventListener('scroll', checkScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return ( return (
<footer className="bg-medical-gray-900 text-white pt-24 pb-12 font-shabnam" dir="rtl"> <footer className="bg-medical-gray-900 text-white pt-24 pb-12 font-shabnam relative" dir="rtl">
{/* Floating Back-to-Top Button */}
{showScroll && (
<button
onClick={scrollToTop}
className="fixed bottom-24 left-6 sm:bottom-8 sm:left-8 z-40 bg-canina-blue text-white p-3 rounded-full shadow-2xl hover:bg-canina-dark hover:scale-110 transition-all border border-white/20 group flex items-center gap-2 font-vazir text-xs font-black"
title="بازگشت به بالای صفحه"
>
<ArrowUp className="w-5 h-5 group-hover:-translate-y-1 transition-transform" />
<span className="hidden sm:inline">بازگشت به بالا</span>
</button>
)}
<div className="max-w-7xl mx-auto px-6"> <div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-16 mb-20"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-16 mb-20">
@ -125,10 +155,10 @@ export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
<div className="pt-12 border-t border-white/5 flex flex-col md:flex-row items-center justify-between gap-6 text-[10px] font-bold uppercase tracking-[0.3em] font-shabnam"> <div className="pt-12 border-t border-white/5 flex flex-col md:flex-row items-center justify-between gap-6 text-[10px] font-bold uppercase tracking-[0.3em] font-shabnam">
<div className="text-slate-400 font-shabnam">{getText('footer_copyright', "© ۲۰۲۶ کانینا ایران. تمامی حقوق محفوظ است.")}</div> <div className="text-slate-400 font-shabnam">{getText('footer_copyright', "© ۲۰۲۶ کانینا ایران. تمامی حقوق محفوظ است.")}</div>
<div className="flex gap-8 font-shabnam text-slate-400"> <div className="flex gap-4 sm:gap-8 font-shabnam text-slate-400 items-center">
<Link href="/about" className="font-extrabold hover:text-white transition-colors">{getText('footer_about', "درباره ما")}</Link> <Link href="/about" className="font-extrabold hover:text-white transition-colors whitespace-nowrap">{getText('footer_about', "درباره ما")}</Link>
<Link href="/contact" className="font-extrabold hover:text-white transition-colors">{getText('footer_contact', "تماس با ما")}</Link> <Link href="/contact" className="font-extrabold hover:text-white transition-colors whitespace-nowrap">{getText('footer_contact', "تماس با ما")}</Link>
<Link href="/privacy" className="font-extrabold hover:text-white transition-colors">{getText('footer_privacy', "حریم خصوصی")}</Link> <Link href="/privacy" className="font-extrabold hover:text-white transition-colors whitespace-nowrap">{getText('footer_privacy', "حریم خصوصی")}</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -47,8 +47,32 @@ export default function Header({
title: string; title: string;
icon: React.ReactNode; icon: React.ReactNode;
solutions: string[]; solutions: string[];
}[]>([]); }[]>([
{
id: "joints",
title: "مفاصل و استخوان",
icon: MENU_ICONS["joints"],
solutions: ["لنگیدن", "سختی در بلند شدن", "درد مفاصل", "رشد سریع توله‌سگ", "پاهای پرانتزی", "ضعف تاندون"]
},
{
id: "immune",
title: "تقویت سیستم ایمنی و گوارش",
icon: MENU_ICONS["immune"],
solutions: ["ضعف بعد از بیماری", "بی‌اشتهایی", "بعد از زایمان", "اسهال", "مشکلات گوارشی"]
},
{
id: "energy",
title: "ویتامین‌ها و انرژی‌بخش‌ها",
icon: MENU_ICONS["energy"],
solutions: ["نفس‌نفس زدن", "خستگی", "سن بالا", "بی‌حالی"]
},
{
id: "special-care",
title: "مراقبت‌های ویژه (پوست، دندان و چشم)",
icon: MENU_ICONS["special-care"],
solutions: ["ریزش مو", "خشکی پوست", "خارش", "جرم دندان", "سوزش چشم"]
}
]);
const petMenuRef = useRef<HTMLDivElement>(null); const petMenuRef = useRef<HTMLDivElement>(null);
const { getTotalItems } = useCartStore(); const { getTotalItems } = useCartStore();
@ -61,13 +85,15 @@ export default function Header({
const loadNavFilters = async () => { const loadNavFilters = async () => {
try { try {
const data = await productService.getNavigationFilters(); const data = await productService.getNavigationFilters();
const mapped = data.map(item => ({ if (data && data.length > 0) {
id: item.slug, const mapped = data.map(item => ({
title: item.name, id: item.slug,
icon: MENU_ICONS[item.slug] || <Pill className="w-5 h-5" />, title: item.name,
solutions: item.symptoms icon: MENU_ICONS[item.slug] || <Pill className="w-5 h-5" />,
})); solutions: item.symptoms || []
setMenuItems(mapped); }));
setMenuItems(mapped);
}
} catch (e) { } catch (e) {
console.error("Failed to load navigation filters:", e); console.error("Failed to load navigation filters:", e);
} }
@ -104,46 +130,49 @@ export default function Header({
</div> </div>
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-medical-gray-100 shadow-sm"> <header className="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-medical-gray-100 shadow-sm">
<div className="max-w-7xl mx-auto h-24 flex items-center justify-between gap-4 px-4 sm:px-6 lg:px-8" dir="rtl"> <div className="max-w-7xl mx-auto py-2 min-[470px]:py-0 min-[470px]:h-24 flex flex-col min-[470px]:flex-row min-[470px]:items-center justify-between gap-2 min-[470px]:gap-4 px-3 sm:px-6 lg:px-8" dir="rtl">
<div className="flex items-center gap-2"> {/* Logo & Mobile Toggle Area */}
{/* Mobile Menu Toggle */} <div className="flex items-center justify-between min-[470px]:justify-start gap-2 w-full min-[470px]:w-auto">
<button <div className="flex items-center gap-2">
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} {/* Mobile Menu Toggle (visible below 800px) */}
className="lg:hidden p-2 rounded-xl text-medical-gray-600 hover:bg-medical-gray-50 transition-all" <button
aria-label="منو" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
> className="min-[800px]:hidden p-2 rounded-xl text-medical-gray-600 hover:bg-medical-gray-50 transition-all flex-shrink-0"
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />} aria-label="منو"
</button> >
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
{/* Logo Section */} {/* Logo Section */}
<Link <Link
href="/" href="/"
className="flex-shrink-0 flex items-center gap-3 cursor-pointer group" className="flex items-center gap-2 sm:gap-3 cursor-pointer group"
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
<div className="w-12 h-12 bg-canina-blue rounded-2xl flex items-center justify-center text-white font-bold text-2xl group-hover:bg-medical-gray-900 transition-all shadow-xl shadow-canina-blue/20 italic">C</div> <div className="w-9 h-9 sm:w-12 sm:h-12 bg-canina-blue rounded-2xl flex items-center justify-center text-white font-bold text-lg sm:text-2xl group-hover:bg-medical-gray-900 transition-all shadow-xl shadow-canina-blue/20 italic flex-shrink-0">C</div>
<div className="flex flex-col justify-center gap-1"> <div className="flex flex-col justify-center gap-0.5 sm:gap-1">
<span className="text-canina-blue font-black text-2xl tracking-tighter italic font-sans flex items-center gap-2 leading-none"> <span className="text-canina-blue font-black text-base sm:text-2xl tracking-tighter italic font-sans flex items-center gap-1.5 sm:gap-2 leading-none whitespace-nowrap">
Canina Canina
<span className="text-sm not-italic font-medium border-r-2 border-medical-gray-200 pr-2 font-vazir"> <span className="text-xs sm:text-sm not-italic font-medium border-r-2 border-medical-gray-200 pr-1.5 sm:pr-2 font-vazir">
{getText('brand_name_fa', "ایران")} {getText('brand_name_fa', "ایران")}
</span>
</span> </span>
</span> <span className="text-[8px] sm:text-[9px] text-medical-gray-400 font-bold uppercase tracking-wider font-vazir leading-none whitespace-nowrap">
<span className="text-[9px] text-medical-gray-400 font-bold uppercase tracking-wider font-vazir leading-none"> {getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")}
{getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")} </span>
</span> </div>
</div> </Link>
</Link> </div>
</div> </div>
{/* Navigation Items */} {/* Navigation Items: Centered on XL screens (1224px+) */}
<nav className="hidden lg:flex items-center gap-1 flex-grow justify-center h-full"> <nav className="hidden xl:flex items-center gap-1 flex-grow justify-center h-full">
<div <div
className="relative h-full flex items-center group" className="relative h-full flex items-center group"
onMouseEnter={() => setIsMegaMenuOpen(true)} onMouseEnter={() => setIsMegaMenuOpen(true)}
onMouseLeave={() => setIsMegaMenuOpen(false)} onMouseLeave={() => setIsMegaMenuOpen(false)}
> >
<button className={`flex items-center justify-center gap-1.5 px-4 py-3 rounded-xl text-sm font-black transition-all whitespace-nowrap focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${isMegaMenuOpen ? 'bg-medical-gray-50 text-canina-blue' : 'text-medical-gray-600 hover:bg-medical-gray-50 hover:text-canina-blue'} font-vazir`}> <button className={`flex items-center justify-center gap-1.5 px-3 xl:px-4 py-3 rounded-xl text-xs xl:text-sm font-black transition-all whitespace-nowrap focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${isMegaMenuOpen ? 'bg-medical-gray-50 text-canina-blue' : 'text-medical-gray-600 hover:bg-medical-gray-50 hover:text-canina-blue'} font-vazir`}>
{getText('nav_solutions', "دسته بندی درمانی")} {getText('nav_solutions', "دسته بندی درمانی")}
<ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isMegaMenuOpen ? 'rotate-180' : ''}`} /> <ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isMegaMenuOpen ? 'rotate-180' : ''}`} />
</button> </button>
@ -209,7 +238,7 @@ export default function Header({
<Link <Link
key={item.id} key={item.id}
href={item.href} href={item.href}
className={`px-4 py-3 rounded-xl text-sm font-black transition-all font-vazir whitespace-nowrap focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] flex items-center justify-center ${currentView === item.id ? 'bg-canina-blue/5 text-canina-blue' : 'text-medical-gray-600 hover:bg-medical-gray-50 hover:text-canina-blue'}`} className={`px-3 xl:px-4 py-3 rounded-xl text-xs xl:text-sm font-black transition-all font-vazir whitespace-nowrap focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] flex items-center justify-center ${currentView === item.id ? 'bg-canina-blue/5 text-canina-blue' : 'text-medical-gray-600 hover:bg-medical-gray-50 hover:text-canina-blue'}`}
> >
{item.label} {item.label}
</Link> </Link>
@ -217,7 +246,7 @@ export default function Header({
</nav> </nav>
{/* Action Area */} {/* Action Area */}
<div className="flex items-center gap-4 flex-shrink-0 justify-end h-16 pl-2"> <div className="flex items-center gap-2 sm:gap-4 flex-shrink-0 justify-end h-auto md:h-16 pl-1 sm:pl-2">
{/* Elastic Search */} {/* Elastic Search */}
<div className="relative flex items-center justify-end z-20"> <div className="relative flex items-center justify-end z-20">
@ -225,10 +254,10 @@ export default function Header({
{isSearchFocused && ( {isSearchFocused && (
<motion.div <motion.div
initial={{ width: 0, opacity: 0 }} initial={{ width: 0, opacity: 0 }}
animate={{ width: 320, opacity: 1 }} animate={{ width: 260, opacity: 1 }}
exit={{ width: 0, opacity: 0 }} exit={{ width: 0, opacity: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 220 }} transition={{ type: "spring", damping: 25, stiffness: 220 }}
className="absolute right-0 top-1/2 -translate-y-1/2 bg-white border border-canina-blue/30 border-l rounded-2xl shadow-xl shadow-canina-blue/5 overflow-hidden h-12 flex items-center pr-12 pl-4 z-25" className="absolute right-0 top-1/2 -translate-y-1/2 bg-white border border-canina-blue/30 border-l rounded-2xl shadow-xl shadow-canina-blue/5 overflow-hidden h-10 sm:h-12 flex items-center pr-10 pl-3 z-25"
> >
<form onSubmit={handleSearch} className="w-full flex items-center"> <form onSubmit={handleSearch} className="w-full flex items-center">
<input <input
@ -237,9 +266,9 @@ export default function Header({
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
onBlur={() => !searchQuery && setIsSearchFocused(false)} onBlur={() => !searchQuery && setIsSearchFocused(false)}
placeholder="جستجوی محصولات، مقالات سلامت و غیره…" placeholder="جستجو…"
dir="rtl" dir="rtl"
className="w-full bg-transparent py-2 pr-4 text-[12px] font-black outline-none font-vazir text-medical-gray-900 text-right placeholder:text-medical-gray-400 placeholder:text-right" className="w-full bg-transparent py-2 pr-2 text-[12px] font-black outline-none font-vazir text-medical-gray-900 text-right placeholder:text-medical-gray-400 placeholder:text-right"
/> />
<input type="submit" className="hidden" /> <input type="submit" className="hidden" />
</form> </form>
@ -250,7 +279,7 @@ export default function Header({
<HeaderButton <HeaderButton
variant={isSearchFocused ? "primary" : "secondary"} variant={isSearchFocused ? "primary" : "secondary"}
className={cn( className={cn(
"w-12 h-12 transition-all duration-300", "w-10 h-10 sm:w-12 sm:h-12 transition-all duration-300",
isSearchFocused isSearchFocused
? "z-30 bg-canina-blue text-white rounded-r-2xl rounded-l-none border border-canina-blue border-l-0 shadow-none" ? "z-30 bg-canina-blue text-white rounded-r-2xl rounded-l-none border border-canina-blue border-l-0 shadow-none"
: "rounded-2xl" : "rounded-2xl"
@ -264,45 +293,45 @@ export default function Header({
} }
}} }}
> >
<Search className={cn("w-5 h-5", isSearchFocused ? "text-white" : "text-canina-blue")} /> <Search className={cn("w-4 h-4 sm:w-5 sm:h-5", isSearchFocused ? "text-white" : "text-canina-blue")} />
</HeaderButton> </HeaderButton>
</div> </div>
<div className="flex gap-2 items-center h-12"> <div className="flex gap-1.5 sm:gap-2 items-center h-12">
{isLoggedIn ? ( {isLoggedIn ? (
<div className="flex items-center h-full gap-2 p-1.5 bg-medical-gray-50/50 border border-medical-gray-100 rounded-3xl"> <div className="flex items-center h-full gap-1.5 sm:gap-2 p-1 bg-medical-gray-50/50 border border-medical-gray-100 rounded-3xl">
{/* User Name (Direct link to dashboard) */} {/* User Name (Direct link to dashboard) */}
<div <div
className="flex items-center gap-2.5 px-4 py-2 hover:bg-white rounded-2xl cursor-pointer transition-all border border-transparent hover:border-medical-gray-200" className="flex items-center gap-1.5 sm:gap-2.5 px-2.5 sm:px-4 py-2 hover:bg-white rounded-2xl cursor-pointer transition-all border border-transparent hover:border-medical-gray-200"
onClick={() => onNavigate("user-dashboard")} onClick={() => onNavigate("user-dashboard")}
> >
<User className="w-5 h-5 text-canina-blue" /> <User className="w-4 h-4 sm:w-5 sm:h-5 text-canina-blue" />
<span className="text-[14px] font-black font-vazir text-medical-gray-900 leading-none"> <span className="text-[12px] sm:text-[14px] font-black font-vazir text-medical-gray-900 leading-none">
{profile.firstName} {profile.firstName}
</span> </span>
</div> </div>
{/* Separator Line */} {/* Separator Line */}
<div className="w-[1.5px] h-5 bg-medical-gray-200" /> <div className="w-[1.5px] h-4 sm:h-5 bg-medical-gray-200" />
{/* Pet Name & Switcher */} {/* Pet Name & Switcher */}
<div className="relative h-full flex items-center" ref={petMenuRef}> <div className="relative h-full flex items-center" ref={petMenuRef}>
<div <div
className={cn( className={cn(
"flex items-center gap-2.5 px-4 py-2 rounded-2xl transition-all cursor-pointer", "flex items-center gap-1.5 sm:gap-2.5 px-2.5 sm:px-4 py-2 rounded-2xl transition-all cursor-pointer",
isPetSwitcherOpen ? "bg-canina-blue text-white shadow-lg shadow-canina-blue/20" : "text-canina-blue hover:bg-white" isPetSwitcherOpen ? "bg-canina-blue text-white shadow-lg shadow-canina-blue/20" : "text-canina-blue hover:bg-white"
)} )}
onClick={() => setIsPetSwitcherOpen(!isPetSwitcherOpen)} onClick={() => setIsPetSwitcherOpen(!isPetSwitcherOpen)}
> >
{activePet?.type === "گربه" ? ( {activePet?.type === "گربه" ? (
<Cat className={cn("w-5 h-5", isPetSwitcherOpen ? "text-white" : "text-canina-blue/60")} /> <Cat className={cn("w-4 h-4 sm:w-5 sm:h-5", isPetSwitcherOpen ? "text-white" : "text-canina-blue/60")} />
) : ( ) : (
<Dog className={cn("w-5 h-5", isPetSwitcherOpen ? "text-white" : "text-canina-blue/60")} /> <Dog className={cn("w-4 h-4 sm:w-5 sm:h-5", isPetSwitcherOpen ? "text-white" : "text-canina-blue/60")} />
)} )}
<span className={cn("text-[14px] font-black font-vazir leading-none", isPetSwitcherOpen ? "text-white" : "text-canina-blue")}> <span className={cn("text-[12px] sm:text-[14px] font-black font-vazir leading-none", isPetSwitcherOpen ? "text-white" : "text-canina-blue")}>
{activePet?.name || "بدون پت"} {activePet?.name || "بدون پت"}
</span> </span>
<ChevronDown className={cn("w-4 h-4 transition-transform", isPetSwitcherOpen ? "rotate-180" : "")} /> <ChevronDown className={cn("w-3.5 h-3.5 sm:w-4 sm:h-4 transition-transform", isPetSwitcherOpen ? "rotate-180" : "")} />
</div> </div>
<AnimatePresence> <AnimatePresence>
@ -391,26 +420,95 @@ export default function Header({
<HeaderButton <HeaderButton
variant="outline" variant="outline"
layout="row" layout="row"
className="px-6 h-12 text-sm font-black flex items-center justify-center" className="px-3 sm:px-6 h-10 sm:h-12 text-xs sm:text-sm font-black flex items-center justify-center"
onClick={() => setIsAuthModalOpen(true)} onClick={() => setIsAuthModalOpen(true)}
icon={<LogIn className="w-5 h-5 text-canina-blue" />} icon={<LogIn className="w-4 h-4 sm:w-5 sm:h-5 text-canina-blue" />}
> >
{getText('nav_login', "ورود / ثبت‌نام")} <span className="hidden xs:inline">{getText('nav_login', "ورود / ثبت‌نام")}</span>
<span className="xs:hidden">ورود</span>
</HeaderButton> </HeaderButton>
)} )}
<HeaderButton <HeaderButton
variant="primary" variant="primary"
className="w-16 h-12" className="w-12 sm:w-16 h-10 sm:h-12"
onClick={onCartOpen} onClick={onCartOpen}
badge={getTotalItems()} badge={getTotalItems()}
icon={<ShoppingBag className="w-6 h-6" />} icon={<ShoppingBag className="w-5 h-5 sm:w-6 sm:h-6" />}
> >
<div className="text-[8px] font-black tracking-tight whitespace-nowrap mt-0.5">{getText('nav_cart', "سبد خرید")}</div> <div className="hidden sm:block text-[8px] font-black tracking-tight whitespace-nowrap mt-0.5">{getText('nav_cart', "سبد خرید")}</div>
</HeaderButton> </HeaderButton>
</div> </div>
</div> </div>
</div> </div>
{/* Medium Screen Sub-Navbar (Visible only between 800px and 1224px) */}
<div className="hidden min-[800px]:flex xl:hidden border-t border-medical-gray-100 bg-medical-gray-50/50 py-2 px-6 justify-center items-center gap-2 font-vazir relative" dir="rtl">
<div className="relative">
<button
onClick={() => setIsMegaMenuOpen(!isMegaMenuOpen)}
className={`px-3 py-1.5 rounded-lg text-xs font-black transition-all flex items-center gap-1.5 whitespace-nowrap ${isMegaMenuOpen ? 'bg-canina-blue text-white shadow-sm' : 'text-medical-gray-700 hover:bg-white'}`}
>
{getText('nav_solutions', "دسته بندی درمانی")}
<ChevronDown className={`w-3.5 h-3.5 transition-transform ${isMegaMenuOpen ? 'rotate-180 text-white' : 'text-canina-blue'}`} />
</button>
<AnimatePresence>
{isMegaMenuOpen && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.98 }}
className="absolute top-[120%] right-0 w-[600px] max-w-[90vw] bg-white border border-medical-gray-100 shadow-2xl rounded-[2rem] p-6 grid grid-cols-2 gap-6 z-50 pointer-events-auto"
>
{menuItems.map((item, idx) => (
<div key={idx} className="group/item">
<Link
href={`/shop?category=${item.id}`}
onClick={() => setIsMegaMenuOpen(false)}
className="flex items-center gap-3 mb-3 cursor-pointer block"
>
<div className="p-2 bg-medical-gray-50 rounded-xl text-canina-blue group-hover/item:bg-canina-blue group-hover/item:text-white transition-all shadow-sm">
{item.icon}
</div>
<h4 className="font-black text-medical-gray-900 text-xs font-vazir">{item.title}</h4>
</Link>
{item.solutions.length > 0 && (
<div className="flex flex-wrap gap-1 border-r-2 border-medical-gray-100 pr-2">
{item.solutions.slice(0, 6).map((sol, sIdx) => (
<Link
key={sIdx}
href={`/shop?category=${item.id}&symptom=${encodeURIComponent(sol)}`}
className="inline-flex items-center px-2 py-0.5 rounded-full text-[9px] font-bold bg-medical-gray-50 border border-medical-gray-200 text-medical-gray-500 hover:bg-canina-blue/5 hover:border-canina-blue/30 hover:text-canina-blue transition-all cursor-pointer font-vazir whitespace-nowrap"
onClick={() => setIsMegaMenuOpen(false)}
>
{sol}
</Link>
))}
</div>
)}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
<div className="w-[1px] h-4 bg-medical-gray-200" />
{[
{ id: 'shop', label: getText('nav_products', 'محصولات تخصصی'), href: '/shop' },
{ id: 'wiki', label: getText('nav_wiki', 'دانشنامه علمی'), href: '/wiki' },
{ id: 'blog', label: getText('nav_blog', 'مجله سلامت پت'), href: '/blog' },
{ id: 'profile', label: getText('nav_pet_profiles', 'شناسنامه پت'), href: '/profile' }
].map((item) => (
<Link
key={item.id}
href={item.href}
className={`px-3 py-1.5 rounded-lg text-xs font-black transition-all whitespace-nowrap ${currentView === item.id ? 'bg-canina-blue text-white shadow-sm' : 'text-medical-gray-600 hover:bg-white hover:text-canina-blue'}`}
>
{item.label}
</Link>
))}
</div>
</header> </header>
{/* Mobile Drawer Menu */} {/* Mobile Drawer Menu */}
@ -423,7 +521,7 @@ export default function Header({
animate={{ opacity: 0.5 }} animate={{ opacity: 0.5 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
className="fixed inset-0 bg-black z-40 lg:hidden" className="fixed inset-0 bg-black z-40 min-[800px]:hidden"
/> />
{/* Drawer */} {/* Drawer */}
<motion.div <motion.div
@ -431,7 +529,7 @@ export default function Header({
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: "100%" }} exit={{ x: "100%" }}
transition={{ type: "spring", damping: 25, stiffness: 200 }} transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed top-0 right-0 bottom-0 w-80 max-w-[85vw] bg-white z-50 shadow-2xl p-6 flex flex-col gap-6 overflow-y-auto lg:hidden" className="fixed top-0 right-0 bottom-0 w-80 max-w-[85vw] bg-white z-50 shadow-2xl p-6 flex flex-col gap-6 overflow-y-auto min-[800px]:hidden"
dir="rtl" dir="rtl"
> >
<div className="flex items-center justify-between border-b border-medical-gray-100 pb-4"> <div className="flex items-center justify-between border-b border-medical-gray-100 pb-4">

View File

@ -168,46 +168,46 @@ export default function Hero({ banners = [] }: { banners?: any[] }) {
</div> </div>
</div> </div>
{/* Stats - Centered, full-width row under Hero content to decrease cognitive load */} {/* Stats - 3 side-by-side cards with centered text on all screens */}
<div className="max-w-7xl mx-auto px-4 relative"> <div className="max-w-7xl mx-auto px-2 sm:px-4 relative">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.6 }} transition={{ delay: 0.8, duration: 0.6 }}
className="grid grid-cols-1 md:grid-cols-3 gap-8 py-10 border-t border-medical-gray-150" className="grid grid-cols-3 gap-2 sm:gap-6 py-6 sm:py-10 border-t border-medical-gray-150"
> >
{/* Stat 1: Founded Year */} {/* Stat 1: Founded Year */}
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-3xl p-6 flex items-center gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all"> <div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
<div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300"> <div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
<Calendar className="w-7 h-7" /> <Calendar className="w-4 h-4 sm:w-7 sm:h-7" />
</div> </div>
<div className="text-right"> <div className="flex flex-col items-center sm:items-start text-center sm:text-right">
<div className="text-3xl font-black text-canina-blue font-vazir leading-none">۱۹۸۴</div> <div className="text-base sm:text-3xl font-black text-canina-blue font-vazir leading-none">۱۹۸۴</div>
<div className="text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-2 font-vazir">{getText('hero_stat_founded', "سال تاسیس برند در آلمان")}</div> <div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">{getText('hero_stat_founded', "سال تاسیس برند")}</div>
</div> </div>
</div> </div>
{/* Stat 2: Active Agencies */} {/* Stat 2: Active Agencies */}
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-3xl p-6 flex items-center gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all"> <div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
<div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300"> <div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
<Globe className="w-7 h-7" /> <Globe className="w-4 h-4 sm:w-7 sm:h-7" />
</div> </div>
<div className="text-right"> <div className="flex flex-col items-center sm:items-start text-center sm:text-right">
<div className="flex items-baseline"> <div className="flex items-baseline justify-center sm:justify-start">
<StatCounter target={40} /> <StatCounter target={40} />
</div> </div>
<div className="text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-2 font-vazir">{getText('hero_stat_agencies', "نمایندگی فعال جهانی")}</div> <div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">{getText('hero_stat_agencies', "نمایندگی فعال")}</div>
</div> </div>
</div> </div>
{/* Stat 3: German Formula */} {/* Stat 3: German Formula */}
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-3xl p-6 flex items-center gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all"> <div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
<div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300"> <div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
<ShieldCheck className="w-7 h-7" /> <ShieldCheck className="w-4 h-4 sm:w-7 sm:h-7" />
</div> </div>
<div className="text-right"> <div className="flex flex-col items-center sm:items-start text-center sm:text-right">
<div className="text-3xl font-black text-canina-blue font-vazir leading-none">۱۰۰٪</div> <div className="text-base sm:text-3xl font-black text-canina-blue font-vazir leading-none">۱۰۰٪</div>
<div className="text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-2 font-vazir">{getText('hero_stat_german_formula', "فرمولاسیون علمی اختصاصی")}</div> <div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">{getText('hero_stat_german_formula', "فرمول آلمانی")}</div>
</div> </div>
</div> </div>
</motion.div> </motion.div>

View File

@ -6,10 +6,14 @@ import { FlaskConical, CheckCircle2, ChevronRight, ChevronLeft, Beaker, Search }
import { useSettingsStore } from "../lib/store/settingsStore"; import { useSettingsStore } from "../lib/store/settingsStore";
import { productService } from "../lib/services/productService"; import { productService } from "../lib/services/productService";
import { useRouter } from 'next/navigation'; import Link from "next/link";
import { SCIENTIFIC_TERMS } from "../lib/data/scientificTerms";
import { useRouter, useSearchParams } from 'next/navigation';
export default function IngredientWiki() { export default function IngredientWiki() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const termParam = searchParams.get('term');
const texts = useSettingsStore(state => state.texts); const texts = useSettingsStore(state => state.texts);
const scientificTerms = useSettingsStore(state => state.scientificTerms); const scientificTerms = useSettingsStore(state => state.scientificTerms);
const [ingredientsWiki, setIngredientsWiki] = useState<IngredientInfo[]>([]); const [ingredientsWiki, setIngredientsWiki] = useState<IngredientInfo[]>([]);
@ -21,43 +25,52 @@ export default function IngredientWiki() {
}, []); }, []);
useEffect(() => { useEffect(() => {
let list: IngredientInfo[] = [];
const raw = texts.ingredients_wiki || texts.ingredients_wiki_text; const raw = texts.ingredients_wiki || texts.ingredients_wiki_text;
if (raw) { if (raw) {
try { try {
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
setIngredientsWiki(parsed.map((item: any) => ({ list = parsed.map((item: any) => ({
id: item.id, id: item.id,
name: item.name, name: item.name,
description: item.description, description: item.description,
benefits: Array.isArray(item.benefits) ? item.benefits : [] benefits: Array.isArray(item.benefits) ? item.benefits : []
}))); }));
return;
} }
} catch (e) { } catch (e) {
console.error("Failed to parse ingredients_wiki:", e); console.error("Failed to parse ingredients_wiki:", e);
} }
} }
const terms = Object.values(scientificTerms);
if (terms.length > 0) { // Merge terms from scientificTerms and static SCIENTIFIC_TERMS dictionary
setIngredientsWiki(terms.map((t: any) => { const mergedTerms = { ...SCIENTIFIC_TERMS, ...scientificTerms };
let benefits: string[] = []; Object.entries(mergedTerms).forEach(([key, t]: [string, any]) => {
if (t.keywords) { const wikiId = t.wikiId || t.key || key;
try { if (!list.some(item => item.id === wikiId || item.name.includes(t.term))) {
const parsed = typeof t.keywords === 'string' ? JSON.parse(t.keywords) : t.keywords; list.push({
if (Array.isArray(parsed)) benefits = parsed; id: wikiId,
} catch (e) {}
}
return {
id: t.key,
name: t.term, name: t.term,
description: t.definition, description: t.definition,
benefits benefits: []
}; });
})); }
} });
setIngredientsWiki(list);
}, [texts, scientificTerms]); }, [texts, scientificTerms]);
useEffect(() => {
if (termParam && ingredientsWiki.length > 0) {
const el = document.getElementById(`wiki-${termParam}`);
if (el) {
setTimeout(() => {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
}
}
}, [termParam, ingredientsWiki]);
return ( return (
<div className="bg-white py-12 px-4 overflow-hidden font-vazir" dir="rtl"> <div className="bg-white py-12 px-4 overflow-hidden font-vazir" dir="rtl">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
@ -150,13 +163,36 @@ export default function IngredientWiki() {
(ing.id === 'l-carnitine' && (miClean.includes('کارنیتین') || miClean.includes('carnitine'))) || (ing.id === 'l-carnitine' && (miClean.includes('کارنیتین') || miClean.includes('carnitine'))) ||
(ing.id === 'seaweed' && (miClean.includes('جلبک') || miClean.includes('seaweed'))) || (ing.id === 'seaweed' && (miClean.includes('جلبک') || miClean.includes('seaweed'))) ||
(ing.id === 'hyaluronic' && (miClean.includes('هیالورونات') || miClean.includes('hyaluron'))) || (ing.id === 'hyaluronic' && (miClean.includes('هیالورونات') || miClean.includes('hyaluron'))) ||
(ing.id === 'calendula-sea-buckthorn' && (miClean.includes('کالاندولا') || miClean.includes('سنجد') || miClean.includes('calendula'))) || (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'))); (ing.id === 'dodecanoic' && (miClean.includes('دودکانوئیک') || miClean.includes('مارگوسا') || miClean.includes('insect')));
}); });
}); });
const isTargeted = termParam === ing.id;
return ( return (
<div key={ing.id} className={`flex flex-col lg:flex-row items-center gap-16 ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}> <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 */} {/* Content */}
<div className="lg:w-1/2 space-y-8"> <div className="lg:w-1/2 space-y-8">
<motion.div <motion.div
@ -201,14 +237,14 @@ export default function IngredientWiki() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 relative z-10"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6 relative z-10">
{hasIngredient.length > 0 ? hasIngredient.map(p => ( {hasIngredient.length > 0 ? hasIngredient.map(p => (
<div <Link
key={p.id} key={p.id}
onClick={() => router.push(`/shop/${p.slug || p.id}`)} href={`/shop/${p.slug || p.id}`}
className="bg-white rounded-[2.5rem] p-6 shadow-xl hover:scale-105 transition-transform cursor-pointer group" className="bg-white rounded-[2.5rem] p-6 shadow-xl hover:scale-105 transition-transform cursor-pointer group block"
> >
<img src={p.image} alt={p.name} className="w-24 h-24 object-contain mx-auto mb-4" referrerPolicy="no-referrer" /> <img src={p.image} alt={p.name} className="w-24 h-24 object-contain mx-auto mb-4" referrerPolicy="no-referrer" />
<h4 className="text-sm font-black text-center text-medical-gray-900 group-hover:text-canina-blue transition-colors px-2">{p.name}</h4> <h4 className="text-sm font-black text-center text-medical-gray-900 group-hover:text-canina-blue transition-colors px-2">{p.name}</h4>
</div> </Link>
)) : ( )) : (
<div className="col-span-full py-8 text-center"> <div className="col-span-full py-8 text-center">
<p className="text-medical-gray-400 font-bold italic mb-3">محصولی با این ماده مؤثره یافت نشد.</p> <p className="text-medical-gray-400 font-bold italic mb-3">محصولی با این ماده مؤثره یافت نشد.</p>

View File

@ -27,11 +27,16 @@ import {
AlertTriangle, AlertTriangle,
ArrowRight, ArrowRight,
TrendingUp, TrendingUp,
Check Check,
Sparkles,
Beaker,
HelpCircle,
FileText
} from "lucide-react"; } from "lucide-react";
import { usePetStore, PetProfile as GlobalPetProfile, Reminder, HealthLog, PetConsumption } from "../lib/store/usePetStore"; import { usePetStore, PetProfile as GlobalPetProfile, Reminder, HealthLog, PetConsumption } from "../lib/store/usePetStore";
import { useCartStore } from "../lib/store/cartStore"; import { useCartStore } from "../lib/store/cartStore";
import SafeImage from "./SafeImage"; import SafeImage from "./SafeImage";
import SmartAdvisor from "./SmartAdvisor";
import { toast } from "sonner"; import { toast } from "sonner";
import { toPersian, cn } from "../lib/utils"; import { toPersian, cn } from "../lib/utils";
@ -165,36 +170,29 @@ export default function PetProfile({ initialView, advisorNeed }: {
let picks: { product: Product, reason?: string }[] = []; let picks: { product: Product, reason?: string }[] = [];
// Priority: Medical Conditions // Priority 1: Match activePet.medicalConditions directly against product.symptoms
if (activePet.medicalConditions.includes("جراحی مفاصل")) { if (activePet.medicalConditions && activePet.medicalConditions.length > 0) {
const gag = products.find(p => p.id === "canhydrox-gag"); products.forEach(p => {
if (gag) picks.push({ product: gag, reason: "پیشنهاد ویژه بر اساس سوابق جراحی مفاصل" }); if (p.suitableFor !== activePet.type && p.suitableFor !== "هر دو") return;
const matchedSymptoms = (p.symptoms || []).filter(s =>
activePet.medicalConditions.some(mc => mc.includes(s) || s.includes(mc))
);
if (matchedSymptoms.length > 0 && !picks.some(x => x.product.id === p.id)) {
picks.push({
product: p,
reason: `پیشنهاد هوشمند برای تسکین ${matchedSymptoms.join(" و ")}`
});
}
});
} }
if (activePet.medicalConditions.includes("زایمان اخیر") || activePet.medicalConditions.includes("بارداری")) { // Priority 2: Biological factors
const booster = products.find(p => p.id === "immun-booster");
if (booster) picks.push({ product: booster, reason: "حمایت حیاتی دوران بارداری و زایمان" });
}
if (activePet.medicalConditions.includes("مشکلات گوارشی")) {
const moor = products.find(p => p.id === "moor-tranke");
if (moor) picks.push({ product: moor, reason: "تنظیم تخصصی سیستم گوارش" });
}
if (activePet.medicalConditions.includes("ریزش موی شدید")) {
const lachs = products.find(p => p.id === "lachs-ol");
if (lachs) picks.push({ product: lachs, reason: "تقویت تارهای مو و کنترل ریزش" });
}
if (activePet.medicalConditions.includes("بی‌اشتهایی")) {
const energy = products.find(p => p.id === "energy-gel");
if (energy) picks.push({ product: energy, reason: "تامین فوری انرژی و تحریک اشتها" });
}
// Biological factors
if (activePet.age > 7) { if (activePet.age > 7) {
const heart = products.find(p => p.id === "herz-vital"); const heart = products.find(p => p.id === "herz-vital" || p.slug?.includes("herz"));
if (heart && !picks.some(x => x.product.id === heart.id)) picks.push({ product: heart, reason: "محافظت از قلب پت‌های مسن" }); if (heart && !picks.some(x => x.product.id === heart.id)) {
picks.push({ product: heart, reason: "محافظت از قلب پت‌های مسن" });
}
} }
// Fill defaults if needed // Fill defaults if needed
@ -383,6 +381,24 @@ export default function PetProfile({ initialView, advisorNeed }: {
// 2. Add / Edit Pet Steps // 2. Add / Edit Pet Steps
if (view === "add" || view === "edit") { if (view === "add" || view === "edit") {
if (view === "add" && pets.length === 0) {
return (
<div className="min-h-screen bg-medical-gray-50 py-8 px-4 font-vazir" dir="rtl">
<div className="max-w-5xl mx-auto space-y-6">
{/* Render SmartAdvisor directly without redundant top banner */}
<SmartAdvisor
onComplete={(data) => {
const newPet: any = { ...data, id: Date.now().toString(), reminders: [], logs: [], consumptions: [] };
addPet(newPet);
setActivePet(newPet.id);
toast.success(`شناسنامه سلامت ${data.name} با موفقیت صادر شد`);
setView("detail");
}}
/>
</div>
</div>
);
}
return ( return (
<div className="min-h-screen bg-medical-gray-50 py-20 px-4 font-vazir" dir="rtl"> <div className="min-h-screen bg-medical-gray-50 py-20 px-4 font-vazir" dir="rtl">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">

View File

@ -109,38 +109,42 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
const apiProduct = allProducts.find(p => p.id === product.id) || product; const apiProduct = allProducts.find(p => p.id === product.id) || product;
if (!apiProduct) return null; if (!apiProduct) return null;
const staticProduct = PRODUCTS.find(p => p.id === apiProduct.id || p.artNo === apiProduct.artNo); const staticProduct = PRODUCTS.find(p => p.id === apiProduct.id || p.artNo === apiProduct.artNo);
if (staticProduct) { if (staticProduct && typeof staticProduct.calculateDosage === 'function') {
return { return {
...apiProduct, ...apiProduct,
calculateDosage: staticProduct.calculateDosage calculateDosage: staticProduct.calculateDosage,
packageSize: apiProduct.packageSize || staticProduct.packageSize || 100
}; };
} }
if (typeof apiProduct.calculateDosage !== 'function') {
return { // Dynamic weight-based calculation fallback for any product without custom logic
...apiProduct, return {
calculateDosage: (w: number, y: boolean) => { ...apiProduct,
return { packageSize: apiProduct.packageSize || 100,
quantity: 1, calculateDosage: (w: number, isYoung: boolean) => {
unit: apiProduct.unit || "قرص", const factor = isYoung ? 0.2 : 0.1;
description: "مصرف روزانه بر اساس دستور پزشک" const baseQty = Math.max(1, Math.round(w * factor));
}; return {
} quantity: baseQty,
}; unit: apiProduct.unit?.includes('ml') ? "میلی‌لیتر" : apiProduct.unit?.includes('g') ? "گرم" : "قرص",
} description: isYoung ? "دوز رشد روزانه" : "دوز نگهداری روزانه"
return apiProduct; };
}
};
}, [product, allProducts]); }, [product, allProducts]);
useEffect(() => { useEffect(() => {
if (activePet) { if (activePet && activePet.weight > 0) {
setWeight(activePet.weight); setWeight(activePet.weight);
setPetType(activePet.age <= 1 ? "young" : "adult"); setPetType(activePet.age <= 1 ? "young" : "adult");
} }
}, [activePet, setWeight, setPetType]); }, [activePet]);
const calculation = useMemo(() => { const calculation = useMemo(() => {
if (!fullProduct || typeof fullProduct.calculateDosage !== 'function') return null; if (!fullProduct || typeof fullProduct.calculateDosage !== 'function') return null;
const result = fullProduct.calculateDosage(weight, petType === "young"); const result = fullProduct.calculateDosage(weight, petType === "young");
const duration = Math.floor(fullProduct.packageSize / result.quantity); const packageSize = fullProduct.packageSize || 100;
const duration = Math.max(1, Math.floor(packageSize / (result.quantity || 1)));
return { return {
dailyDose: result.quantity, dailyDose: result.quantity,
@ -336,64 +340,97 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
</section> </section>
)} )}
{/* Smart Dosage Calculator Utility Section */} {/* Smart Dosage Calculator Utility Section / Topical Usage Notice */}
<section className="bg-white rounded-[2.5rem] border border-medical-gray-200 p-8 md:p-10 shadow-md space-y-8"> {fullProduct.categorySlug === 'special-care' ||
<div className="flex items-center gap-4"> fullProduct.name.toLowerCase().includes('pfotenpflege') ||
<div className="w-12 h-12 bg-canina-blue text-white rounded-2xl flex items-center justify-center shadow-lg"> fullProduct.name.toLowerCase().includes('augenpflege') ||
<Clock className="w-6 h-6" /> fullProduct.unit?.includes('ml Lotion') ||
</div> fullProduct.unit?.includes('Balsam') ||
<div> fullProduct.nameFa?.includes('کرم') ||
<h3 className="text-2xl font-black text-medical-gray-900 italic font-vazir">ماشین حساب هوشمند مصرف</h3> fullProduct.nameFa?.includes('قطره تمیزکننده') ||
<p className="text-xs text-medical-gray-400 font-bold font-vazir mt-1">تخمین دقیق دوز روزانه و ماندگاری هر بسته بر اساس مشخصات پت شما</p> fullProduct.nameFa?.includes('اسپری') ? (
</div> <section className="bg-white rounded-[2.5rem] border border-medical-gray-200 p-8 md:p-10 shadow-md space-y-6">
</div> <div className="flex items-center gap-4">
<div className="w-12 h-12 bg-canina-blue text-white rounded-2xl flex items-center justify-center shadow-lg">
<div className="grid md:grid-cols-2 gap-8 items-center"> <Sparkles className="w-6 h-6" />
{/* Manual Slider Inputs */}
<div className="bg-medical-gray-50 rounded-[2rem] p-6 border border-medical-gray-100 space-y-6">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-medical-gray-500 font-vazir">وزن پت (کیلوگرم)</span>
<span className="text-lg font-black text-canina-blue font-vazir">{toPersian(weight)} ک‌گ</span>
</div> </div>
<input <div>
type="range" <h3 className="text-2xl font-black text-medical-gray-900 italic font-vazir">نحوه استفاده موضعی و کاربرد</h3>
min="1" max="100" <p className="text-xs text-medical-gray-400 font-bold font-vazir mt-1">محصولات بهداشتی و بهداشت موضعی نیاز به دوزبندی بر اساس وزن ندارند</p>
value={weight}
onChange={(e) => setWeight(parseInt(e.target.value))}
className="w-full h-2 bg-medical-gray-200 rounded-lg appearance-none cursor-pointer accent-canina-blue [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-canina-blue [&::-webkit-slider-thumb]:appearance-none"
/>
<div className="space-y-2">
<span className="text-[10px] font-black text-medical-gray-400 font-vazir">سن حیوان</span>
<div className="flex gap-2">
<button onClick={() => setPetType("young")} className={`flex-1 py-3 rounded-xl text-xs font-black transition-all border ${petType === "young" ? 'bg-canina-blue border-canina-blue text-white shadow-lg' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}>جوان</button>
<button onClick={() => setPetType("adult")} className={`flex-1 py-3 rounded-xl text-xs font-black transition-all border ${petType === "adult" ? 'bg-canina-blue border-canina-blue text-white shadow-lg' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}>بالغ</button>
</div>
</div> </div>
</div> </div>
{/* Calculation Results Output */} <div className="bg-medical-gray-50 rounded-[2rem] p-6 border border-medical-gray-100 flex items-start gap-4">
<div className="bg-canina-blue rounded-[2rem] p-8 text-white border-b-8 border-white/20 shadow-2xl flex flex-col justify-between h-full min-h-[220px]"> <div className="w-10 h-10 rounded-xl bg-canina-blue/10 text-canina-blue flex items-center justify-center font-black flex-shrink-0">
<div className="flex items-center gap-2 mb-4 opacity-90 border-b border-white/10 pb-3"> ℹ️
<Sparkles className="w-4 h-4 text-white" />
<span className="text-[10px] font-bold uppercase tracking-widest font-vazir text-white">تخمین هوش مصنوعی (AI Advice)</span>
</div> </div>
<div className="flex items-center justify-between"> <div>
<div> <h4 className="text-sm font-black text-medical-gray-900 mb-1 font-vazir">راهنمای مصرف موضعی</h4>
<div className="text-3xl font-black font-vazir text-white">{toPersian(calculation.duration)} روز</div> <p className="text-xs text-medical-gray-600 font-bold font-vazir leading-relaxed">
<div className="text-[10px] font-bold text-white/80 font-vazir whitespace-nowrap mt-1">تخمین اتمام مصرف بسته</div> این محصول یک فرآورده موضعی/بهداشتی است و مقدار مصرف آن به صورت موضعی (روی پوست، چشم یا پنجه) اعمال می‌شود و وابسته به وزن بدن حیوان نمی‌باشد. ماندگاری بسته بسته به میزان و تکرار مصرف روزانه شما متفاوت خواهد بود.
</div> </p>
<div className="text-left">
<div className="text-2xl font-black font-vazir text-white">{toPersian(calculation.dailyDose)}</div>
<div className="text-[10px] font-bold text-white/80 font-vazir whitespace-nowrap mt-1">دوز روزانه ({calculation.unit === 'g' ? 'گرم' : calculation.unit})</div>
</div>
</div> </div>
<p className="text-[11px] text-white/60 font-bold font-vazir mt-6 leading-relaxed">
* دوزها تخمینی هستند؛ توصیه می‌شود برای مصرف دقیق کاتالوگ محصول را مطالعه کنید.
</p>
</div> </div>
</div> </section>
</section> ) : (
<section className="bg-white rounded-[2.5rem] border border-medical-gray-200 p-8 md:p-10 shadow-md space-y-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-canina-blue text-white rounded-2xl flex items-center justify-center shadow-lg">
<Clock className="w-6 h-6" />
</div>
<div>
<h3 className="text-2xl font-black text-medical-gray-900 italic font-vazir">ماشین حساب هوشمند مصرف</h3>
<p className="text-xs text-medical-gray-400 font-bold font-vazir mt-1">تخمین دقیق دوز روزانه و ماندگاری هر بسته بر اساس مشخصات پت شما</p>
</div>
</div>
<div className="grid md:grid-cols-2 gap-8 items-center">
{/* Manual Slider Inputs */}
<div className="bg-medical-gray-50 rounded-[2rem] p-6 border border-medical-gray-100 space-y-6">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-medical-gray-500 font-vazir">وزن پت (کیلوگرم)</span>
<span className="text-lg font-black text-canina-blue font-vazir">{toPersian(weight)} ک‌گ</span>
</div>
<input
type="range"
min="1" max="100"
value={weight}
onChange={(e) => setWeight(parseInt(e.target.value))}
className="w-full h-2 bg-medical-gray-200 rounded-lg appearance-none cursor-pointer accent-canina-blue [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-canina-blue [&::-webkit-slider-thumb]:appearance-none"
/>
<div className="space-y-2">
<span className="text-[10px] font-black text-medical-gray-400 font-vazir">سن حیوان</span>
<div className="flex gap-2">
<button onClick={() => setPetType("young")} className={`flex-1 py-3 rounded-xl text-xs font-black transition-all border ${petType === "young" ? 'bg-canina-blue border-canina-blue text-white shadow-lg' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}>جوان</button>
<button onClick={() => setPetType("adult")} className={`flex-1 py-3 rounded-xl text-xs font-black transition-all border ${petType === "adult" ? 'bg-canina-blue border-canina-blue text-white shadow-lg' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}>بالغ</button>
</div>
</div>
</div>
{/* Calculation Results Output */}
<div className="bg-canina-blue rounded-[2rem] p-8 text-white border-b-8 border-white/20 shadow-2xl flex flex-col justify-between h-full min-h-[220px]">
<div className="flex items-center gap-2 mb-4 opacity-90 border-b border-white/10 pb-3">
<Sparkles className="w-4 h-4 text-white" />
<span className="text-[10px] font-bold uppercase tracking-widest font-vazir text-white">تخمین هوش مصنوعی (AI Advice)</span>
</div>
<div className="flex items-center justify-between">
<div>
<div className="text-3xl font-black font-vazir text-white">{toPersian(calculation.duration)} روز</div>
<div className="text-[10px] font-bold text-white/80 font-vazir whitespace-nowrap mt-1">تخمین اتمام مصرف بسته</div>
</div>
<div className="text-left">
<div className="text-2xl font-black font-vazir text-white">{toPersian(calculation.dailyDose)}</div>
<div className="text-[10px] font-bold text-white/80 font-vazir whitespace-nowrap mt-1">دوز روزانه ({calculation.unit === 'g' ? 'گرم' : calculation.unit})</div>
</div>
</div>
<p className="text-[11px] text-white/60 font-bold font-vazir mt-6 leading-relaxed">
* دوزها تخمینی هستند؛ توصیه می‌شود برای مصرف دقیق کاتالوگ محصول را مطالعه کنید.
</p>
</div>
</div>
</section>
)}
{/* Ingredients Section */} {/* Ingredients Section */}
<section className="space-y-8"> <section className="space-y-8">
@ -410,7 +447,17 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
<h4 className="text-xs font-black text-medical-gray-400 uppercase tracking-widest mb-6 font-vazir">مواد تشکیل‌دهنده برتر</h4> <h4 className="text-xs font-black text-medical-gray-400 uppercase tracking-widest mb-6 font-vazir">مواد تشکیل‌دهنده برتر</h4>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{product.main_ingredients.map((ing, idx) => { {product.main_ingredients.map((ing, idx) => {
const termKey = termKeys.find(key => ing.includes(key)); const termKey = termKeys.find(key => {
const termObj = scientificTerms[key] || SCIENTIFIC_TERMS[key];
const termName = termObj?.term || key;
const ingClean = ing.toLowerCase();
const keyClean = key.toLowerCase();
const termNameClean = termName.toLowerCase();
return ingClean.includes(keyClean) ||
keyClean.includes(ingClean) ||
ingClean.includes(termNameClean) ||
termNameClean.includes(ingClean);
});
return ( return (
<div key={idx} className="bg-medical-gray-50 border border-medical-gray-100 px-5 py-3 rounded-2xl flex items-center gap-3 group hover:border-canina-blue/30 transition-all"> <div key={idx} className="bg-medical-gray-50 border border-medical-gray-100 px-5 py-3 rounded-2xl flex items-center gap-3 group hover:border-canina-blue/30 transition-all">
<span className="text-sm font-bold text-medical-gray-700 font-vazir"> <span className="text-sm font-bold text-medical-gray-700 font-vazir">

View File

@ -6,6 +6,7 @@ import { motion } from "motion/react";
import { Product } from "../lib/data/products"; import { Product } from "../lib/data/products";
import { productService } from "../lib/services/productService"; import { productService } from "../lib/services/productService";
import Link from "next/link";
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
export default function SearchResultsPage({ query }: { query: string }) { export default function SearchResultsPage({ query }: { query: string }) {
@ -64,46 +65,50 @@ export default function SearchResultsPage({ query }: { query: string }) {
<motion.div <motion.div
key={product.id} key={product.id}
layoutId={product.id} layoutId={product.id}
onClick={() => router.push(`/shop/${product.slug || product.id}`)} className="h-full"
className="bg-white rounded-[2.5rem] p-8 border border-medical-gray-200 group cursor-pointer hover:shadow-2xl hover:shadow-canina-blue/10 transition-all flex flex-col relative"
> >
<div className="relative aspect-square mb-8 bg-medical-gray-50 rounded-[2rem] overflow-hidden p-6 flex items-center justify-center"> <Link
<img href={`/shop/${product.slug || product.id}`}
src={product.image} className="bg-white rounded-[2.5rem] p-8 border border-medical-gray-200 group hover:shadow-2xl hover:shadow-canina-blue/10 transition-all flex flex-col relative h-full block"
alt={product.nameFa || product.name} >
className="w-full h-full object-contain group-hover:scale-110 transition-transform duration-500" <div className="relative aspect-square mb-8 bg-medical-gray-50 rounded-[2rem] overflow-hidden p-6 flex items-center justify-center">
referrerPolicy="no-referrer" <img
/> src={product.image}
{product.specialBadge && ( alt={product.nameFa || product.name}
<div className="absolute top-4 right-4 bg-medical-gray-900 text-white text-[8px] font-black px-3 py-1 rounded-full uppercase tracking-widest"> className="w-full h-full object-contain group-hover:scale-110 transition-transform duration-500"
{product.specialBadge} referrerPolicy="no-referrer"
</div> />
)} {product.specialBadge && (
{compatibility && ( <div className="absolute top-4 right-4 bg-medical-gray-900 text-white text-[8px] font-black px-3 py-1 rounded-full uppercase tracking-widest">
<div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${ {product.specialBadge}
compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' : </div>
compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600' )}
}`}> {compatibility && (
{compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />} <div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${
{compatibility.text} compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' :
</div> compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600'
)} }`}>
</div> {compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />}
{compatibility.text}
<div className="flex-1"> </div>
<span className="text-[10px] font-black text-canina-blue uppercase tracking-widest mb-2 block">{product.category}</span> )}
<h3 className="text-xl font-black text-medical-gray-900 mb-2 leading-tight group-hover:text-canina-blue transition-colors">{product.nameFa || product.name}</h3>
<p className="text-xs text-medical-gray-400 line-clamp-2 leading-relaxed mb-6">{product.benefits}</p>
</div>
<div className="flex items-center justify-between pt-6 border-t border-medical-gray-100 italic">
<div>
<span className="text-lg font-black text-medical-gray-900">{product.price}</span>
</div> </div>
<button className="bg-medical-gray-900 text-white w-10 h-10 rounded-xl flex items-center justify-center group-hover:bg-canina-blue transition-colors">
<ChevronLeft className="w-5 h-5" /> <div className="flex-1">
</button> <span className="text-[10px] font-black text-canina-blue uppercase tracking-widest mb-2 block">{product.category}</span>
</div> <h3 className="text-xl font-black text-medical-gray-900 mb-2 leading-tight group-hover:text-canina-blue transition-colors">{product.nameFa || product.name}</h3>
<p className="text-xs text-medical-gray-400 line-clamp-2 leading-relaxed mb-6">{product.benefits}</p>
</div>
<div className="flex items-center justify-between pt-6 border-t border-medical-gray-100 italic">
<div>
<span className="text-lg font-black text-medical-gray-900">{product.price}</span>
</div>
<div className="bg-medical-gray-900 text-white w-10 h-10 rounded-xl flex items-center justify-center group-hover:bg-canina-blue transition-colors">
<ChevronLeft className="w-5 h-5" />
</div>
</div>
</Link>
</motion.div> </motion.div>
) )
})} })}
@ -124,12 +129,12 @@ export default function SearchResultsPage({ query }: { query: string }) {
<h3 className="text-2xl font-black text-medical-gray-900 mb-10 italic">محصولات پیشنهادی (پرفروش)</h3> <h3 className="text-2xl font-black text-medical-gray-900 mb-10 italic">محصولات پیشنهادی (پرفروش)</h3>
<div className="grid md:grid-cols-3 gap-8 opacity-60 hover:opacity-100 transition-opacity"> <div className="grid md:grid-cols-3 gap-8 opacity-60 hover:opacity-100 transition-opacity">
{bestSellers.map(p => ( {bestSellers.map(p => (
<div key={p.id} onClick={() => router.push(`/shop/${p.id}`)} className="cursor-pointer group"> <Link key={p.id} href={`/shop/${p.slug || p.id}`} className="cursor-pointer group block">
<div className="aspect-square bg-white rounded-[2rem] p-6 mb-4 border border-medical-gray-100 flex items-center justify-center"> <div className="aspect-square bg-white rounded-[2rem] p-6 mb-4 border border-medical-gray-100 flex items-center justify-center">
<img src={p.image} className="w-40 h-40 object-contain group-hover:scale-105 transition-transform" /> <img src={p.image} className="w-40 h-40 object-contain group-hover:scale-105 transition-transform" />
</div> </div>
<h4 className="font-black text-medical-gray-900 text-center">{p.name}</h4> <h4 className="font-black text-medical-gray-900 text-center">{p.name}</h4>
</div> </Link>
))} ))}
</div> </div>
</div> </div>

View File

@ -12,7 +12,8 @@ import {
Bone, Bone,
Sparkles, Sparkles,
Pill, Pill,
Sparkle Sparkle,
Check
} from "lucide-react"; } from "lucide-react";
import { toPersian, cn } from "../lib/utils"; import { toPersian, cn } from "../lib/utils";
@ -25,13 +26,24 @@ interface SmartAdvisorProps {
onComplete?: (data: any) => void; onComplete?: (data: any) => void;
} }
const MEDICAL_OPTIONS = [ const CURRENT_SYMPTOMS = [
{ id: "joint_surgery", label: "جراحی مفاصل", condition: "جراحی مفاصل" }, { id: "joint_pain", label: "درد مفاصل و لنگیدن", condition: "درد مفاصل" },
{ id: "recent_birth", label: "زایمان اخیر", condition: "زایمان اخیر" }, { id: "hair_loss", label: "ریزش مو، خارش و خشکی پوست", condition: "ریزش مو" },
{ id: "pregnancy", label: "بارداری", condition: "بارداری" }, { id: "diarrhea", label: "اسهال، یبوست و مشکلات گوارشی", condition: "اسهال" },
{ id: "digestion", label: "مشکلات گوارشی", condition: "مشکلات گوارشی" }, { id: "appetite", label: "بی‌اشتهایی و کاهش وزن", condition: "بی‌اشتهایی" },
{ id: "hair_loss", label: "ریزش موی شدید", condition: "ریزش موی شدید" }, { id: "dental", label: "جرم دندان و بوی بد دهان", condition: "جرم دندان" },
{ id: "appetite", label: "بی‌اشتهایی", condition: "بی‌اشتهایی" }, { id: "eye", label: "ترشحات یا مشکلات چشم", condition: "چشم" },
{ id: "puppy_growth", label: "رشد سریع و تعادل استخوانی", condition: "رشد سریع توله‌سگ" },
{ id: "tendon", label: "سفتی عضلات و ضعف تاندون", condition: "تاندون" },
];
const MEDICAL_HISTORIES = [
{ id: "joint_surgery", label: "سابقه جراحی مفاصل/استخوان", condition: "جراحی مفاصل" },
{ id: "recent_birth", label: "زایمان اخیر / شیردهی فعال", condition: "زایمان اخیر" },
{ id: "pregnancy", label: "دوران بارداری", condition: "بارداری" },
{ id: "weakness", label: "ضعف بعد از بیماری / نقاهت", condition: "ضعف بعد از بیماری" },
{ id: "heart", label: "نارسایی یا حساسیت قلبی", condition: "نارسایی قلبی" },
{ id: "parasite", label: "سابقه آلودگی انگلی", condition: "آلودگی انگلی" },
]; ];
export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorProps & { rules?: any[] }) { export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorProps & { rules?: any[] }) {
@ -41,17 +53,6 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
const { addPet, setActivePet } = usePetStore(); const { addPet, setActivePet } = usePetStore();
const texts = useSettingsStore(state => state.texts); const texts = useSettingsStore(state => state.texts);
const getText = useSettingsStore(state => state.getText); const getText = useSettingsStore(state => state.getText);
const medicalOptions = React.useMemo(() => {
const jsonStr = texts['medical_options'];
if (jsonStr) {
try {
return JSON.parse(jsonStr) as typeof MEDICAL_OPTIONS;
} catch (e) {
console.error("Failed to parse medical_options JSON:", e);
}
}
return MEDICAL_OPTIONS;
}, [texts]);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
@ -67,7 +68,8 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
const [weight, setWeight] = useState<number>(10); const [weight, setWeight] = useState<number>(10);
const [activityLevel, setActivityLevel] = useState<"کم" | "متوسط" | "زیاد">("متوسط"); const [activityLevel, setActivityLevel] = useState<"کم" | "متوسط" | "زیاد">("متوسط");
// Step 3 Data // Step 3 Data (Symptoms) & Step 4 Data (History)
const [currentSymptoms, setCurrentSymptoms] = useState<string[]>([]);
const [medicalConditions, setMedicalConditions] = useState<string[]>([]); const [medicalConditions, setMedicalConditions] = useState<string[]>([]);
const handleNext = () => { const handleNext = () => {
@ -84,8 +86,9 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
const handleBack = () => setStep(s => s - 1); const handleBack = () => setStep(s => s - 1);
const handleSubmit = () => { const handleSubmit = () => {
const combinedConditions = Array.from(new Set([...currentSymptoms, ...medicalConditions]));
const data = { const data = {
name, type, breed, age, weight, activityLevel, medicalConditions name, type, breed, age, weight, activityLevel, medicalConditions: combinedConditions
}; };
if (onComplete) { if (onComplete) {
onComplete(data); onComplete(data);
@ -102,28 +105,87 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
}; };
return ( return (
<section id="canino-advisor" className="py-12 bg-white"> <section id="canino-advisor" className="py-16 bg-gradient-to-b from-white via-medical-gray-50/50 to-white">
<div className="max-w-4xl mx-auto px-4"> <div className="max-w-5xl mx-auto px-4">
<div className="text-center mb-8"> {/* Header Title */}
<div className="text-center mb-10">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 rounded-full mb-4" className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 rounded-full mb-4 border border-canina-blue/10"
> >
<Sparkle className="w-4 h-4 text-canina-blue" /> <Sparkle className="w-4 h-4 text-canina-blue animate-spin" />
<span className="text-canina-blue text-xs font-black uppercase tracking-widest font-vazir">{getText('advisor_badge', "تکنولوژی پایش هوشمند")}</span> <span className="text-canina-blue text-xs font-black uppercase tracking-widest font-vazir">{getText('advisor_badge', "سیستم پایش و تجویز هوشمند")}</span>
</motion.div> </motion.div>
<h2 className="text-4xl lg:text-5xl font-black text-medical-gray-900 mb-4 font-lalezar tracking-wide">{getText('advisor_title', "دستیار سلامت کانینا")}</h2> <h2 className="text-4xl lg:text-5xl font-black text-medical-gray-900 mb-4 font-lalezar tracking-wide">{getText('advisor_title', "دستیار سلامت و الگوریتم پایش کانینا")}</h2>
<p className="text-medical-gray-600 font-medium text-base font-shabnam">{getText('advisor_subtitle', "فقط چند قدم تا صدور شناسنامه هوشمند و دریافت پیشنهادات تخصصی")}</p> <p className="text-medical-gray-600 font-medium text-base font-shabnam max-w-2xl mx-auto leading-relaxed">
{getText('advisor_subtitle', "با ثبت اطلاعات همدم خود در ۴ قدم ساده، از الگوریتم علمی کانینا برای تجویز دقیق مکمل و پایش سلامت بهره‌مند شوید.")}
</p>
</div> </div>
{/* Value Proposition Cards (Explaining Why & How) */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="grid md:grid-cols-3 gap-6 mb-12"
>
<div className="bg-white p-6 rounded-[2.5rem] border border-medical-gray-200/80 shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
<div>
<div className="w-12 h-12 bg-canina-blue/10 rounded-2xl flex items-center justify-center text-canina-blue mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm">
<Pill className="w-6 h-6" />
</div>
<h3 className="text-lg font-black text-medical-gray-900 mb-2 font-vazir">چرا ثبت شناسنامه؟</h3>
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
مکمل‌های دارویی کانینا غلیظ و تخصصی هستند؛ الگوریتم هوشمند ما بر اساس سن، وزن و جثه پت شما، دوز دقیق مصرفی روزانه را محاسبه می‌کند تا حداکثر اثرگذاری ایجاد شود.
</p>
</div>
<div className="mt-4 pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
<Check className="w-3.5 h-3.5" />
تضمین ایمنی و عدم اوردوز
</div>
</div>
<div className="bg-white p-6 rounded-[2.5rem] border border-medical-gray-200/80 shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
<div>
<div className="w-12 h-12 bg-canina-blue/10 rounded-2xl flex items-center justify-center text-canina-blue mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm">
<Activity className="w-6 h-6" />
</div>
<h3 className="text-lg font-black text-medical-gray-900 mb-2 font-vazir">تطبیق علائم بالینی</h3>
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
با انتخاب علائمی مثل لنگیدن، ریزش مو، یا اسهال، موتور هوشمند ما دقیق‌ترین راهکار مکمل آلمانی مرتبط را به همراه دلایل علمی به شما پیشنهاد می‌دهد.
</p>
</div>
<div className="mt-4 pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
<Check className="w-3.5 h-3.5" />
پیشنهاد هدفمند و تخصصی
</div>
</div>
<div className="bg-white p-6 rounded-[2.5rem] border border-medical-gray-200/80 shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
<div>
<div className="w-12 h-12 bg-canina-blue/10 rounded-2xl flex items-center justify-center text-canina-blue mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm">
<ShieldCheck className="w-6 h-6" />
</div>
<h3 className="text-lg font-black text-medical-gray-900 mb-2 font-vazir">پایش موجودی و یادآور</h3>
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
پس از ایجاد شناسنامه، زمان‌های مصرف مکمل‌ها تنظیم شده و سیستم قبل از اتمام دوره درمانی، پیام هشدار شارژ مجدد به شما ارسال خواهد کرد.
</p>
</div>
<div className="mt-4 pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
<Check className="w-3.5 h-3.5" />
تکمیل منظم دوره درمان
</div>
</div>
</motion.div>
<div className="bg-medical-gray-50 rounded-[3.5rem] border border-medical-gray-100 p-8 lg:p-12 shadow-2xl shadow-medical-gray-200/50 relative overflow-hidden"> <div className="bg-medical-gray-50 rounded-[3.5rem] border border-medical-gray-100 p-8 lg:p-12 shadow-2xl shadow-medical-gray-200/50 relative overflow-hidden">
{/* Progress Indicator */} {/* Progress Indicator */}
<div className="absolute top-0 left-0 w-full h-2 bg-medical-gray-200"> <div className="absolute top-0 left-0 w-full h-2 bg-medical-gray-200">
<motion.div <motion.div
className="h-full bg-canina-blue" className="h-full bg-canina-blue"
initial={{ width: "0%" }} initial={{ width: "0%" }}
animate={{ width: `${(step / 3) * 100}%` }} animate={{ width: `${(step / 4) * 100}%` }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
/> />
</div> </div>
@ -277,7 +339,7 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
onClick={handleNext} onClick={handleNext}
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]" className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
> >
{getText('advisor_next_final', "گام نهایی سلامت")} {getText('advisor_next_symptoms', "بررسی علائم بالینی فعلی")}
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
</div> </div>
@ -293,12 +355,63 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
className="space-y-8" className="space-y-8"
> >
<div className="text-center"> <div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step3_title', "گام آخر: سوابق و حساسیت‌ها")}</h3> <h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step3_symptoms_title', "گام سوم: علائم بالینی فعلی")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step3_desc', "در صورت وجود هر یک از موارد زیر، آن را تیک بزنید")}</p> <p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر هیچ‌کدام از علائم زیر را تجربه می‌کند؟")}</p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{medicalOptions.map((opt) => ( {CURRENT_SYMPTOMS.map((opt) => (
<button
key={opt.id}
onClick={() => {
setCurrentSymptoms(prev =>
prev.includes(opt.condition) ? prev.filter(c => c !== opt.condition) : [...prev, opt.condition]
);
}}
className={`flex items-center gap-4 p-5 rounded-2xl border-2 transition-all text-right focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${currentSymptoms.includes(opt.condition) ? 'border-canina-blue bg-white shadow-md' : 'border-transparent bg-white/50 text-medical-gray-600 hover:bg-white'}`}
>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center border ${currentSymptoms.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 text-transparent'}`}>
<ShieldCheck className="w-5 h-5" />
</div>
<span className={`text-sm font-black font-vazir ${currentSymptoms.includes(opt.condition) ? 'text-canina-blue' : 'text-medical-gray-900'}`}>{opt.label}</span>
</button>
))}
</div>
<div className="flex gap-4 pt-4">
<button
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
<ChevronRight className="w-5 h-5" />
{getText('advisor_back', "قبلی")}
</button>
<button
onClick={handleNext}
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
{getText('advisor_next_history', "سوابق پزشکی و جراحی")}
<ChevronLeft className="w-5 h-5" />
</button>
</div>
</motion.div>
)}
{step === 4 && (
<motion.div
key="step4"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step4_history_title', "گام چهارم: سوابق پزشکی و جراحی")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{MEDICAL_HISTORIES.map((opt) => (
<button <button
key={opt.id} key={opt.id}
onClick={() => { onClick={() => {

View File

@ -32,28 +32,31 @@ export default function Tooltip({ termKey, children, onWikiNavigate }: TooltipPr
initial={{ opacity: 0, y: 10, scale: 0.95 }} initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }} exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-64 p-4 bg-medical-gray-900 text-white rounded-2xl shadow-2xl z-[100] text-sm" className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-72 p-5 bg-white text-medical-gray-900 rounded-2xl shadow-2xl border border-medical-gray-200 z-[100] text-sm"
dir="rtl" dir="rtl"
> >
<div className="flex items-center gap-2 mb-2 text-canina-blue"> <div className="flex items-center gap-2 mb-2 text-canina-blue">
<Info className="w-4 h-4" /> <Info className="w-4 h-4" />
<span className="font-black text-[10px] uppercase tracking-widest">دانشنامه علمی کانینا</span> <span className="font-black text-[11px] uppercase tracking-widest text-canina-blue">دانشنامه علمی کانینا</span>
</div> </div>
<h5 className="font-black mb-1">{termData.term}</h5> <h5 className="font-black text-base text-medical-gray-900 mb-2 leading-snug">{termData.term}</h5>
<p className="text-white/70 text-xs leading-relaxed mb-3"> <p className="text-medical-gray-600 text-xs leading-relaxed mb-4 font-bold">
{termData.definition} {termData.definition}
</p> </p>
{termData.wikiId && ( {termData.wikiId && (
<button <button
onClick={() => onWikiNavigate?.(termData.wikiId!)} onClick={(e) => {
className="flex items-center gap-1.5 text-canina-blue hover:text-white transition-colors text-[10px] font-bold" e.stopPropagation();
onWikiNavigate?.(termData.wikiId!);
}}
className="flex items-center justify-between text-canina-blue hover:text-blue-700 transition-colors text-xs font-black cursor-pointer pt-2 border-t border-medical-gray-100 w-full"
> >
<span>مطالعه مقاله کامل</span> <span>مطالعه مقاله کامل</span>
<ExternalLink className="w-3 h-3" /> <ExternalLink className="w-3.5 h-3.5" />
</button> </button>
)} )}
{/* Arrow */} {/* Arrow */}
<div className="absolute top-full left-1/2 -translate-x-1/2 border-8 border-transparent border-t-medical-gray-900" /> <div className="absolute top-full left-1/2 -translate-x-1/2 border-8 border-transparent border-t-white" />
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>

View File

@ -1055,7 +1055,9 @@ export const PRODUCTS: Product[] = [
symptoms: ["توله بدون مادر", "نیاز به شیردهی مصنوعی"], symptoms: ["توله بدون مادر", "نیاز به شیردهی مصنوعی"],
suitableFor: "سگ", suitableFor: "سگ",
calculateDosage: (weight) => { calculateDosage: (weight) => {
return { quantity: 1, unit: "قسمت", description: "مخلوط ۱:۴ با آب ۵۰ درجه سانتی‌گراد" }; // 1 part milk powder (approx 10g per kg of puppy weight) mixed with 4 parts water
const grams = Math.max(10, Math.round(weight * 10));
return { quantity: grams, unit: "گرم پودر", description: `مخلوط ${grams} گرم پودر با ${grams * 4} میلی‌لیتر آب ۵۰ درجه` };
}, },
analysis: { "پروتئین": "۳۰٪" }, analysis: { "پروتئین": "۳۰٪" },
feedingAdvice: "هر ۲ ساعت در هفته اول، سپس هر ۴ ساعت.", feedingAdvice: "هر ۲ ساعت در هفته اول، سپس هر ۴ ساعت.",
@ -1086,8 +1088,10 @@ export const PRODUCTS: Product[] = [
symptoms: ["توله ۳ هفته", "شروع غذای کمکی"], symptoms: ["توله ۳ هفته", "شروع غذای کمکی"],
suitableFor: "سگ", suitableFor: "سگ",
contraindications: ["مصرف همزمان ویتامین D2 مجاز نیست"], contraindications: ["مصرف همزمان ویتامین D2 مجاز نیست"],
calculateDosage: () => { calculateDosage: (weight) => {
return { quantity: 1, unit: "قسمت", description: "مخلوط ۱:۳ با آب ۵۰ درجه" }; // 1 part porridge powder (approx 15g per kg of puppy weight) mixed with 3 parts warm water
const grams = Math.max(15, Math.round(weight * 15));
return { quantity: grams, unit: "گرم فرنی", description: `مخلوط ${grams} گرم فرنی با ${grams * 3} میلی‌لیتر آب ۵۰ درجه` };
}, },
analysis: { "پروتئین": "۲۷٪" }, analysis: { "پروتئین": "۲۷٪" },
feedingAdvice: "کم‌کم از هفته سوم به عنوان غذای کمکی معرفی شود.", feedingAdvice: "کم‌کم از هفته سوم به عنوان غذای کمکی معرفی شود.",

View File

@ -125,6 +125,106 @@ export const SCIENTIFIC_TERMS: Record<string, ScientificTerm> = {
definition: "روغن‌های تسکین‌دهنده، ضدباکتری و التیام‌بخش پوست جهت بهبود سوزش، زخم‌های سطحی و خشکی پنجه‌ها.", definition: "روغن‌های تسکین‌دهنده، ضدباکتری و التیام‌بخش پوست جهت بهبود سوزش، زخم‌های سطحی و خشکی پنجه‌ها.",
wikiId: "calendula-sea-buckthorn" wikiId: "calendula-sea-buckthorn"
}, },
"نقره کلوئیدی": {
term: "نقره کلوئیدی (Colloidal Silver)",
definition: "محلول استریل حاوی ذرات ریز نقره با اثرات قوی آنتی‌باکتریال و ضدالتهاب طبیعی جهت پاکسازی ملتحمه چشم و جلوگیری از عفونت‌های چشمی و پوستی.",
wikiId: "colloidal-silver"
},
"رتینول پالمیتات": {
term: "رتینول پالمیتات / ویتامین A (Retinol Palmitate)",
definition: "فرمی پایدار از ویتامین A دارویی که باعث ترمیم لایه‌های مخاطی قرنیه چشم، بازسازی اپیتلیوم آسیب‌دیده و محافظت در برابر خشکی قرنیه می‌شود.",
wikiId: "retinol-palmitate"
},
"روغن خولان دریایی": {
term: "روغن خولان دریایی / سنجد دریایی (Sea Buckthorn Oil)",
definition: "روغن غنی از ویتامین E، امگا ۷ و آنتی‌اکسیدان‌های کمیاب برای ترمیم سریع بافت‌های آسیب‌دیده، التهابات پوستی و ترک‌های پنجه.",
wikiId: "calendula-sea-buckthorn"
},
"سنجد دریایی": {
term: "روغن سنجد دریایی (Sea Buckthorn Oil)",
definition: "روغن غنی از ویتامین E، امگا ۷ و آنتی‌اکسیدان‌های کمیاب برای ترمیم سریع بافت‌های آسیب‌دیده و ترک‌خورده پوست.",
wikiId: "calendula-sea-buckthorn"
},
"وازلین": {
term: "وازلین با گرید دارویی (Medical Vaseline)",
definition: "پوشش نرم‌کننده و ایزوله‌کننده پوست که مانع تبخیر رطوبت لایه‌های اپیدرم شده و از پنجه‌ها در برابر نمک، یخ و زبری زمین محافظت می‌کند.",
wikiId: "vaseline"
},
"پانتنول": {
term: "پانتنول / پیش‌ساز ویتامین B5 (Panthenol)",
definition: "ترکیب نفوذکننده و ترمیم‌کننده عمیق پوست که سرعت بازسازی سلولی را افزایش داده و خارش و التهاب پوست و پنجه را تسکین می‌دهد.",
wikiId: "panthenol"
},
"عصاره دانه گریپ فروت": {
term: "عصاره دانه گریپ فروت (Grapefruit Seed Extract)",
definition: "منبع غنی بیوفلاونوئیدها و آنتی‌اکسیدان‌های طبیعی که با مهار باکتری‌های مولد پلاک، از تشکیل تارتار و جرم دندان جلوگیری می‌کند.",
wikiId: "grapefruit-seed"
},
"گریپ فروت": {
term: "عصاره دانه گریپ فروت (Grapefruit Seed Extract)",
definition: "منبع غنی بیوفلاونوئیدها و آنتی‌اکسیدان‌های طبیعی که با مهار باکتری‌های مولد پلاک، از تشکیل تارتار و جرم دندان جلوگیری می‌کند.",
wikiId: "grapefruit-seed"
},
"عصاره مارگوسا": {
term: "عصاره طبیعی درخت نیم / مارگوسا (Margosa Extract)",
definition: "عصاره گیاهی دورکننده حشرات با خواص ضد انگل، کک و کنه بدون استفاده از سموم شیمیایی آفت‌کش.",
wikiId: "dodecanoic"
},
"خاک دیاتومه": {
term: "خاک دیاتومه ارگانیک (Diatomaceous Earth)",
definition: "ماده طبیعی حاصل از سنگ‌های رسوبی جلبک‌های تک‌سلولی جهت کنترل ارگانیک انگل‌ها و انباشت مواد سمی در روده.",
wikiId: "diatomaceous"
},
"کنگر فرنگی": {
term: "عصاره کنگر فرنگی (Artichoke Extract)",
definition: "ترکیب گیاهی محافظ کبد و بهبوددهنده ترشح صفرا جهت ارتقای هضم چربی‌ها و پاکسازی متابولیک.",
wikiId: "artichoke"
},
"مخمر آبجو": {
term: "مخمر آبجو (Brewer's Yeast)",
definition: "منبع غنی ویتامین‌های گروه B، اسیدهای آمینه و مواد معدنی برای سلامت پوست، درخشندگی موها و تقویت اشتهای حیوان.",
wikiId: "brewers-yeast"
},
"پوست درخت بید": {
term: "عصاره پوست درخت بید (Willow Bark Extract)",
definition: "منبع طبیعی سالیسین با خواص ضدالتهابی و تسکین‌دهنده دردهای مفصلی و تاندون‌ها در حیوانات مسن.",
wikiId: "willow-bark"
},
"زنجبیل": {
term: "عصاره زنجبیل (Ginger Extract)",
definition: "آنتی‌اکسیدان گیاهی قوی برای مهار رادیکال‌های آزاد، بهبود گردش خون در مفاصل و تسکین حالت تهوع و مشکلات گوارشی.",
wikiId: "ginger"
},
"سرم طبیعی گاو": {
term: "سرم طبیعی خون گاو (Bovine Blood Serum)",
definition: "کنسانتره غنی از پروتئین‌ها و بیواکتیواتورهای طبیعی برای تحریک اشتها و تسریع بازگشت انرژی در دوره نقاهت.",
wikiId: "bovine-serum"
},
"پودر خون گاو": {
term: "پودر خون گاو / هموگلوبین (Bovine Blood Powder)",
definition: "منبع ۱۰۰٪ طبیعی آهن آلی (هموگلوبین) و اسیدهای آمینه ضروری جهت اکسیژن‌رسانی به خون و عضله‌سازی در رژیم خام BARF.",
wikiId: "bovine-blood"
},
"چربی گوشت گاو": {
term: "چربی تصفیه شده گاو (Beef Fat Powder)",
definition: "سوخت انرژی‌بخش حیوانی زودجذب جهت وزن‌گیری سریع، تامین کالری سگ‌های کاری و افزایش اشتهای سگ‌های بدخوراک.",
wikiId: "beef-fat"
},
"روغن آرگان": {
term: "روغن آرگان خالص (Pure Argan Oil)",
definition: "روغن نایاب سرشار از فیتواسترول‌ها، اسکوالن و ویتامین E طبیعی برای درخشندگی موها، تغذیه پوست و تقویت غشاهای سلولی.",
wikiId: "argan-oil"
},
"روغن دانه گل رز": {
term: "روغن دانه گل رز (Rosehip Seed Oil)",
definition: "منبع طبیعی اسیدهای چرب امگا ۳ و پروویتامین A برای ترمیم مخاط‌ها و لایه‌های محافظ پوست.",
wikiId: "rosehip-oil"
},
"روغن گردو": {
term: "روغن گردو (Walnut Oil)",
definition: "روغن گیاهی غنی از اسیدهای چرب غیراشباع و آنتی‌اکسیدان‌ها برای سلامت سیستم عصبی و پوست.",
wikiId: "walnut-oil"
},
"تورین": { "تورین": {
term: "تورین (Taurine)", term: "تورین (Taurine)",
definition: "آمینو اسید بسیار ضروری برای گربه‌ها جهت جلوگیری از دژنراسیون شبکیه (نابینایی) و نارسایی قلبی.", definition: "آمینو اسید بسیار ضروری برای گربه‌ها جهت جلوگیری از دژنراسیون شبکیه (نابینایی) و نارسایی قلبی.",
@ -134,6 +234,171 @@ export const SCIENTIFIC_TERMS: Record<string, ScientificTerm> = {
term: "گرید دارویی (Pharmaceutical Grade)", term: "گرید دارویی (Pharmaceutical Grade)",
definition: "بالاترین سطح خلوص مواد اولیه؛ تهیه شده با استانداردهای دارویی انسان و کاملاً بی‌خطر در صورت لیسیده شدن توسط حیوان." definition: "بالاترین سطح خلوص مواد اولیه؛ تهیه شده با استانداردهای دارویی انسان و کاملاً بی‌خطر در صورت لیسیده شدن توسط حیوان."
}, },
"پلی گلیسریل-4-کاپرات": {
term: "پلی گلیسریل-4-کاپرات (Polyglyceryl-4 Caprate)",
definition: "امولسیفایر گیاهی ملایم و زیست‌تخریب‌پذیر حاصل از گلیسرول و اسیدهای چرب نارگیل جهت انحلال یکنواخت ترکیبات فعال بدون ایجاد سوزش پوست.",
wikiId: "polyglyceryl-caprate"
},
"کربومر": {
term: "کربومر دارویی (Carbomer / Polyacrylic Acid)",
definition: "پلیمر قوام‌دهنده و ویسکوزکننده فرمولاسیون‌های ژل که پایداری مواد فعال بر سطح دندان و پوست را افزایش می‌دهد.",
wikiId: "carbomer"
},
"پلی گلیسریل": {
term: "پلی گلیسریل-4-کاپرات (Polyglyceryl-4 Caprate)",
definition: "امولسیفایر گیاهی ملایم حاصل از گلیسرول و اسیدهای چرب نارگیل جهت انحلال یکنواخت ترکیبات فعال بدون ایجاد سوزش.",
wikiId: "polyglyceryl-caprate"
},
"اتانول": {
term: "اتانول دارویی (Pharmaceutical Ethanol)",
definition: "حلال استریل و عصاره‌گیر پایه حیوانی و گیاهی با غلظت استاندارد و ایمن برای نگهداری محلول‌های بیواکتیو.",
wikiId: "ethanol"
},
"دکستروز": {
term: "دکستروز / گلوکز مونوهیدرات (Dextrose)",
definition: "قند ساده زودجذب و منبع فوری انرژی سلولی برای حیوانات ضعیف، بیمار یا دچار افت قند خون.",
wikiId: "dextrose"
},
"آب پنیر": {
term: "پودر آب پنیر (Whey Powder)",
definition: "منبع غنی پروتئین‌های زودهضم، کلسیم ارگانیک و اسیدهای آمینه شاخه‌دار جهت رشد و بالانس غذایی.",
wikiId: "whey-powder"
},
"نشاسته سیب زمینی": {
term: "نشاسته سیب زمینی (Potato Starch)",
definition: "کربوهیدرات پیچیده و عامل اتصال‌دهنده طبیعی در قرص‌ها و ژل‌ها بدون ایجاد حساسیت گوارشی.",
wikiId: "potato-starch"
},
"لاکتوز": {
term: "لاکتوز دارویی (Lactose)",
definition: "قند طبیعی شیر به عنوان پایه انرژی‌بخش و طعم‌دهنده طبیعی در مکمل‌های جایگزین شیر مادر.",
wikiId: "lactose"
},
"چربی نارگیل": {
term: "چربی و روغن نارگیل (Coconut Fat)",
definition: "منبع اسیدهای چرب زنجیره متوسط (MCT) جهت تامین انرژی سریع مغز و دستگاه گوارش.",
wikiId: "coconut-fat"
},
"پروتئین ایزوله سویا": {
term: "پروتئین ایزوله سویا (Soy Protein Isolate)",
definition: "پروتئین خالص گیاهی با قابلیت هضم فوق‌العاده بالا برای ساخت بافت‌های عضلانی.",
wikiId: "soy-protein"
},
"پودر تخم مرغ کامل": {
term: "پودر تخم مرغ کامل (Whole Egg Powder)",
definition: "غذای کامل سرشار از پروتئین‌های با ارزش بیولوژیک بالا، لسیپین و اسیدهای چرب ضروری.",
wikiId: "egg-powder"
},
"کربنات کلسیم": {
term: "کربنات کلسیم (Calcium Carbonate)",
definition: "منبع آلی معدنی کلسیم برای استحکام استخوان‌ها، دندان‌ها و عملکرد صحیح انقباض عضلانی.",
wikiId: "calcium-carbonate"
},
"تری کلسیم فسفات": {
term: "تری کلسیم فسفات (Tricalcium Phosphate)",
definition: "ترکیب معدنی فوق‌العاده شکل‌دهنده ساختار استخوان‌ها (بیش از ۵۰٪) و مینای دندان‌ها (بیش از ۹۰٪).",
wikiId: "tricalcium-phosphate"
},
"ویتامین A": {
term: "ویتامین A (Retinol)",
definition: "ویتامین محلول در چربی ضروری برای سلامت بینایی، بازسازی مخاط و سیستم ایمنی.",
wikiId: "vitamins"
},
"ویتامین E": {
term: "ویتامین E (Tocopherol)",
definition: "آنتی‌اکسیدان قوی محافظ غشای سلولی در برابر استرس اکسیداتیو و رادیکال‌های آزاد.",
wikiId: "vitamins"
},
"ویتامین C": {
term: "ویتامین C (L-Ascorbic Acid)",
definition: "ویتامین تقویت‌کننده ساخت کلاژن، لثه‌ها و سیستم ایمنی بدن.",
wikiId: "vitamins"
},
"ویتامین B1": {
term: "ویتامین B1 (Thiamine)",
definition: "ویتامین حیاتی برای سیستم عصبی و متابولیسم کربوهیدرات‌ها.",
wikiId: "vitamins"
},
"ویتامین B2": {
term: "ویتامین B2 (Riboflavin)",
definition: "کوآنزیم فاکتورهای رشد، درخشندگی پوشش مویی و سلامت پوست.",
wikiId: "vitamins"
},
"ویتامین B6": {
term: "ویتامین B6 (Pyridoxine)",
definition: "ویتامین ضروری برای سنتز اسیدهای آمینه و هموگلوبین خون.",
wikiId: "vitamins"
},
"ویتامین B12": {
term: "ویتامین B12 (Cobalamin)",
definition: "فاکتور کلیدی خون‌سازی و حفظ سلامت رشته‌های عصبی.",
wikiId: "vitamins"
},
"اسید نیکوتینیک": {
term: "اسید نیکوتینیک / نیاسین (Niacin / Vitamin B3)",
definition: "ویتامین تنظیم‌کننده پیگمنت‌های پوست و متابولیسم چربی‌ها.",
wikiId: "vitamins"
},
"سلنیوم": {
term: "سلنیوم (Selenium)",
definition: "عنصر کمیاب آنتی‌اکسیدانی که همراه با ویتامین E از تخریب سلولی جلوگیری می‌کند.",
wikiId: "selenium"
},
"روی": {
term: "روی (Zinc)",
definition: "عنصر ضروری برای ترمیم زخم‌ها، رشد موها و تقویت سیستم ایمنی.",
wikiId: "zinc"
},
"مس": {
term: "مس (Copper)",
definition: "عنصر کاتالیزور در سنتز هموگلوبین و پیگمنت‌های رنگی مو.",
wikiId: "copper"
},
"آهن": {
term: "آهن (Iron)",
definition: "ماده معدنی اصلی ساخت هموگلوبین و اکسیژن‌رسانی به بافت‌ها.",
wikiId: "iron"
},
"متیونین": {
term: "متیونین (DL-Methionine)",
definition: "اسید آمینه گوگرددار ضروری برای سلامت مفاصل، کبد و رشد مو.",
wikiId: "methionine"
},
"کیک بذر کتان": {
term: "کیک بذر کتان (Flaxseed Cake)",
definition: "منبع غنی فیبرهای حل‌شونده و اسید چرب آلفا لینولنیک برای دستگاه گوارش.",
wikiId: "flaxseed"
},
"تفاله سیب": {
term: "تفاله سیب ارگانیک (Apple Pomace)",
definition: "منبع پری‌بیوتیک طبیعی پکتین جهت تنظیم فلور و حرکات روده.",
wikiId: "apple-pomace"
},
"پودر یونجه سبز": {
term: "پودر یونجه سبز (Alfalfa Powder)",
definition: "گیاه غنی از کلروفیل، ویتامین K و مواد معدنی قلیایی‌کننده.",
wikiId: "alfalfa"
},
"فیبر نخود فرنگی": {
term: "فیبر نخود فرنگی (Pea Fiber)",
definition: "فیبر غیرقابل هضم پاک‌کننده مکانیکی جدار روده و حجم‌دهنده مدفوع.",
wikiId: "pea-fiber"
},
"تفاله هویج": {
term: "تفاله هویج (Carrot Pomace)",
definition: "سرشار از بتاکاروتن و فیبر برای سلامت بینایی و گوارش.",
wikiId: "carrot-pomace"
},
"روغن کلزا": {
term: "روغن کلزا (Rapeseed Oil)",
definition: "روغن گیاهی غنی از امگا ۳ و ۶ برای لطافت و نرمی پوست.",
wikiId: "rapeseed-oil"
},
"روغن ماهی": {
term: "روغن خالص ماهی (Fish Oil)",
definition: "منبع اسیدهای چرب امگا ۳ برای سلامت قلب، مفاصل و پوست.",
wikiId: "salmon-oil"
},
"IFS": { "IFS": {
term: "استاندارد بین‌المللی IFS", term: "استاندارد بین‌المللی IFS",
definition: "سخت‌گیرانه‌ترین استاندارد ایمنی و کیفیت محصولات دارویی و غذایی در اروپا." definition: "سخت‌گیرانه‌ترین استاندارد ایمنی و کیفیت محصولات دارویی و غذایی در اروپا."