Compare commits

..

23 Commits

Author SHA1 Message Date
parsa aghaei
fd45910387 feat(admin): implement live global search, fixed position sidebar, click-to-navigate dashboard cards, and new orders count badge on sidebar
Some checks failed
Deploy Canina / deploy (push) Failing after 2m37s
2026-07-15 19:33:33 +03:30
parsa aghaei
72650dba36 fix(admin/ui): resolve tooltip overlaps by opening downwards, fix layout shifts with fixed modal content height, and add getProductImageUrl helper to dynamically resolve hardcoded dev base image URLs 2026-07-15 18:26:58 +03:30
parsa aghaei
4860a0e2e3 feat(admin/seo): sticky google SERP simulator, tooltip hints on all fields, broken image layout fix, dynamic view online url helper and map slug parameter in application 2026-07-15 18:17:44 +03:30
parsa aghaei
ac4813ebde feat(admin/seo): add Google SERP Simulator in SEO tab, View Online deep link in modal header & seed scientific taglines in database 2026-07-15 17:56:37 +03:30
parsa aghaei
8345410376 feat(admin): implement symptoms management in admin panel product form 2026-07-15 17:49:03 +03:30
parsa aghaei
717a61a225 fix(database): fix baseSlug format in products seed to populate product symptoms successfully + format cleanup in ArchivePage 2026-07-15 17:46:27 +03:30
parsa aghaei
6dafe86344 feat: full symptoms filtering system — backend API, megamenu pills, sidebar server-side filter, PDP tags 2026-07-15 15:03:09 +03:30
parsa aghaei
4676f3a592 feat: phase2 - split product names (nameFa/nameEn) across frontend cards, admin panel, search, and cart 2026-07-15 14:33:31 +03:30
parsa aghaei
f66170cc20 feat: split Product name into nameFa and nameEn, add symptoms mapping mapping to seed-products 2026-07-15 14:08:45 +03:30
parsa aghaei
1d9b699d61 feat: implement dynamic category solutions in MegaMenu navigation and convert static footer buttons to Next.js links 2026-07-15 13:53:23 +03:30
parsa aghaei
0ccf37f922 feat: implement fully dynamic filter sidebar in backend getActiveFilters and frontend ArchivePage mapping with URL sync 2026-07-15 13:49:13 +03:30
parsa aghaei
2745125c5b fix: add comfortable right padding pr-4 to search input 2026-07-15 13:39:56 +03:30
parsa aghaei
40c9618ddf fix: fine-tune search capsule border alignment and fix RTL placeholder text using unicode ellipsis 2026-07-15 13:36:52 +03:30
parsa aghaei
24bf530e50 fix: redesign header search bar input, button borders, logo alignment, and copywriting details 2026-07-15 13:35:10 +03:30
parsa aghaei
7d93a8d7c2 fix: replace desktop and mobile onClick navigation handlers with standard next/link components in Header.tsx 2026-07-15 13:30:27 +03:30
parsa aghaei
565ef70840 fix: replace onClick router.push navigation with standard next/link components for SEO accessibility 2026-07-15 12:43:20 +03:30
parsa aghaei
4dfe36b882 fix: complete Technical SEO and Semantic HTML improvements across all routes and layout 2026-07-15 12:29:38 +03:30
parsa aghaei
27ac668e42 fix: re-seed database with exact official PDF catalog details, prices, Farsi categories, and short descriptions, and remove frontend slicing fallbacks 2026-07-15 12:18:58 +03:30
parsa aghaei
7bc4ca137e fix: refine FeaturedProducts layout, remove shield icon, position tag independently, enlarge product image, and display full first sentence in description 2026-07-15 12:11:16 +03:30
parsa aghaei
8d3a0bfafb fix: resolve title overlap and expand short description display in FeaturedProducts cards 2026-07-15 12:05:05 +03:30
parsa aghaei
a92b1a61ff fix: resolve footer email typo, unify assistant naming, add German scientific team context, add tooltips definitions, and optimize homepage featured products category diversity 2026-07-15 11:58:47 +03:30
parsa aghaei
67d6f71d7a fix: resolve product images falling back to logo by mapping real URLs in database seed, correct 2x price discrepancy to match PDF catalog exactly, and set local cover for third video 2026-07-15 11:51:10 +03:30
parsa aghaei
cf705ea121 style: improve health advisor UX, fix broken thumbnails, add Persian equivalents to product names for SEO, and refine card title alignments 2026-07-15 11:40:39 +03:30
40 changed files with 2366 additions and 1031 deletions

View File

@ -118,7 +118,8 @@ model Product {
barcode String? @unique @db.VarChar(30)
slug String @unique @db.VarChar(150)
productGroup String? @map("product_group") @db.VarChar(100)
name String @db.VarChar(200)
nameFa String @map("name_fa") @db.VarChar(200)
nameEn String @map("name_en") @db.VarChar(200)
scientificTagline String? @map("scientific_tagline") @db.VarChar(250)
description String @db.Text
shortDescription String? @map("short_description") @db.Text

View File

@ -1,298 +1,547 @@
[
{
"name": "Canina Ballaststoff Mix",
"name": "Canina Ballaststoff Mix (فیبر میکس کانینا)",
"category_slug": "immune",
"description": "فیبر میکس کانینا حاوی منابع فیبر گیاهی است که به طور روزانه رژیم غذایی خام پت را تقویت می کند. این فیبرها به عنوان پری بیوتیک برای فلور پایدار روده مهم هستند. حرکت طبیعی روده را تحریک کرده، مدفوع را حجیم می کند و تاثیر مثبتی بر تخلیه غدد مقعدی دارد. سیستم ایمنی روده را تقویت کرده و انرژی باکتری های روده بزرگ را تامین می کند. بدون غلات و پروتئین حیوانی بوده و برای آلرژی غذایی مناسب است.",
"ingredients": "تفاله سیب، پودر یونجه سبز، فیبر نخود فرنگی، تفاله هویج، کیک بذر کتان، کمپلکس الیگوساکارید و بتاگلوکان، مخمر آبجو، کنگر فرنگی، خاک دیاتومه، روغن کلزا، کربنات کلسیم. مواد تشکیل دهنده: پروتئین خام 11.9%، چربی خام 6.3%، فیبر خام 27.3%.",
"usage_and_dosage": "مناسب برای توله سگ ها، بچه گربه ها و حیوانات مسن، باردار و شیرده. پودر را با کمی آب گرم مخلوط کنید. مقدار مصرف: 1 گرم به ازای هر کیلوگرم وزن بدن، حداکثر 15 گرم در روز.",
"description": "فیبر میکس کانینا حاوی منابع فیبر گیاهی از سیب، یونجه، نخود فرنگی و هویج است که به طور روزانه رژیم غذایی خام پت را تقویت می‌کند. فیبرها برای فلور پایدار روده مهم هستند؛ به عنوان پری‌بیوتیک، آن‌ها به عنوان غذا برای باکتری‌های روده بزرگ عمل می‌کنند. اجزای گیاهی، همی‌سلولز، سلولز، لیگنین و مواد معدنی طبیعی مانند منیزیم و پتاسیم را فراهم می‌کنند. فیبر میکس کانینا را می‌توان به هر نوع غذایی اضافه کرد. حتی رژیم‌های غذایی خام و غذاهای خانگی اغلب فاقد فیبری هستند که حرکت طبیعی روده را تحریک کرده و مدفوع را حجیم‌تر می‌کند. این فیبرها می‌توانند تأثیر مثبتی بر حرکات دودی روده، مدفوع و تخلیه غدد مقعدی داشته باشند. مانان الیگوساکارید و بتا گلوکان از دیواره سلولی مخمر سیستم ایمنی روده را تقویت می‌کنند. بتا گلوکان‌ها به عنوان منبع انرژی برای باکتری‌های روده بزرگ عمل می‌کنند و توسط آن‌ها به اسیدهای چرب زنجیره کوتاه تبدیل می‌شوند که به نوبه خود به عنوان ماده مغذی برای سلول‌های دیواره روده عمل می‌کنند.",
"short_description": "فیبر گیاهی پری‌بیوتیک برای تنظیم فلور روده و بهبود گوارش سگ و گربه.",
"ingredients": "تفاله سیب، پودر یونجه سبز، فیبر نخود فرنگی، تفاله هویج، کیک بذر کتان، کمپلکس الیگوساکارید و بتا-گلوکان، مخمر آبجو، کنگر فرنگی، خاک دیاتومه، روغن کلزا، کربنات کلسیم.",
"usage_and_dosage": "گرم به ازای هر کیلوگرم وزن بدن، حداکثر 15 گرم در روز.",
"variants": [
{ "size": "100g", "art_no": "144006", "barcode": "4027565144006", "buy_price": 1550000, "sell_price": 3100000 }
{
"size": "100g",
"art_no": "144006",
"barcode": "4027565144006",
"buy_price": 775000,
"sell_price": 1550000
}
]
},
{
"name": "Canina CANHYDROX GAG",
"name": "Canina CANHYDROX GAG (کنهیدروکس جی ای جی)",
"category_slug": "joints",
"description": "ویژه سگ های بزرگ و سنگین وزن برای درمان بافت همبند، مفاصل، استخوان ها، تاندون ها و غضروف. ایده آل برای سگ های در مرحله رشد، مسن و سگ های با عملکرد بالا. از تشکیل کالوس و استحکام استخوان پشتیبانی کرده و ظرفیت بافر مفاصل را بهبود می بخشد.",
"description": "کنهیدروکس به طور ویژه برای سگ‌های بزرگ و سنگین وزن طراحی شده است و به عنوان یک درمان با کیفیت بالا برای بافت همبند، مفاصل، استخوان‌ها، تاندون‌ها، رباط‌ها، غضروف و دندان‌ها استفاده می‌شود. این محصول به ویژه برای سگ‌های جوان، سگ‌های مسن و سگ‌های با عملکرد بالا مناسب است. تری کلسیم فسفات ماده‌ای است که در استخوان (بیش از ۵۰٪) و در دندان‌ها (بیش از ۹۰٪) وجود دارد. این ماده می‌تواند از تشکیل کالوس و استحکام انعطاف‌پذیر استخوان پشتیبانی کند و تأثیر مثبتی بر مینای دندان دارد. بنابراین، این محصول برای سگ‌های بزرگ در مرحله رشد، یک مکمل ایده‌آل است. پودر گوشت صدف لب سبز می‌تواند با افزودن مواد فعال مفاصل مانند گلیکوزآمینوگلیکان‌ها و اسیدهای چرب امگا ۳، ظرفیت بافر مفاصل را بهبود بخشد. ویتامین سی بافت همبند را تقویت کرده و از لثه‌ها پشتیبانی می‌کند. ویتامین ای و سلنیوم به عنوان عوامل محافظت از سلول، اثرات آنتی‌اکسیدانی دارند. متیونین به عنوان یک اسید آمینه حاوی گوگرد، تأثیر مثبتی بر متابولیسم مفاصل دارد.",
"short_description": "مکمل تخصصی مفاصل و استخوان سگ‌های نژاد بزرگ و مسن.",
"ingredients": "30% تری کلسیم فسفات، هیدروکسی آپاتیت، مخمر آبجو، 10% پودر صدف سبز، جلبک دریایی. ویتامین سی، ویتامین ایی، سلنیوم و متیونین.",
"usage_and_dosage": "سگ های جوان از 8 هفتگی: برای پیشگیری 2 قرص و برای اندام های بدشکل 4 قرص به ازای هر 10 کیلوگرم تا 15 ماهگی. سگ های مسن و عملکردی: 4 قرص به ازای هر 10 کیلوگرم. پس از شکستگی: 4 قرص برای دوره نقاهت.",
"usage_and_dosage": "سگ های جوان از 8 هفتگی: 2 قرص به ازای هر 10 کیلوگرم وزن بدن. سگ های مسن و عملکردی: 4 قرص به ازای هر 10 کیلوگرم.",
"variants": [
{ "size": "100g (60 Tablets)", "art_no": "123490", "barcode": "4027565123490", "buy_price": 4240000, "sell_price": 8480000 }
{
"size": "100g (60 Tablets)",
"art_no": "123490",
"barcode": "4027565123490",
"buy_price": 2120000,
"sell_price": 4240000
}
]
},
{
"name": "Canina Eierschalenpulver",
"name": "Canina Eierschalenpulver (پودر پوسته تخم مرغ کانینا)",
"category_slug": "energy",
"description": "پودر پوسته تخم مرغ برای تامین کلسیم، سلامت دندان ها و فعالیت عضلات. جایگزین پودر استخوان برای سگ های دارای آلرژی به پروتئین گوشت. مناسب برای تکمیل وعده های غذایی گوشتی خام (B.A.R.F).",
"description": "پودر پوسته تخم مرغ کانینا نه تنها کلسیم معدنی مهم مورد نیاز برای متابولیسم استخوان، سلامت دندان‌ها و فعالیت عضلات را تأمین می‌کند، بلکه سایر مواد معدنی طبیعی مانند منیزیم، پتاسیم، سدیم، آهن، مس و روی را نیز تأمین می‌کند. از آنجایی که پودر پوسته تخم مرغ کانینا ریز آسیاب شده است، به خوبی تحمل می‌شود و بنابراین جذب کلسیم بسیار بالا است. پودر پوسته تخم مرغ کانینا یک جایگزین با کیفیت بالا برای پودر استخوان گوشت برای سگ‌هایی است که به پروتئین‌های گوشتی مانند گوشت گاو، مرغ یا خوک حساسیت دارند. پودر پوسته تخم مرغ کانینا به دلیل فسفر کم و کلسیم بالا، به ویژه برای تکمیل وعده‌های غذایی گوشتی خام غنی از فسفر و کم کلسیم یا آماده‌سازی‌های گوشتی پخته شده مناسب است.",
"short_description": "تامین‌کننده کلسیم ارگانیک و طبیعی مناسب رژیم خام گوشتی.",
"ingredients": "100% پوسته تخم مرغ آسیاب شده. خاکستر خام 96%، کلسیم 36%، فسفر 0.1%.",
"usage_and_dosage": "0.2 گرم به ازای هر کیلوگرم وزن بدن، حداکثر 16 گرم برای هر حیوان در روز.",
"usage_and_dosage": "0.2 گرم به ازای هر کیلوگرم وزن بدن در روز.",
"variants": [
{ "size": "250g", "art_no": "120208", "barcode": "4027565120208", "buy_price": 1460000, "sell_price": 2920000 }
{
"size": "250g",
"art_no": "120208",
"barcode": "4027565120208",
"buy_price": 730000,
"sell_price": 1460000
}
]
},
{
"name": "Canina FLEXAN",
"name": "Canina FLEXAN (فلکسان کانینا)",
"category_slug": "joints",
"description": "پشتیبانی از سیستم اسکلتی عضلانی، مفاصل متحرک و تناسب اندام. پپتیدهای کلاژن غضروف را بازسازی کرده و پودر صدف لب سبز بافت همبند را تقویت می کند.",
"ingredients": "90% کلاژن، 10% پودر گوشت صدف لب سبز. پروتئین خام 94.2%.",
"usage_and_dosage": "تغذیه روزانه برای توله سگ ها تا مسن. 2 گرم به ازای هر 10 کیلوگرم وزن بدن، حداکثر 15 گرم در روز.",
"description": "فلکسان کانینا به طور طبیعی از سیستم اسکلتی عضلانی سگ شما پشتیبانی می‌کند. مفاصل متحرک، استخوان‌های پایدار، تحرک و تناسب اندام - پپتیدهای کلاژن زیست‌فعال، بلوک‌های سازنده اولیه بدن هستند. کلاژن پروتئین ساختاری اصلی بافت همبند است. نشان داده شده است که تأمین هدفمند هیدرولیز کلاژن، سلول‌های غضروف را فعال می‌کند و بنابراین به طور بهینه از بازسازی غضروف مفاصل پشتیبانی کرده و روند تخریب آن را کند می‌کند. همچنین سلول‌های تشکیل‌دهنده استخوان را تحریک کرده و بافت استخوان را از نظر پایداري، انعطاف‌پذیري، ماده و چگالی بهبود می‌بخشد. کلاژن زیست‌فعال موجود در مواد اولیه طبیعی با منشأ حیوانی تولید می‌شود. پپتیدهای کلاژن با فراهمی زیستی عالی مشخص می‌شوند و تقریباً بدون از دست رفتن توسط بدن جذب می‌شوند. پودر گوشت صدف لب سبز از صدف لب سبز نیوزیلند بلوک‌های ساختمانی ارزشمندی (گلیکوزآمینوگلیکان‌ها) را برای غضروف مفصل فراهم می‌کند و تمام بافت همبند را تقویت می‌کند. پپتیدهای کلاژن زیست‌فعال و پودر گوشت صدف لب سبز به صورت هم‌افزایی با هم کار می‌کنند و تأثیر را بر کل سیستم اسکلتی عضلانی بهینه می‌کنند.",
"short_description": "پپتیدهای کلاژن زیست‌فعال برای بازسازی غضروف و بهبود تحرک سگ.",
"ingredients": "90% کلاژن هیدرولیز شده، 10% پودر گوشت صدف لب سبز.",
"usage_and_dosage": "2 گرم به ازای هر 10 کیلوگرم وزن بدن، حداکثر 15 گرم در روز.",
"variants": [
{ "size": "150g", "art_no": "710003", "barcode": "4027565710003", "buy_price": 3230000, "sell_price": 6460000 }
{
"size": "150g",
"art_no": "710003",
"barcode": "4027565710003",
"buy_price": 1615000,
"sell_price": 3230000
}
]
},
{
"name": "Canina HERZ VITAL",
"category_slug": "energy",
"description": "برای سگ ها و گربه های مسن دارای نارسایی قلبی. ال کارنیتین بر تامین انرژی قلب و عضلات تاثیر گذاشته و نشاط و رفاه حیوان را افزایش می دهد.",
"ingredients": "دکستروز، مخمر غیر فعال، زالزالک. حاوی ال کارنیتین.",
"usage_and_dosage": "نصف قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن، حداکثر 3 قاشق چایخوری. به مدت 6 ماه همراه غذا تجویز شود.",
"variants": [
{ "size": "100ml", "art_no": "112036", "barcode": "4027565112036", "buy_price": 4285000, "sell_price": 8570000 }
]
},
{
"name": "Canina IMMUN-BOOSTER Paste",
"category_slug": "immune",
"description": "بازیابی و نقاهت تغذیه ای با چگالی انرژی بالا. ایمونوگلوبولین های آغوز سیستم دفاعی را تقویت کرده و برای نقاهت پس از زایمان، تمرین و استرس عالی است. انتروکوک فاسیوم فلور روده را سالم نگه می دارد.",
"ingredients": "روغن های گیاهی، 20% پودر آغوز، شکر انگور. ویتامین آ، د3، ایی و تثبیت کننده فلور روده.",
"usage_and_dosage": "توله ها 0.25 میلی لیتر، سگ های متوسط 0.5-1 میلی لیتر، سگ های بزرگ 1-4 میلی لیتر در روز تا زمان بهبودی کامل.",
"variants": [
{ "size": "15ml", "art_no": "700707", "barcode": "4027565700707", "buy_price": 2990000, "sell_price": 5980000 }
]
},
{
"name": "Canina Katzenmilch",
"category_slug": "nutrition",
"description": "غذای کامل جایگزین شیر مادر با ویتامین ها و مواد معدنی. مناسب بچه گربه های بی مادر از 1 روزگی، گربه های مسن، بیمار و شیرده.",
"ingredients": "لاکتوز، پودر آب پنیر، چربی نارگیل، روغن ماهی، پروتئین 30%. غنی از ویتامین ها و 100 میلی گرم تورین.",
"usage_and_dosage": "1 قسمت شیر با 3 قسمت آب ولرم مخلوط شود. به بچه گربه های 1 تا 14 روزه هر 2 ساعت تغذیه شود.",
"variants": [
{ "size": "150g", "art_no": "230808", "barcode": "4027565230808", "buy_price": 2315000, "sell_price": 4630000 }
]
},
{
"name": "Canina Lachsöl",
"category_slug": "special-care",
"description": "روغن ماهی سالمون 100% خالص سرشار از اسیدهای چرب امگا 3 (EPA و DHA). متابولیسم را تنظیم کرده و از بازسازی پوست و رشد مو پشتیبانی می کند.",
"ingredients": "100% روغن ماهی سالمون. اسیدهای چرب چند غیراشباع 56%، امگا 3 (14%) و امگا 6 (11%).",
"usage_and_dosage": "2 میلی لیتر به ازای هر 10 کیلوگرم وزن بدن، حداکثر 10 میلی لیتر در روز مخلوط با خوراک.",
"variants": [
{ "size": "250ml", "art_no": "151004", "barcode": "4027565150007", "buy_price": 2240000, "sell_price": 4480000 }
]
},
{
"name": "Canina Marine-Ölmischung PREMIUM",
"category_slug": "special-care",
"description": "ترکیبی هماهنگ از روغن های خوراکی که متابولیسم ارگانیسم را ارتقا می دهد. از کمبود مواد مغذی در رژیم های خام جلوگیری می کند. روغن آرگان و گل رز به عنوان آنتی اکسیدان و بازسازی کننده عمل می کنند.",
"ingredients": "روغن ماهی، روغن گردو، روغن آرگان، روغن دانه گل رز. چربی خام 100%.",
"usage_and_dosage": "قابل افزودن به تمام وعده های غذایی برای بهینه سازی ویتامین ها و اسیدهای چرب امگا.",
"variants": [
{ "size": "250ml", "art_no": "153008", "barcode": "4027565153008", "buy_price": 4270000, "sell_price": 8540000 }
]
},
{
"name": "Canina Moortränke",
"category_slug": "immune",
"description": "عصاره پیت حاوی مواد هومیک برای محافظت از غشاهای مخاطی و سیستم ایمنی روده. ایده آل برای نفخ، اسهال، پس از درمان آنتی بیوتیک، تحریک اشتها و خون سازی.",
"ingredients": "100% پیت، پیت مایع (بدون افزودنی و نگهدارنده).",
"usage_and_dosage": "قبل از استفاده تکان دهید. سگ های کوچک 2.5 تا 5 میلی لیتر، سگ های متوسط 10 میلی لیتر، سگ های بزرگتر 15 تا 20 میلی لیتر. از 20 میلی لیتر تجاوز نکند.",
"variants": [
{ "size": "250ml", "art_no": "112708", "barcode": "4027565112708", "buy_price": 3310000, "sell_price": 6620000 }
]
},
{
"name": "Canina PETVITAL Arthro-Tabletten",
"category_slug": "joints",
"description": "متابولیسم استخوان را بهبود بخشیده و مفاصل ضعیف را تقویت می کند. عصاره بید و زنجبیل به عنوان آنتی اکسیدان عمل کرده و خاصیت ارتجاعی استخوان را افزایش می دهند.",
"ingredients": "گیاهان خشک (بید و زنجبیل)، ویتامین ایی، ویتامین ب1، آهن، مس، روی.",
"usage_and_dosage": "توله سگ ها: 2 قرص به ازای 10 کیلوگرم برای 1 ماه. سگ های بالغ: 5 قرص به ازای 10 کیلوگرم برای 3 ماه (سالی یکبار تکرار شود).",
"variants": [
{ "size": "60g (60 Tablets)", "art_no": "723003", "barcode": "4027565723003", "buy_price": 3240000, "sell_price": 6480000 }
]
},
{
"name": "Canina PETVITAL Bio-Aktivator",
"category_slug": "energy",
"description": "حاوی سرم خون گاو برای تحریک اشتها و تسریع بهبودی در دوره نقاهت. رفاه و هماهنگی متابولیسم را بهبود می بخشد.",
"ingredients": "سرم خون گاو، اتانول 164.4 گرم (به عنوان طعم دهنده).",
"usage_and_dosage": "1 قطره به ازای هر کیلوگرم وزن بدن، حداکثر 20 قطره در روز. قطره ها به مدت 4 هفته داده شوند، سپس 2 هفته استراحت.",
"variants": [
{ "size": "20ml", "art_no": "712007", "barcode": "4027565712007", "buy_price": 3395000, "sell_price": 6790000 }
]
},
{
"name": "Canina PETVITAL Biotin-Tabs",
"category_slug": "special-care",
"description": "دوز بالای بیوتین (ویتامین H) برای جلوگیری از ریزش مو، ایجاد پوشش براق، رنگدانه های قوی و پنجه های محکم.",
"ingredients": "دکستروز، آب پنیر، نشاسته، سلولز. بیوتین 1000000 میکروگرم، ویتامین ب2، اسید نیکوتینیک.",
"usage_and_dosage": "یک قرص به ازای هر 10 کیلوگرم وزن بدن، حداکثر 5 قرص در روز.",
"variants": [
{ "size": "100g (50 Tablets)", "art_no": "702008", "barcode": "4027565702008", "buy_price": 3210000, "sell_price": 6420000 }
]
},
{
"name": "Canina PETVITAL ENERGY-GEL",
"category_slug": "energy",
"description": "کنسانتره انرژی برای اصلاح سریع کمبودهای مواد مغذی. ایده آل برای دوره نقاهت جهت تحریک اشتها، دوره های رشد، بارداری و شیردهی.",
"ingredients": "روغن ها، چربی ها، ویتامین ایی، ویتامین ب1، ب2، ب6، ب12.",
"usage_and_dosage": "1 قاشق چایخوری (3 میلی لیتر) به ازای 10 کیلوگرم وزن بدن، حداکثر 4 قاشق چایخوری در روز.",
"variants": [
{ "size": "100ml", "art_no": "712106", "barcode": "4027565712106", "buy_price": 2500000, "sell_price": 5000000 }
]
},
{
"name": "Canina PETVITAL MINERAL-Tabs",
"category_slug": "energy",
"description": "انواع مختلف کلسیم برای برطرف کردن کمبود و تشکیل بافت استخوانی. عالی برای دوره رشد، بارداری و شیردهی جهت تامین نیاز کلسیم.",
"ingredients": "کربنات کلسیم، مخمر آبجو، ویتامین د3 100000 (برای ذخیره کلسیم در استخوان).",
"usage_and_dosage": "سگ های بالغ: 2 قرص به ازای 10 کیلوگرم. باردار و شیرده: 3 قرص به ازای 10 کیلوگرم. مصرف ویتامین D2 همزمان مجاز نیست.",
"variants": [
{ "size": "100g (50 Tablets)", "art_no": "723102", "barcode": "4027565723102", "buy_price": 2685000, "sell_price": 5370000 }
]
},
{
"name": "Canina PETVITAL GAG",
"category_slug": "joints",
"description": "تامین گلیکوزآمینوگلیکان ها، تاندون ها، رباط ها و غضروف مفاصل را تثبیت می کند. شامل پودر صدف لب سبز و اسیدهای آمینه برای عملکرد بهینه سیستم حرکتی.",
"ingredients": "پودر ماهی، پودر جلبک دریایی، پودر گوشت صدف لب سبز. دی ال متیونین، ویتامین ایی، سلنیوم.",
"usage_and_dosage": "پودر: 1 قاشق چایخوری به ازای 10 کیلوگرم (حداکثر 4 قاشق). قرص: 1 قرص به ازای 10 کیلوگرم (حداکثر 5 قرص).",
"variants": [
{ "size": "100g Powder", "art_no": "723201", "barcode": "4027565723201", "buy_price": 2920000, "sell_price": 5840000 },
{ "size": "90g (90 Tablets)", "art_no": "723300", "barcode": "4027565723300", "buy_price": 4740000, "sell_price": 9480000 },
{ "size": "400g Powder", "art_no": "723225", "barcode": "4027565723225", "buy_price": 8395000, "sell_price": 16790000 }
]
},
{
"name": "Canina PETVITAL VITAMIN-Tabs",
"category_slug": "energy",
"description": "اصلاح کمبود ویتامین، جلوگیری از خستگی، عدم تناسب و اختلالات رشد. ایده آل برای مادران باردار و شرایط استرس زا.",
"ingredients": "مخمر آبجو، سلولز، ویتامین های آ، ب1 تا ب12، د3، ایی، کا3.",
"usage_and_dosage": "1 قرص به ازای هر 10 کیلوگرم وزن بدن در روز.",
"variants": [
{ "size": "100g (50 Tablets)", "art_no": "712205", "barcode": "4027565712205", "buy_price": 3485000, "sell_price": 6970000 }
]
},
{
"name": "Canina Rinderblut Pulver",
"category_slug": "energy",
"description": "پودر خون گاو، منبع ارزشمند آهن و هموگلوبین برای انتقال اکسیژن. مکمل عالی برای رژیم گوشت خام (B.A.R.F) جهت تامین خون از دست رفته و رشد عضلات. مناسب دوره نقاهت و سگ های ورزشی.",
"ingredients": "خون کامل گاو خشک شده. پروتئین خام 92%، آهن.",
"usage_and_dosage": "0.3 گرم به ازای هر کیلوگرم وزن بدن (حداکثر 12 گرم در روز) مخلوط با خوراک خام برای 6 ماه.",
"variants": [
{ "size": "250g", "art_no": "792016", "barcode": "4027565792016", "buy_price": 1560000, "sell_price": 3120000 },
{ "size": "500g", "art_no": "791514", "barcode": "4027565791514", "buy_price": 2530000, "sell_price": 5060000 }
]
},
{
"name": "Canina Rinderfett Pulver",
"category_slug": "energy",
"description": "منبع انرژی طبیعی از چربی گوشت گاو آب پز بدون شکر. عالی برای حیوانات لاغر، سگ های بسیار فعال، و جبران کمبود چربی در وعده های خام.",
"ingredients": "72% چربی گوشت گاو، 27% آب گوشت گاو خشک شده. انرژی: 740 کیلوکالری.",
"usage_and_dosage": "نصف قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن، حداکثر 3 قاشق چایخوری در روز همراه غذای مرطوب.",
"variants": [
{ "size": "75g", "art_no": "131235", "barcode": "4027565131235", "buy_price": 1520000, "sell_price": 3040000 }
]
},
{
"name": "Canina Schwarz Kümmel Samen",
"name": "Canina HERZ VITAL (هرز ویتال کانینا)",
"category_slug": "general",
"description": "دانه های زیره سیاه سرشار از اسیدهای چرب غیر اشباع که هضم و متابولیسم را تنظیم می کنند. سیستم دفاعی و عملکرد سگ را تقویت می کند.",
"ingredients": "100% دانه های زیره سیاه مصری.",
"usage_and_dosage": "1 قاشق چایخوری به ازای 10 کیلوگرم وزن بدن، حداکثر 4 قاشق چایخوری مخلوط با آب ولرم. در بارداری استفاده نشود.",
"description": "سگ‌ها و گربه‌های مسن اغلب از نارسایی قلبی رنج می‌برند. ال-کارنیتین می‌تواند بر تأمین انرژی قلب و عضلات اسکلتی تأثیر مثبت بگذارد. این ماده می‌تواند نشاط، انگیزه و رفاه سگ/گربه شما را افزایش دهد. ال-کارنیتین می‌تواند عملکرد فیزیولوژیکی قلب را تقویت کند.",
"short_description": "تقویت‌کننده طبیعی کارکرد ماهیچه قلب برای سگ و گربه مسن.",
"ingredients": "دکستروز، مخمر غیرفعال، عصاره زالزالک، ال-کارنیتین.",
"usage_and_dosage": "قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن. حداکثر 3 قاشق چایخوری روزانه.",
"variants": [
{ "size": "250g", "art_no": "131105", "barcode": "4027565131105", "buy_price": 2140000, "sell_price": 4280000 }
{
"size": "100ml",
"art_no": "112036",
"barcode": "4027565112036",
"buy_price": 2142500,
"sell_price": 4285000
}
]
},
{
"name": "Canina Seealgen / Bio-Seealgenmehl",
"category_slug": "special-care",
"description": "جلبک دریایی ارگانیک سرشار از مواد معدنی و اسیدهای آمینه که با ید و روی، رنگدانه های پوست، بینی و پوشش را پشتیبانی می کند. هضم و سیستم ایمنی را بهبود می بخشد.",
"ingredients": "جلبک دریایی حاوی لیزین، سلولز و ویتامین سی.",
"usage_and_dosage": "قرص: 2 عدد به ازای 10 کیلوگرم. پودر: نصف قاشق چایخوری به ازای 10 کیلوگرم وزن بدن. جهت تامین نیاز ید.",
"name": "Canina IMMUN-BOOSTER (ایمون بوستر کانینا)",
"category_slug": "immune",
"description": "بازیابی و نقاهت تغذیه‌ای > چگالی انرژی بالا، غلظت بالای مواد مغذی ضروری و مواد تشکیل‌دهنده با قابلیت هضم بالا. تقویت‌کننده سیستم ایمنی کانینا انرژی قابل هضم و در دسترس، ویتامین‌های با غلظت بالا و ایمونوگلوبولین‌های آغوز را برای سگ‌ها و گربه‌ها فراهم می‌کند. این اولین شیری است که مادر در طول و بعد از تولد تولید می‌کند. این شیر سرشار از لاکتوفرین و لیزوزیم است. تقویت‌کننده سیستم ایمنی کانینا از سیستم دفاعی بدن پشتیبانی می‌کند و مرحله بهبودی حیوانات را، به ویژه در حین و بعد از فعالیت‌های بدنی مانند زایمان (حیوان مادر و همچنین توله سگ‌ها و بچه گربه‌ها)، تمرین، ورزش و آزمایش، بهینه می‌کند. میکروب تشکیل‌دهنده اسید لاکتیک، انتروکوک فاسیوم، در تقویت‌کننده سیستم ایمنی کانینا، کلونیزاسیون سریع با لاکتوباسیل‌های مهم را تضمین می‌کند. این باکتری‌ها برای فلور روده سالم و متعادل و در نتیجه برای سیستم ایمنی پایدار روده ضروری هستند. اتصال میکروب‌های منفی به مخاط روده خنثی می‌شود. این محصول یک خمیر خوشمزه است که سگ‌ها و گربه‌ها از آن لذت می‌برند. این محصول از توله سگ‌ها/بچه گربه‌ها تا گربه‌های مسن مناسب است. برای حمایت از سلامت و عملکرد، این محصول می‌تواند جیره غذایی روزانه را افزایش دهد. این خمیر همچنین می‌تواند در مراحل خاصی از زندگی که غذای روزانه به طور مطلوب نیازهای بالای حیوان را برآورده نمی‌کند، تجویز شود.",
"short_description": "خمیر انرژی‌بخش و ایمن‌ساز حاوی آغوز و پروبیوتیک فعال.",
"ingredients": "روغن ها و چربی های گیاهی، پودر آغوز 20%، شکر انگور.",
"usage_and_dosage": "توله سگ ها: 0.25 میلی لیتر. سگ های متوسط: 0.5 تا 1 میلی لیتر. گربه ها: 0.25 میلی لیتر.",
"variants": [
{ "size": "225g (225 Tablets)", "art_no": "130504", "barcode": "4027565130504", "buy_price": 2605000, "sell_price": 5210000 },
{ "size": "750g (750 Tablets)", "art_no": "130511", "barcode": "4027565130511", "buy_price": 6895000, "sell_price": 13790000 },
{ "size": "750g (Powder)", "art_no": "130412", "barcode": "4027565130412", "buy_price": 3690000, "sell_price": 7380000 }
{
"size": "15ml",
"art_no": "700707",
"barcode": "4027565700707",
"buy_price": 1495000,
"sell_price": 2990000
}
]
},
{
"name": "Canina TAURIN Für Katzen",
"category_slug": "special-care",
"description": "اسید آمینه ضروری برای گربه ها که سیستم ایمنی، بینایی و قلب را پشتیبانی می کند. کمبود آن باعث نابینایی و اختلالات عصبی می شود.",
"ingredients": "مخمر آبجو، تورین 700 گرم.",
"usage_and_dosage": "گربه تا 6 کیلو: نصف قاشق چایخوری. بالای 6 کیلو و باردار/شیرده: 1 قاشق چایخوری در روز.",
"name": "Canina Katzenmilch (شیر گربه کانینا)",
"category_slug": "nutrition",
"description": "غذای کامل جایگزین شیر مادر با اجزای سازنده بیولوژیکی ارزشمند: انرژی و قدرت. غذا با تمام ویتامین‌ها و مواد معدنی لازم برای رشد سالم. جایگزین شیر مادر برای بچه‌گربه‌ها از ۱ روزگی، برای تغذیه بچه‌گربه‌های بی‌مادر، اگر تولید شیر مادر کافی نیست، برای توله‌های بزرگ، برای آماده‌سازی و در طول شیردهی، به عنوان مکمل برای گربه‌های مولد، به عنوان مکمل برای گربه‌های مسن و بیمار. فرمول این شیر بر اساس شیر طبیعی گربه مادر تهیه شده است.",
"short_description": "شیر خشک غنی از تورین جایگزین کامل شیر مادر برای بچه‌گربه‌ها.",
"ingredients": "لاکتوز، پودر آب پنیر، چربی نارگیل، پروتئین ایزوله سویا، پودر تخم مرغ کامل، روغن ماهی، تورین.",
"usage_and_dosage": "1 قسمت شیر با 3 قسمت آب ولرم مخلوط شود. هر 2 ساعت در هفته اول تغذیه شود.",
"variants": [
{ "size": "100g", "art_no": "229505", "barcode": "4027565229055", "buy_price": 2350000, "sell_price": 4700000 }
{
"size": "150g",
"art_no": "230808",
"barcode": "4027565230808",
"buy_price": 1157500,
"sell_price": 2315000
}
]
},
{
"name": "Canina Velox Gelenkenergie",
"name": "Canina Lachsol (روغن ماهی سالمون کانینا)",
"category_slug": "nutrition",
"description": "روغن ماهی سالمون کانینا از روغن ماهی سالمون ۱۰۰٪ خالص با کیفیت غذایی ساخته شده و سرشار از اسیدهای چرب چند غیراشباع (اسیدهای چرب امگا، EPA و DPA) است. از آنجایی که این اسیدهای چرب نمی‌توانند توسط خود سگ‌ها و گربه‌ها تولید شوند، باید از منابع خارجی در غذای آنها تأمین شوند. آنها برای متابولیسم روان ضروری و حیاتی هستند. روغن ماهی سالمون کانینا از بازسازی پوست و رشد مو پشتیبانی می‌کند و کل فرآیندهای متابولیکی بدن را هماهنگ می‌کند. صرف نظر از نوع خوراک، روغن ماهی سالمون کانینا غذای حیوان شما را به عنوان یک ماده خوراکی با کیفیت بالا بهینه می‌کند.",
"short_description": "روغن سالمون ۱۰۰٪ خالص برای براقیت پوست و مو و تنظیم متابولیسم.",
"ingredients": "100% روغن طبیعی ماهی سالمون.",
"usage_and_dosage": "2 میلی لیتر به ازای هر 10 کیلوگرم وزن بدن روزانه.",
"variants": [
{
"size": "250ml",
"art_no": "151004",
"barcode": "4027565150007",
"buy_price": 1120000,
"sell_price": 2240000
}
]
},
{
"name": "Canina Marine-Olmischung PREMIUM (روغن دریایی پریمیوم کانینا)",
"category_slug": "nutrition",
"description": "روغن دریایی پریمیوم، ترکیبی هماهنگ و ویژه از روغن‌های خوراکی با کیفیت بالا است که در اثرات مثبت خود بر کل ارگانیسم، یکدیگر را به طور بهینه تکمیل می‌کنند. روغن دریایی امگا ۳ طبیعی (کنترل شده با آفت‌کش) سرشار از اسیدهای چرب اشباع نشده EPA و DHA است که برای سگ‌ها و گربه‌ها ضروری هستند. این اسیدهای چرب باید با غذا تأمین شوند، زیرا حیوانات نمی‌توانند خودشان آن‌ها را تولید کنند. ویتامین‌هایی مانند آ و د ۳ که به طور طبیعی در این محصول وجود دارند، متابولیسم استخوان و پوست را پشتیبانی می‌کنند. این محصول سرشار از آنتی‌اکسیدان‌های طبیعی است. روغن گردو، روغن دانه گل رز و روغن آرگان سرشار از ویتامین E هستند که از سلول‌ها محافظت و بازسازی می‌کند و رادیکال‌های آزاد و مضر را جذب می‌کند. ویتامین آ حاصل از روغن دانه گل رز تأثیر مثبتی بر پوست و غشاهای مخاطی دارد و به ویژه برای بینایی و رشد مهم است. روغن آرگان از دانه‌های درخت آرگان به دست می‌آید. روغن آرگان علاوه بر طیف متعادلی از اسیدهای چرب، حاوی مواد گیاهی ثانویه مانند فیتواسترول‌ها و اسکوالن است که تأثیر مثبتی بر متابولیسم سگ‌ها و گربه‌ها دارند. صرف نظر از نوع خوراک، ترکیب روغن دریایی پریمیوم تمام وعده‌های غذایی حیوان را بهبود می‌بخشد. این محصول از کمبود مواد مغذی در وعده‌های غذایی خام (ویتامین آ، ایی، د ۳ و اسیدهای چرب امگا ۳) جلوگیری می‌کند.",
"short_description": "روغن دریایی امگا ۳ غنی از روغن آرگان و گل رز برای پوست و رشد.",
"ingredients": "روغن ماهی (امگا 3 طبیعی)، روغن گردو، روغن آرگان، روغن دانه گل رز.",
"usage_and_dosage": "1 قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن روزانه.",
"variants": [
{
"size": "250ml",
"art_no": "153008",
"barcode": "4027565153008",
"buy_price": 2135000,
"sell_price": 4270000
}
]
},
{
"name": "Canina Moortranke (عصاره پیت کانینا - مایع)",
"category_slug": "immune",
"description": "مواد هومیک موجود در عصاره پیت، از غشاهای مخاطی دستگاه گوارش محافظت کرده و از سیستم ایمنی روده، چه در حیوانات جوان و چه در حیوانات بالغ، پشتیبانی می‌کنند. عصاره پیت برای حیوانات خانگی یک ماده خوراکی طبیعی بدون افزودنی و بدون مواد نگهدارنده است. موارد استفاده: برای تحریک دستگاه گوارش، تغییر غلظت مدفوع، نفخ یا پس از درمان‌های آنتی‌بیوتیکی، برای تحریک اشتها، برای تقویت رشد و تقویت سیستم ایمنی، برای تقویت باروری در حیوانات نر و ماده، برای تحریک خون‌سازی و برای اتصال و حذف مواد مضر.",
"short_description": "عصاره طبیعی پیت غنی از اسید هومیک برای تنظیم فلور گوارشی.",
"ingredients": "100% پیت (عصاره مایع پیت آلمانی).",
"usage_and_dosage": "سگ های کوچک: 2.5 میلی لیتر. سگ های متوسط: 5 میلی لیتر. سگ های بزرگ: 10 تا 15 میلی لیتر.",
"variants": [
{
"size": "250ml",
"art_no": "112708",
"barcode": "4027565112708",
"buy_price": 1655000,
"sell_price": 3310000
}
]
},
{
"name": "Canina PETVITAL Arthro-Tabletten (قرص آرترو پت ویتال)",
"category_slug": "joints",
"description": "کنسانتره صدف لب سبز نیوزیلندی برای بازسازی مفاصل، رباط ها و غضروف. افزایش استقامت مفاصل توله ها در حال رشد و سگ های پیر.",
"ingredients": "100% پودر گوشت صدف لب سبز.",
"usage_and_dosage": "1 قاشق چایخوری به ازای 10 کیلوگرم وزن بدن (حداکثر 2 قاشق) در روز به مدت 4 هفته یا مداوم.",
"description": "قرص‌های آرترو پت ویتال حاوی ویتامین‌ها، مواد معدنی و گیاهان خشک هستند. این ترکیبات تأثیر مطلوبی بر متابولیسم استخوان دارند؛ مفاصل ضعیف تقویت شده و دوباره انعطاف‌پذیر می‌شوند. با پشتیبانی از تعادل یونی طبیعی بافت استخوان، روند پیری کند می‌شود و خاصیت ارتجاعی استخوان‌ها و تاندون‌ها افزایش می‌یابد. عصاره‌های گیاهی موجود در قرص‌های آرترو پت ویتال از استخوان‌ها و مفاصل در عملکرد طبیعی آنها پشتیبانی می‌کنند. پوست درخت بید (Willow bark) و زنجبیل به عنوان یک آنتی‌اکسیدان، رادیکال‌های آزاد را متوقف می‌کنند و به عنوان یک ویتامین محافظ سلول عمل می‌کنند. ویتامین ای برای انتقال تکانه‌های عصبی ضروری است. ویتامین ب ۱ برای تشکیل خون، انتقال اکسیژن و فعالیت عضلات مهم است. آهن در تشکیل خون، توسعه رنگدانه‌های مو و تشکیل غضروف نقش دارد. مس از رشد سلولی و تقسیم سلولی پشتیبانی می‌کند. روی برای پشتیبانی از استخوان‌ها، مفاصل و بافت همبند استفاده می‌شود.",
"short_description": "قرص گیاهی تقویت مفاصل ضعیف و کندکننده روند پیری استخوان.",
"ingredients": "پوست درخت بید، زنجبیل، ویتامین E، ویتامین B1، آهن، مس، روی، پودر مخمر.",
"usage_and_dosage": "توله سگ ها: 2 قرص روزانه به مدت 1 ماه. سگ های بالغ: 5 قرص روزانه به مدت 3 ماه.",
"variants": [
{ "size": "150g", "art_no": "701902", "barcode": "4027565701902", "buy_price": 4750000, "sell_price": 9500000 }
{
"size": "60g",
"art_no": "723003",
"barcode": "4027565723003",
"buy_price": 1620000,
"sell_price": 3240000
}
]
},
{
"name": "Canina Welpenbrei",
"name": "Canina PETVITAL Bio-Aktivator (بیو اکتیواتور پت ویتال)",
"category_slug": "general",
"description": "پت ویتال بیواکتیواتور حاوی سرم خون گاو است که در یک فرآیند ویژه به دست آمده است. این محصول می‌تواند تأثیر مثبتی بر دوره نقاهت و رفاه حیوان داشته باشد. به تحریک اشتها و پشتیبانی از مرحله بهبودی کمک می‌کند. این محصول حاوی طعم‌دهنده‌ها و محرک‌های طبیعی اشتها است.",
"short_description": "سرم طبیعی خون گاو برای بازگرداندن انرژی و افزایش اشتها.",
"ingredients": "سرم طبیعی گاو، اتانول 164.4 گرم در لیتر.",
"usage_and_dosage": "1 قطره به ازای هر کیلوگرم وزن بدن، حداکثر 20 قطره روزانه به مدت 4 هفته.",
"variants": [
{
"size": "20ml",
"art_no": "712007",
"barcode": "4027565712007",
"buy_price": 1697500,
"sell_price": 3395000
}
]
},
{
"name": "Canina PETVITAL Biotin-Tabs (قرص بیوتین پت ویتال)",
"category_slug": "special-care",
"description": "قرص‌های بیوتین پت ویتال حاوی دوز بالای ویتامین اچ (بیوتین) هستند که تأثیر ویژه‌ای بر رشد مو و رنگدانه‌های مو دارند. بیوتین به عنوان یک کوآنزیم برای رشد مو و ماده پنجه در سنتز کراتین نقش دارد. کمبود تغذیه‌ای بیوتین می‌تواند با افزایش ریزش مو و خشکی و شکنندگی مو آشکار شود. پنجه‌ها می‌توانند نرم و شکننده شوند. قرص‌های بیوتین پت ویتال حاوی دوز بالای ویتامین ب ۲ و اسید نیکوتینیک هستند که از ترکیبات فعال مهم برای عملکرد و رشد مو و رنگدانه‌های مو می‌باشند. ویتامین‌ها رنگدانه‌سازی پوست و مو هستند. برای موهای براق و متراکم، برای رنگدانه‌های قوی و برای پنجه‌های محکم و قوی.",
"short_description": "قرص بیوتین دوز بالا جهت رفع سریع ریزش مو و سفت کردن ناخن‌ها.",
"ingredients": "دکستروز، آب پنیر، نشاسته سیب زمینی، بیوتین (1,100,000mcg)، ویتامین B2، اسید نیکوتینیک.",
"usage_and_dosage": "1 قرص به ازای هر 10 کیلوگرم وزن بدن روزانه، حداکثر 5 قرص.",
"variants": [
{
"size": "100gr 50 Tab",
"art_no": "702008",
"barcode": "4027565702008",
"buy_price": 1605000,
"sell_price": 3210000
}
]
},
{
"name": "Canina PETVITAL ENERGY-GEL (انرژی ژل پت ویتال)",
"category_slug": "energy",
"description": "این محصول یک کنسانتره انرژی برای اصلاح سریع کمبودهای مواد مغذی است. این کنسانتره انرژی ارزشمند، ویتامین‌های گروه بی و ویتامین ای را در مراحلی از زندگی سگ‌ها و گربه‌ها که نیاز به افزایش نیاز ضروری است، فراهم می‌کند: اگر غذا مصرف نشود، انرژی حیوان را تأمین می‌کند؛ در مرحله نقاهت، اشتها را برای شروع مجدد تغذیه حیوانات ضعیف با کمک تحریک می‌کند. در مرحله رشد توله سگ‌ها و حیوانات جوان و در طول دوره‌های بارداری و شیردهی سگ مادر، شکاف نیاز را که می‌تواند با غذای معمولی به دلیل افزایش تقاضا و عملکرد بالا ایجاد شود، پر می‌کند. کنسانتره انرژی برای سرزندگی، عملکرد و رفاه.",
"short_description": "کنسانتره غلیظ مولتی‌ویتامین و کربوهیدرات برای جبران سریع بی‌اشتهایی.",
"ingredients": "روغن ها و چربی ها، فرآورده های فرعی گیاهی، ویتامین E، ویتامین B1، B2، B6، B12، نیکوتینیک اسید.",
"usage_and_dosage": "1 قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن روزانه، حداکثر 4 قاشق چایخوری.",
"variants": [
{
"size": "100ml",
"art_no": "712106",
"barcode": "4027565712106",
"buy_price": 1250000,
"sell_price": 2500000
}
]
},
{
"name": "Canina PETVITAL MINERAL-Tabs (قرص مینرال پت ویتال)",
"category_slug": "general",
"description": "قرص‌های معدنی پت ویتال حاوی ترکیبی بهینه از انواع مختلف کلسیم هستند. مصرف منظم آن، کمبود کلسیم را برطرف کرده و تشکیل بافت استخوانی را تقویت می‌کند. قرص‌های معدنی پت ویتال به تأمین نیاز افزایش یافته به کلسیم در طول دوره رشد و در طول دوره‌های بارداری و شیردهی کمک می‌کنند. افزودن ویتامین د ۳ می‌تواند به طور ایده آل کلسیم را در استخوان‌ها ذخیره کند. مخمر، اسیدهای آمینه ارزشمند، آنزیم‌ها و ویتامین‌های گروه بی را برای پشتیبانی از کل متابولیسم فراهم می‌کند. برای بهینه‌سازی نیاز به کلسیم، فسفر و ویتامین د ۳، قرص‌های این محصول را به غذای خانگی (خوراک خام / خوراک خانگی) یا قوطی‌های گوشت خالص اضافه کنید.",
"short_description": "قرص کلسیم و ویتامین D3 برای رشد استخوان توله‌ها و سگ‌های باردار.",
"ingredients": "کربنات کلسیم، نشاسته سیب زمینی، پودر مخمر، ویتامین D3 (10,000 IU).",
"usage_and_dosage": "سگ های بالغ: 2 قرص به ازای هر 10 کیلوگرم. سگ های باردار و شیرده: 3 قرص به ازای هر 10 کیلوگرم.",
"variants": [
{
"size": "100g 50 Tab",
"art_no": "723102",
"barcode": "4027565723102",
"buy_price": 1342500,
"sell_price": 2685000
}
]
},
{
"name": "Canina PETVITAL GAG (پودر و قرص جی ای جی پت ویتال)",
"category_slug": "joints",
"description": "این محصول برای تأمین گلیکوزآمینوگلیکان‌ها با کیفیت بالا برای سیستم اسکلتی-عضلانی استفاده می‌شود. محتوای بالای گلیکوزآمینوگلیکان‌ها، ساختارهای بافت همبند مانند تاندون‌ها، رباط‌ها، غضروف مفاصل و دیسک‌های بین مهره‌ای را تثبیت و بازسازی می‌کند. این محصول شامل مواد طبیعی است که برای عملکرد و کارایی و همچنین انعطاف‌پذیری سیستم اسکلتی عضلانی ضروری هستند: اسیدهای آمینه و پودر جلبک دریایی به عنوان منبعی در دسترس از گوگرد. پودر گوشت صدف لب سبز، خشک شده به صورت انجمادی: برای تأمین گلیکوزآمینوگلیکان‌ها، کندرویتین ۴- و ۶-سولفات، هپارین و هپاران سولفات و همچنین اسیدهای چرب امگا ۳ برای بافت همبند و غضروف مفصلی. ویتامین ای و سلنیوم به عنوان عوامل محافظت سلولی، که عمدتاً برای کاهش محصولات متابولیک سیتوتوکسیک عمل می‌کنند.",
"short_description": "تثبیت و بازسازی غضروف‌ها، تاندون‌ها و دیسک‌های بین مهره‌ای.",
"ingredients": "پودر ماهی، مخمر، 19% پودر جلبک دریایی، 15% پودر گوشت صدف لب سبز، کربنات کلسیم، متیونین، ویتامین E، سلنیوم.",
"usage_and_dosage": "پودر: 1 قاشق چایخوری به ازای هر 10 کیلوگرم. قرص: 1 قرص به ازای هر 10 کیلوگرم.",
"variants": [
{
"size": "90 Tab",
"art_no": "723300",
"barcode": "4027565723300",
"buy_price": 2370000,
"sell_price": 4740000
},
{
"size": "100gr",
"art_no": "723201",
"barcode": "4027565723201",
"buy_price": 1460000,
"sell_price": 2920000
},
{
"size": "400gr",
"art_no": "723225",
"barcode": "4027565723225",
"buy_price": 4197500,
"sell_price": 8395000
}
]
},
{
"name": "Canina PETVITAL VITAMIN-Tabs (قرص ویتامین پت ویتال)",
"category_slug": "general",
"description": "کمبود ویتامین‌های مرتبط با تغذیه می‌تواند عواقبی داشته باشد: کمبود انگیزه، خستگی، عدم تناسب اندام، اختلالات در مرحله رشد و نمو، از دست دادن اشتها، عصبی بودن و پوشش کدر. قرص‌های ویتامین پت ویتال برای اصلاح این کمبودها تولید شده‌اند. قرص‌های ویتامین پت ویتال به ویژه برای مادران باردار و شیرده، در مرحله رشد و در شرایط استرس‌زا ضروری هستند.",
"short_description": "قرص مولتی‌ویتامین و مینرال روزانه برای رفع خستگی و تقویت عمومی.",
"ingredients": "مخمر آبجو غیرفعال، مونوکلسیم فسفات، کربنات کلسیم، ویتامین A، B1، B2، B6، B12، D3، E، K3، اسید نیکوتینیک.",
"usage_and_dosage": "1 قرص به ازای هر 10 کیلوگرم وزن بدن روزانه.",
"variants": [
{
"size": "100g 50 Tab",
"art_no": "712205",
"barcode": "4027565712205",
"buy_price": 1742500,
"sell_price": 3485000
}
]
},
{
"name": "Canina Rinderblut Pulver (پودر خون گاو کانینا)",
"category_slug": "nutrition",
"description": "فرنی با پروتئین و ویتامین بالا که پرورش سالم توله سگ را از هفته 3 تضمین می کند.",
"ingredients": "غلات، گوشت، فرآورده های لبنی، ماهی، پروتئین 27%.",
"usage_and_dosage": "1 قسمت فرنی را با 3 قسمت آب گرم (50 درجه سانتیگراد) مخلوط کنید. مصرف ویتامین D2 همزمان مجاز نیست.",
"description": "پودر خون گاو کانینا منبع ارزشمندی از رنگدانه قرمز خون (هموگلوبین) است که حاوی آهن به عنوان اتم مرکزی است. آهن اکسیژن را به خون منتقل می‌کند. سایر مواد معدنی که به طور طبیعی در پودر خون گاو وجود دارند عبارتند از کلسیم، فسفر، سدیم، پتاسیم، منیزیم، مس، منگنز و روی. علاوه بر این، خون گاو حاوی پروتئین‌هایی است که به نوبه خود از اسیدهای آمینه تشکیل شده‌اند که برای رشد ماده ژنتیکی در هر سلول بدن مورد نیاز هستند. افزودن پودر خون گاو کانینا به ویژه برای سگ‌ها و گربه‌هایی که با گوشت تازه (B.A.R.F.) تغذیه می‌شوند توصیه می‌شود زیرا گوشت خام دیگر حاوی خون نیست. به دلیل محتوای پروتئین طبیعی بالا، پودر خون گاو کانینا باعث رشد عضلات می‌شود. پودر خون گاو کانینا از ایجاد شکل بدنی هماهنگ در طول مرحله رشد پشتیبانی می‌کند. سگ‌ها و گربه‌هایی که در حال گذراندن دوره نقاهت هستند یا تحت فشار خاصی قرار دارند از مصرف پودر خون گاو کانینا سود می‌برند: این پودر از قدرت، استقامت و رفاه پشتیبانی می‌کند و عملکرد را بهبود می‌بخشد.",
"short_description": "منبع آهن طبیعی هموگلوبین و اسیدهای آمینه برای سگ و گربه بارف.",
"ingredients": "100% پودر خون گاو خشک شده.",
"usage_and_dosage": "0.3 گرم به ازای هر کیلوگرم وزن بدن روزانه، حداکثر 12 گرم.",
"variants": [
{ "size": "600g", "art_no": "130603", "barcode": "4027565130603", "buy_price": 2835000, "sell_price": 5670000 }
{
"size": "250g",
"art_no": "792016",
"barcode": "4027565792016",
"buy_price": 780000,
"sell_price": 1560000
},
{
"size": "500g",
"art_no": "791514",
"barcode": "4027565791514",
"buy_price": 1265000,
"sell_price": 2530000
}
]
},
{
"name": "Canina Welpenmilch",
"name": "Canina Rinderfett Pulver (پودر چربی گوشت گاو کانینا)",
"category_slug": "energy",
"description": "پودر چربی گوشت گاو کانینا یک منبع انرژی طبیعی از چربی گوشت گاو آب‌پز و آبگوشت حاصل از آن، بدون افزودن شکر است. سگ‌ها و گربه‌ها می‌توانند انرژی چربی را از این چربی حیوانی، منحصراً چربی گاو، تبدیل کرده و برای بدن خود استفاده کنند. حیوانات جوان در حال رشد که با وجود خوردن غذای کافی، لاغر هستند و وزن کم می‌کنند، بدنشان را برای نمایش آماده می‌کنند. حیوانات باردار و شیرده از کالری چربی سود می‌برند، همچنین در مورد تغذیه توله سگ‌هایشان. برای سگ‌هایی که از نظر جسمی بسیار فعال هستند، پودر چربی گوشت گاو کانینا باید به عنوان یک منبع انرژی طبیعی برای قدرت و استقامت در وعده‌های غذایی روزانه آنها گنجانده شود. پودر چربی گوشت گاو کانینا می‌تواند به حیوانات حساس به طعم و بدخوران کمک کند تا اشتهای خود را افزایش دهند زیرا چربی در انتقال طعم عالی است. علاوه بر این، پودر چربی گوشت گاو کانینا یک منبع عالی چربی حیوانی برای بهینه‌سازی وعده‌های غذایی خام است.",
"short_description": "پودر چربی طبیعی گاو جهت افزایش سریع وزن و اشتهاآور پت‌های بدخوراک.",
"ingredients": "72% چربی گوشت گاو خشک شده، 28% عصاره گوشت گاو.",
"usage_and_dosage": "نصف قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن، حداکثر 3 قاشق چایخوری روزانه.",
"variants": [
{
"size": "75g",
"art_no": "131235",
"barcode": "4027565131235",
"buy_price": 760000,
"sell_price": 1520000
}
]
},
{
"name": "Canina Schwarz Kummel Samen (سیاه دانه مصری کانینا)",
"category_slug": "immune",
"description": "دانه‌های زیره سیاه کانینا غنی از اسیدهای چرب غیراشباع چندگانه مانند اسید لینولئیک هستند و همچنین حاوی روغن‌های ضروری و مواد تلخ مانند نیژلین می‌باشند. آنها می‌توانند تأثیر تنظیم‌کننده‌ای بر متابولیسم و هضم داشته باشند. آنها می‌توانند تأثیر مثبتی بر سلامت و عملکرد سگ شما داشته باشند و سیستم دفاعی بدن را تقویت کنند. به دلیل عطر تند و آجیلی، پذیرش دانه‌های زیره سیاه کانینا عموماً بسیار خوب است. برای سگ‌های باردار و شیرده استفاده نشود.",
"short_description": "دانه‌های سیاه دانه مصری جهت تقویت سیستم ایمنی و دفع انگل‌های گوارشی.",
"ingredients": "100% دانه های سیاه دانه مصری.",
"usage_and_dosage": "1 قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن روزانه، حداکثر 4 قاشق چایخوری.",
"variants": [
{
"size": "250g",
"art_no": "131105",
"barcode": "4027565131105",
"buy_price": 1070000,
"sell_price": 2140000
}
]
},
{
"name": "Canina Seelgen Tabletten (قرص جلبک دریایی کانینا)",
"category_slug": "special-care",
"description": "این محصول از جلبک دریایی تازه (از کشت ارگانیک کنترل شده) به دست می‌آید. با توجه به محتوای بالای مواد معدنی، اسیدهای آمینه و عناصر کمیاب موجود در این محصول، این محصول طبیعی با کیفیت بالا، تغذیه نامتعادل را جبران کرده و از طریق محتوای ید، روی و کاروتنوئید خود، متابولیسم پوست و رنگدانه‌های پوشش، بینی و پنجه‌ها را پشتیبانی می‌کند. اسید آمینه لیزین موجود در این محصول ضریب تبدیل غذایی را بهینه می‌کند؛ سلولز و مانیتول هضم را پشتیبانی می‌کنند. محتوای بالای ویتامین‌ها (به عنوان مثال ویتامین سی) سیستم ایمنی را تقویت کرده و تناسب اندام، انگیزه و رفاه را افزایش می‌دهد.",
"short_description": "قرص جلبک دریایی ارگانیک جهت تیره‌سازی پیگمنت بینی، پنجه و براقیت مو.",
"ingredients": "100% جلبک دریایی تازه ارگانیک فشرده شده (Ascophyllum nodosum).",
"usage_and_dosage": "2 قرص به ازای هر 10 کیلوگرم وزن بدن، حداکثر 10 قرص روزانه.",
"variants": [
{
"size": "225g",
"art_no": "130504",
"barcode": "4027565130504",
"buy_price": 1302500,
"sell_price": 2605000
},
{
"size": "750g",
"art_no": "130511",
"barcode": "4027565130511",
"buy_price": 3447500,
"sell_price": 6895000
}
]
},
{
"name": "Canina Bio-Seelgenmehl (پودر جلبک دریایی ارگانیک کانینا)",
"category_slug": "special-care",
"description": "این محصول از جلبک دریایی تازه (از کشت ارگانیک کنترل شده) به دست می‌آید. با توجه به محتوای بالای مواد معدنی، اسیدهای آمینه و عناصر کمیاب موجود در این محصول، این محصول طبیعی با کیفیت بالا، تغذیه نامتعادل را جبران کرده و از طریق محتوای ید، روی و کاروتنوئید خود، متابولیسم پوست و رنگدانه‌های پوشش، بینی و پنجه‌ها را پشتیبانی می‌کند. اسید آمینه لیزین موجود در این محصول ضریب تبدیل غذایی را بهینه می‌کند؛ سلولز و مانیتول هضم را پشتیبانی می‌کنند. محتوای بالای ویتامین‌ها (به عنوان مثال ویتامین سی) سیستم ایمنی را تقویت کرده و تناسب اندام، انگیزه و رفاه را افزایش می‌دهد.",
"short_description": "پودر خالص جلبک دریایی ارگانیک برای تقویت پیگمنت‌های تیره و غده تیروئید.",
"ingredients": "100% پودر خالص جلبک دریایی ارگانیک (Ascophyllum nodosum).",
"usage_and_dosage": "نصف قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن، حداکثر 3 قاشق چایخوری.",
"variants": [
{
"size": "750g",
"art_no": "130412",
"barcode": "4027565130412",
"buy_price": 1845000,
"sell_price": 3690000
}
]
},
{
"name": "Canina TAURIN Fur Katzen (پودر تورین مخصوص گربه‌ها کانینا)",
"category_slug": "special-care",
"description": "تورین یک اسید آمینه مبتنی بر گوگرد است که در تمام سلول‌های بدن حیوانات یافت می‌شود. گربه‌ها نمی‌توانند خود تورین تولید کنند. تورین برای آنها ضروری (حیاتی) است و بنابراین باید با غذا تأمین شود. تورین دارای اثر سوخت و ساز است، سیستم ایمنی را تقویت می‌کند، از بینایی و عملکرد قلب پشتیبانی می‌کند و پوست و پوشش را بهبود می‌بخشد. مواد معدنی و آنزیم‌های ارزشمندی را فراهم می‌کند، مخمر آبجو موجود در این محصول، ویتامین‌های گروه بی که تأثیر مثبتی بر سوخت و ساز بدن دارند و سیستم ایمنی را تقویت می‌کنند.",
"short_description": "پودر تورین ۱۰۰٪ خالص و مخمر آبجو برای سلامت قلب و بینایی گربه‌ها.",
"ingredients": "پودر مخمر، تورین (700 گرم در هر کیلوگرم).",
"usage_and_dosage": "گربه های بالغ: نصف تا 1 قاشق چایخوری روزانه.",
"variants": [
{
"size": "100g",
"art_no": "229505",
"barcode": "4027565229055",
"buy_price": 1175000,
"sell_price": 2350000
}
]
},
{
"name": "Canina Velox Gelenkenergie (ولوکس ژلنک‌انرژی کانینا)",
"category_slug": "joints",
"description": "قدرت متمرکز صدف لب سبز نیوزیلندی برای سیستم اسکلتی عضلانی و عملکرد دوست چهارپای شما! این محصول برای تأمین با کیفیت بالای گلیکوزآمینوگلیکان‌ها به سیستم اسکلتی عضلانی استفاده می‌شود که در صدف لب سبز نیوزیلندی موجود است. گگ‌ها، مانند سولفات کندرویتین، سولفات هپاران و اسید هیالورونیک، ساختارهای بافت همبند مانند تاندون‌ها، رباط‌ها، غضروف مفاصل و دیسک‌های بین مهره‌ای را تثبیت و بازسازی می‌کنند. عملکرد دوست چهارپای شما بهبود می‌یابد و در نتیجه سلامت آنها را ارتقا می‌دهد. اسیدهای چرب امگا طبیعی به طور کلی سهم ارزشمندی در متابولیسم کلی و به طور خاص در متابولیسم مفاصل دارند. ظرفیت بافری غضروف مفصلی در عملکرد فیزیولوژیکی آن پشتیبانی می‌شود.",
"short_description": "پودر ۱۰۰٪ خالص صدف لب‌سبز نیوزیلندی برای بازسازی مفاصل و تاندون‌ها.",
"ingredients": "100% پودر خالص گوشت صدف لب سبز خشک شده به روش انجمادی.",
"usage_and_dosage": "1 قاشق چایخوری به ازای هر 10 کیلوگرم وزن بدن روزانه، حداکثر 2 قاشق چایخوری.",
"variants": [
{
"size": "150g",
"art_no": "701902",
"barcode": "4027565701902",
"buy_price": 2375000,
"sell_price": 4750000
}
]
},
{
"name": "Canina Welpenbrei (سرلاک توله سگ - ولپن‌برای کانینا)",
"category_slug": "nutrition",
"description": "شیر کامل جایگزین شیر مادر با انرژی و ویتامین بالا برای توله سگ های بی مادر از روز 1.",
"ingredients": "لاکتوز، پودر آب پنیر، روغن ماهی، پروتئین 30%، تورین، ویتامین ها.",
"usage_and_dosage": "1 قسمت شیر با 4 قسمت آب 50 درجه مخلوط شود. تغذیه هر 2 ساعت در ابتدا و سپس هر 4 ساعت.",
"description": "فرنی توله سگ کانینا پرورش سالم توله سگ‌های شما را ارتقا می‌دهد. طیف گسترده‌ای از پروتئین‌ها، ترکیب مناسب مواد مغذی و فیبر و محتوای بالای مواد معدنی، عناصر کمیاب و ویتامین‌ها، این خوراک را به یک محصول برتر تبدیل می‌کند. از آنجایی که فقط شیر توله سگ کانینا باید در طول ۳ هفته اول به توله سگ‌ها داده شود، فرنی توله سگ کانینا را می‌توان از این مرحله شروع کرد. توله سگ‌ها هنوز باید شیر توله سگ دریافت کنند و این مقدار فقط باید از ۶ هفتگی کاهش یابد. رشد بهینه دندان‌ها و سیستم اسکلتی تضمین شود. تجویز همزمان ویتامین دی ۲ مجاز نیست.",
"short_description": "غذای کمکی توله سگ از ۳ هفتگی سرشار از کلسیم برای رشد اسکلتی.",
"ingredients": "غلات، گوشت و فرآورده های فرعی حیوانی، فرآورده های فرعی گیاهی، شیر و محصولات لبنی، مخمر.",
"usage_and_dosage": "1 قسمت فرنی با 3 قسمت آب گرم مخلوط شود. از سن 3 تا 6 هفتگی.",
"variants": [
{ "size": "150g", "art_no": "130702", "barcode": "4027565130702", "buy_price": 2315000, "sell_price": 4630000 }
{
"size": "600g",
"art_no": "130603",
"barcode": "4027565130603",
"buy_price": 1417500,
"sell_price": 2835000
}
]
},
{
"name": "Canina PETVITAL Bio-Insect Shocker",
"category_slug": "special-care",
"description": "اسپری دفع کننده محیطی کک، شپش و کنه. تخم ها و انگل های خارجی را به طور موثر از بین می برد.",
"ingredients": "15 گرم در لیتر دودکانوئیک اسید، 5 گرم در لیتر عصاره مارگوسا.",
"usage_and_dosage": "هر 4 تا 6 هفته به صورت اسپری از فاصله 30 سانتی متری روی محل زندگی و خواب حیوان استفاده شود.",
"name": "Canina Welpenmilch (شیر خشک توله سگ کانینا)",
"category_slug": "nutrition",
"description": "شیر مخصوص توله سگ با اجزای سازنده بیولوژیکی ارزشمند: انرژی و قدرت تغذیه با تمام ویتامین‌ها و مواد معدنی لازم برای رشد سالم. جایگزین شیر مادر برای توله سگ‌ها از ۱ روزگی، برای تغذیه توله سگ‌های بی مادر - اگر تولید شیر مادر کافی نیست - برای توله‌های بزرگ - برای آماده‌سازی و در طول شیردهی - به عنوان مکمل برای سگ‌های کار و پرورش - به عنوان مکمل برای سگ‌های مسن و بیمار. طرز تهیه: بر اساس شیر طبیعی سگ مادر، نوشیدنی‌های شیر را با ۴ قسمت وزنی آب (۸۰٪) و ۱ قسمت وزنی شیر توله سگ (۲۰٪) آماده کنید.",
"short_description": "شیر خشک غنی از پروتئین جایگزین کامل شیر مادر مخصوص توله سگ‌ها.",
"ingredients": "لاکتوز، پودر آب پنیر، چربی نارگیل، پروتئین ایزوله سویا، پودر تخم مرغ کامل، روغن ماهی سالمون.",
"usage_and_dosage": "مخلوط 1:4 با آب گرم. هر 2 ساعت در روزهای اول زندگی تغذیه شود.",
"variants": [
{ "size": "100ml", "art_no": "741304", "barcode": "4027565741304", "buy_price": 1593000, "sell_price": 3186000 },
{ "size": "250ml", "art_no": "741311", "barcode": "4027565741311", "buy_price": 2730000, "sell_price": 5460000 }
{
"size": "150g",
"art_no": "130702",
"barcode": "4027565130702",
"buy_price": 1157500,
"sell_price": 2315000
}
]
},
{
"name": "Canina Mikrosilber Zahngel",
"name": "Canina PETVITAL Bio-Insect Shocker (اسپری دفع حشرات بیو اینسکت شوکر)",
"category_slug": "special-care",
"description": "ژل دندانی با میکروسیلور که پلاک، جرم و التهاب لثه را هدف قرار می دهد. بیوفلاونوئیدها از باکتری های پلاک جلوگیری می کنند.",
"ingredients": "آب، نقره، گلیسیرین، عصاره دانه گریپ فروت.",
"usage_and_dosage": "روزانه عصرها 1 ساعت پس از غذا روی دندان های آسیاب مالیده شود. سگ تا 10 کیلو: 1 سانتی متر ژل.",
"description": "کک، شپش، کنه، شپش‌های گزنده و کنه‌ها را به طور قابل اعتمادی از محیط حیوان دور نگه می‌دارد. این محصول یک ماده دفع‌کننده است که پس از استفاده اولیه، به طور مؤثری انگل‌های خارجی معمول (انگل‌های خارجی) را از بین می‌برد، به طوری که هیچ انگل یا تخمی نمی‌تواند در محیط حیوان مستقر شود. در صورت آلودگی به کک، توصیه می‌شود حیوان را علاوه بر این با محصول مخصوص سگ‌ها/گربه‌ها درمان کنید، که اثر دفع‌کنندگی روی خود حیوان دارد. تخم‌ها و تخم‌های کک را نیز می‌توان با شامپوی با اثر فوری از روی پوشش حیوان پاک کرد. نحوه استفاده: مناطق آلوده به آفت را به مقدار کم با این محصول از فاصله تقریبی ۳۰ سانتی‌متر، به خصوص محل خواب و زندگی حیوان (تقریباً هر ۴ تا ۶ هفته) اسپری کنید.",
"short_description": "اسپری طبیعی دافع حشرات، کک، کنه و شپش از محیط زندگی پت.",
"ingredients": "15 گرم در لیتر اسید دودکانوئیک، 5 گرم در لیتر عصاره مارگوسا.",
"usage_and_dosage": "محیط آلوده را از فاصله 30 سانتی متری اسپری کنید. هر 4 تا 6 هفته یکبار تکرار شود.",
"variants": [
{ "size": "50ml", "art_no": "742639", "barcode": "4027565742639", "buy_price": 2765000, "sell_price": 5530000 }
{
"size": "100ml",
"art_no": "741304",
"barcode": "4027565741304",
"buy_price": 796500,
"sell_price": 1593000
},
{
"size": "250ml",
"art_no": "741311",
"barcode": "4027565741311",
"buy_price": 1365000,
"sell_price": 2730000
}
]
},
{
"name": "Canina NovaGard Green Augenpflege",
"name": "Canina Mikrosilber Zahngel (ژل دندان میکروسیلور کانینا)",
"category_slug": "special-care",
"description": "قطره چشم برای تمیز کردن و مراقبت از چشم های گریان و گرفتگی چشم. نقره کلوئیدی از التهاب جلوگیری می کند.",
"ingredients": "آب بدون مواد معدنی، هیالورونات سدیم، نقره کلوئیدی.",
"usage_and_dosage": "چند قطره روی کیسه ملتحمه ریخته و اطراف چشم پاک شود.",
"description": "ژل دندانی میکروسیلور کانینا با عصاره دانه گریپ فروت مشکلات اصلی بهداشت دهان و دندان در سگ‌ها و گربه‌ها، مانند پلاک، جرم و التهاب لثه را برطرف می‌کند. اثر تمیزکنندگی دندان بر اساس فرمولاسیونی از میکروسیلور و عصاره دانه گریپ فروت است که به خوبی تحمل می‌شود. در صورت استفاده منظم، تأثیر واقعی بر تشکیل جرم جدید دارد. مصرف آن بی خطر است. بیوفلاونوئیدهای موجود در عصاره دانه گریپ فروت دارای اثر آنتی‌اکسیدانی هستند. آنها به محافظت از دندان‌ها در برابر حملات باکتری‌های پلاک کمک می‌کنند. ویتامین سی موجود در این محصول از سلامت لثه‌ها پشتیبانی می‌کند و اسیدهای آمینه و چرب عصاره دانه گریپ فروت، حل شدن پلاک دندان را تقویت می‌کنند.",
"short_description": "ژل دندان تخصصی برای از بین بردن پلاک، تارتار و بوی بد دهان.",
"ingredients": "آب، پلی گلیسریل-4-کاپرات، کربومر، نقره میکروسیلور، عصاره دانه گریپ فروت، ویتامین C.",
"usage_and_dosage": "روزانه یک بار ترجیحا عصرها روی لثه و قسمت بیرونی دندان ها مالیده شود.",
"variants": [
{ "size": "10ml", "art_no": "NG0013", "barcode": "4260118880130", "buy_price": 3180000, "sell_price": 6360000 }
{
"size": "50ml",
"art_no": "742639",
"barcode": "4027565742639",
"buy_price": 1382500,
"sell_price": 2765000
}
]
},
{
"name": "Canina NovaGard Green Pfotenpflege",
"name": "Canina Nova Gard Green Augenpflege (قطره تمیزکننده چشم نوواگارد گرین)",
"category_slug": "special-care",
"description": "کرم مراقبت از پنجه 100% طبیعی برای تسکین پوست خارش دار، التیام زخم ها و خراشیدگی پوست سگ ها در زمستان و تابستان.",
"ingredients": "وازلین، روغن پالپ سنجد دریایی، روغن کالاندولا، پانتنول.",
"usage_and_dosage": "چندین بار در روز روی نواحی آسیب دیده بمالید و ماساژ دهید.",
"description": "مراقبت صحیح از چشم‌ها و گوش‌ها برای سلامت و رفاه سگ‌ها و گربه‌ها بسیار مهم است. آبریزش یا گرفتگی چشم، مانند خارش گوش‌ها، به ویژه در گربه‌ها به طور کلی و در سگ‌هایی با گوش‌های افتاده، یک مشکل روزمره است. پیشگیری با یک محصول مراقبتی طبیعی، ملایم و مؤثر که به طور مؤثر از درمان دامپزشکی نیز پشتیبانی می‌کند. مراقبت از چشم کانینا (برای سگ‌ها و گربه‌ها) چشم‌های گرفته/گریان حیوانات (به علاوه ناحیه چشم) را تمیز و مراقبت می‌کند. با چکاندن قطره در چشم، چشم‌های تحریک شده تسکین می‌یابند. این محصول را می‌توان برای مراقبت روزانه از چشم استفاده کرد و از چسبندگی و سوزش مجدد جلوگیری می‌کند. استفاده نوآورانه از نقره کلوئیدی از التهاب جلوگیری می‌کند.",
"short_description": "قطره استریل حاوی نقره کلوئیدی جهت رفع التهاب و ترشحات چشم پت.",
"ingredients": "آب، هیالورونات سدیم، نقره کلوئیدی، رتینول پالمیتات.",
"usage_and_dosage": "چند قطره روزانه روی کیسه ملتحمه چشم ریخته شود. اطراف چشم تمیز شود.",
"variants": [
{ "size": "50ml", "art_no": "NG0016", "barcode": "4260118880161", "buy_price": 2500000, "sell_price": 5000000 }
{
"size": "10ml",
"art_no": "NG0013",
"barcode": "4260118880130",
"buy_price": 1590000,
"sell_price": 3180000
}
]
},
{
"name": "Canina Nova Gard Green Pfotenpflege (کرم محافظت پنجه نوواگارد گرین)",
"category_slug": "special-care",
"description": "این محصول (برای سگ‌ها) از بهبود سوزش‌های جزئی و سطحی پوست، زخم‌های کوچک ناشی از گزش و نواحی خراشیده پوست در سگ‌ها با پایه طبیعی (وازلین) پشتیبانی می‌کند. پوست خارش‌دار و تحریک شده تسکین می‌یابد. این محصول عموماً از فرآیندهای بازسازی پوست پس از درمان اولیه زخم/سوزش پوست و مراقبت از جای زخم پشتیبانی می‌کند. این کرم حاوی روغن‌های گیاهی با کیفیت بالا و بدون مواد نگهدارنده است. این محصول همچنین می‌تواند در تابستان و زمستان برای مراقبت از پدهای پنجه استفاده شود. روغن خالص و پرس سرد خولان دریایی: دارای خواص ضد التهابی، ضد باکتری و التیام‌بخش پوست است. روغن کالاندولا دارای خواص التیام‌بخش زخم و تسکین‌دهنده درد است و پانتنول (پیش‌ساز ویتامین ب ۵) از بازسازی پوست پشتیبانی می‌کند.",
"short_description": "کرم پد پنجه ۱۰۰٪ طبیعی ترمیم‌کننده سوزش، خشکی و ترک پنجه سگ.",
"ingredients": "وازلین، روغن خولان دریایی (سنجد دریایی)، روغن کالاندولا، پانتنول.",
"usage_and_dosage": "چندین بار در روز روی نواحی آسیب دیده مالیده و ماساژ داده شود.",
"variants": [
{
"size": "50ml",
"art_no": "NG0016",
"barcode": "4260118880161",
"buy_price": 1250000,
"sell_price": 2500000
}
]
}
]

View File

@ -33,6 +33,42 @@ function parseSize(size: string): { unit: string; packageSize: number } {
return { unit: 'g', packageSize: 100 };
}
function getProductImageUrl(baseSlug: string): string {
const images: Record<string, string> = {
'canina-ballaststoff-mix': 'https://www.canina.de/media/4c/32/38/1715077271/Ballaststoff-Mix-140108-V1-Canino-100g_600x600.webp',
'canina-canhydrox-gag': 'https://www.canina.de/media/83/86/e1/123000_123005_Canhydrox_GAG_Canina-Pharma_1280x1280.png',
'canina-eierschalenpulver': 'https://www.canina.de/media/fb/d3/18/120208_Eierschalenpulver_Canina-Pharma_1280x1280.png',
'canina-flexan': 'https://www.canina.de/media/01/be/93/710003_Flexan_Canina-Pharma_1280x1280.png',
'canina-herz-vital': 'https://www.canina.de/media/b9/8b/4c/112036_Herz_Vital_Canina-Pharma_1280x1280.png',
'canina-immun-booster-paste': 'https://www.canina.de/media/8c/fb/8e/311000_Canino_Immun_Booster_Paste_Abwehrkraefte_1280x1280.png',
'canina-katzenmilch': 'https://www.canina.de/media/6b/48/81/731000_Canino_Lachsoel_Lachs_Oel_1280x1280.png',
'canina-lachs-l': 'https://www.canina.de/media/6b/48/81/731000_Canino_Lachsoel_Lachs_Oel_1280x1280.png',
'canina-lachs-ol': 'https://www.canina.de/media/6b/48/81/731000_Canino_Lachsoel_Lachs_Oel_1280x1280.png',
'canina-marine-lmischung-premium': 'https://www.canina.de/media/ad/28/71/153008_Marine_Oelmischung_Premium_Canina-Pharma_1280x1280.png',
'canina-moortrnke': 'https://www.canina.de/media/9d/b2/87/112708_Moortraenke_Canina-Pharma_1280x1280.png',
'canina-petvital-arthro-tabletten': 'https://www.canina.de/media/90/a6/50/723003_PETVITAL_Arthro_Tabletten_Canina-Pharma_1280x1280.png',
'canina-petvital-bio-aktivator': 'https://www.canina.de/media/c9/2e/0f/712007_PETVITAL_Bio_Aktivator_Canina-Pharma_1280x1280.png',
'canina-petvital-biotin-tabs': 'https://www.canina.de/media/e1/9b/6c/702008_PETVITAL_Biotin_Tabs_Canina-Pharma_1280x1280.png',
'canina-petvital-energy-gel': 'https://www.canina.de/media/7f/0f/06/712106_PETVITAL_Energy_Gel_Canina-Pharma_1280x1280.png',
'canina-petvital-mineral-tabs': 'https://www.canina.de/media/70/4e/f2/723102_PETVITAL_Mineral_Tabs_Canina-Pharma_1280x1280.png',
'canina-petvital-gag': 'https://www.canina.de/media/58/05/92/723201_723300_PETVITAL_GAG_Canina-Pharma_1280x1280.png',
'canina-petvital-vitamin-tabs': 'https://www.canina.de/media/2c/80/7e/712205_PETVITAL_Vitamin_Tabs_Canina-Pharma_1280x1280.png',
'canina-rinderblut-pulver': 'https://www.canina.de/media/14/d0/0d/792016_791514_Rinderblut_Pulver_Canina-Pharma_1280x1280.png',
'canina-rinderfett-pulver': 'https://www.canina.de/media/9a/31/59/131235_Rinderfett_Pulver_Canina-Pharma_1280x1280.png',
'canina-schwarz-kmmel-samen': 'https://www.canina.de/media/a9/c8/aa/131105_Schwarzkuemmelsamen_Canina-Pharma_1280x1280.png',
'canina-seealgen-bio-seealgenmehl': 'https://www.canina.de/media/e4/c4/b2/130504_130511_130412_Seealgen_Bio-Seealgenmehl_Canina-Pharma_1280x1280.png',
'canina-taurin-fr-katzen': 'https://www.canina.de/media/c6/aa/62/229505_Taurin_fuer_Katzen_Canina-Pharma_1280x1280.png',
'canina-velox-gelenkenergie': 'https://www.canina.de/media/1a/0c/33/701902_Velox_Gelenkenergie_Canina-Pharma_1280x1280.png',
'canina-welpenbrei': 'https://www.canina.de/media/98/95/43/130603_Welpenbrei_Canina-Pharma_1280x1280.png',
'canina-welpenmilch': 'https://www.canina.de/media/2c/e0/75/130702_Welpenmilch_Canina-Pharma_1280x1280.png',
'canina-petvital-bio-insect-shocker': 'https://www.canina.de/media/76/a4/1c/741304_741311_PETVITAL_Bio_Insect_Shocker_Canina-Pharma_1280x1280.png',
'canina-mikrosilber-zahngel': 'https://www.canina.de/media/9d/5e/54/131454_Mikrosilber_Zahngel_Canina-Pharma_1280x1280.png',
'canina-novagard-green-augenpflege': 'https://www.canina.de/media/6b/48/81/731000_Canino_Lachsoel_Lachs_Oel_1280x1280.png',
'canina-novagard-green-pfotenpflege': 'https://www.canina.de/media/6b/48/81/731000_Canino_Lachsoel_Lachs_Oel_1280x1280.png',
};
return images[baseSlug] || `/products/${baseSlug}.png`;
}
async function findOrCreateCategory(slug: string): Promise<{ id: string; slug: string }> {
const CATEGORY_NAMES: Record<string, string> = {
'joints': 'مفاصل و استخوان',
@ -51,6 +87,70 @@ async function findOrCreateCategory(slug: string): Promise<{ id: string; slug: s
});
}
const PRODUCT_SYMPTOMS_MAP: Record<string, string[]> = {
'canina-ballaststoff-mix': ["اسهال", "یبوست", "اسهال مزمن", "تنظیم فلور روده"],
'canina-canhydrox-gag': ["درد مفاصل", "سختی در بلند شدن", "لنگیدن", "رشد سریع توله‌سگ", "تقویت رباط و تاندون"],
'canina-eierschalenpulver': ["کمبود کلسیم", "رژیم خام گوشتی", "سلامت دندان‌ها"],
'canina-flexan': ["درد مفاصل", "سختی در بلند شدن", "لنگیدن", "تخریب غضروف"],
'canina-herz-vital': ["نارسایی قلبی", "بی‌حالی", "کاهش انرژی"],
'canina-immun-booster-paste': ["ضعف بعد از بیماری", "اسهال", "ضعف توله‌سگ", "کاهش ایمنی"],
'canina-katzenmilch': ["تغذیه بچه گربه", "بی‌مادری بچه گربه"],
'canina-lachs-ol': ["خشکی پوست", "ریزش مو", "خارش", "التهاب پوست"],
'canina-marine-lmischung-premium': ["خشکی پوست", "ریزش مو", "التهاب"],
'canina-moortrnke': ["اسهال", "اشتهای کم", "مسمومیت گوارشی"],
'canina-petvital-arthro-tabletten': ["درد مفاصل", "سختی در بلند شدن", "لنگیدن"],
'canina-petvital-bio-aktivator': ["کاهش ایمنی", "خستگی", "دوران نقاهت"],
'canina-petvital-biotin-tabs': ["ریزش مو", "خشکی پوست", "شکنندگی ناخن"],
'canina-petvital-energy-gel': ["بی‌اشتهایی", "کاهش وزن", "کمبود انرژی"],
'canina-petvital-mineral-tabs': ["کمبود مواد معدنی", "ضعف استخوان"],
'canina-petvital-gag': ["سختی در بلند شدن", "لنگیدن"],
'canina-petvital-vitamin-tabs': ["کمبود ویتامین", "کاهش انرژی"],
'canina-rinderblut-pulver': ["کم‌خونی", "بی‌اشتهایی", "دوران رشد"],
'canina-rinderfett-pulver': ["کمبود وزن", "اشتهای کم"],
'canina-schwarz-kmmel-samen': ["انگل روده", "ضعف ایمنی"],
'canina-seealgen-bio-seealgenmehl': ["کاهش پیگمنت مو", "کمبود تیروئید"],
'canina-taurin-fr-katzen': ["مشکل بینایی گربه", "نارسایی قلبی گربه"],
'canina-velox-gelenkenergie': ["درد مفاصل", "لنگیدن"],
'canina-welpenbrei': ["تغذیه توله‌سگ", "از شیر گرفتن توله‌سگ"],
'canina-welpenmilch': ["تغذیه توله‌سگ", "بی‌مادری توله‌سگ"],
'canina-petvital-bio-insect-shocker': ["کک و کنه", "انگل‌های پوستی"],
'canina-mikrosilber-zahngel': ["جرم دندان", "بوی بد دهان", "التهاب لثه"],
'canina-novagard-green-augenpflege': ["ترشحات چشم", "التهاب چشم", "کثیفی چشم"],
'canina-novagard-green-pfotenpflege': ["ترک پنجه", "خشکی پنجه", "زخم پنجه"],
};
const PRODUCT_TAGLINES_MAP: Record<string, string> = {
'canina-ballaststoff-mix': "مکمل فیبر پری‌بیوتیک جهت تنظیم گوارش و پایداری فلور روده",
'canina-canhydrox-gag': "فرمولاسیون ویژه دامپزشکی برای پایداری بافت‌های همبند، غضروف و استخوان‌ها",
'canina-eierschalenpulver': "کلسیم ارگانیک صد در صد طبیعی مناسب رژیم‌های غذایی خام (BARF)",
'canina-flexan': "پپتیدهای کلاژن زیست‌فعال برای بهینه‌سازی دامنه حرکتی و بازسازی مفصلی",
'canina-herz-vital': "تقویت عملکرد فیزیولوژیک عضله قلب و افزایش نشاط حیوان",
'canina-immun-booster-paste': "تامین فوری ایمونوگلوبولین‌های آغوز و تثبیت فلور روده در زمان نقاهت",
'canina-katzenmilch': "شیر خشک جایگزین بچه گربه حاوی تورین و فاقد لاکتوز مزاحم",
'canina-lachs-ol': "اسیدهای چرب ضروری امگا ۳ برای درخشش پوشش مویی و سلامت پوست",
'canina-marine-lmischung-premium': "ترکیب روغن‌های ممتاز دریایی غنی از EPA و DHA جهت کاهش التهابات پوستی",
'canina-moortrnke': "عصاره پیت طبیعی برای جذب بیولوژیکی سموم و بهبود ترشحات گوارشی",
'canina-petvital-arthro-tabletten': "فرمول گیاهی-معدنی برای کاهش دردهای حاد مفصلی و تسهیل در بلند شدن",
'canina-petvital-bio-aktivator': "آمینو اسیدها و آهن فعال جهت بازسازی قوای جسمی و بهبود اشتها",
'canina-petvital-biotin-tabs': "دوز بالای بیوتین و ویتامین ب برای توقف سریع ریزش مو و بازسازی ناخن",
'canina-petvital-energy-gel': "کنسانتره انرژی بالا همراه با الکترولیت‌ها برای سگ‌ها و گربه‌های ضعیف",
'canina-petvital-mineral-tabs': "مواد معدنی و عناصر کمیاب جهت تراکم استخوانی و دوران بارداری و رشد",
'canina-petvital-gag': "ترکیب صدف لب‌سبز نیوزیلند و اسیدهای آمینه جهت تقویت رباط‌ها و تاندون‌ها",
'canina-petvital-vitamin-tabs': "مولتی‌ویتامین کامل روزانه برای تقویت سیستم دفاعی و افزایش شادابی پت",
'canina-rinderblut-pulver': "پودر خون گاو غنی از آهن طبیعی و هموگلوبین برای بهبود اشتها و رفع کم‌خونی",
'canina-rinderfett-pulver': "مکمل چربی طبیعی با طعم‌دهندگی بالا جهت جبران کمبود وزن و افزایش انرژی",
'canina-schwarz-kmmel-samen': "دانه‌های سیاه دانه مصری برای پشتیبانی متابولیک و دفع طبیعی انگل‌ها",
'canina-seealgen-bio-seealgenmehl': "جلبک دریایی ارگانیک سرشار از ید طبیعی جهت درخشش و تیره کردن پیگمنت‌های مو و بینی",
'canina-taurin-fr-katzen': "اسید آمینه تورین خالص برای سلامت بینایی و پیشگیری از کاردیومیوپاتی گربه‌ها",
'canina-velox-gelenkenergie': "پودر صد در صد صدف لب‌سبز نیوزیلند برای مفاصل، غضروف‌ها و رباط‌ها",
'canina-welpenbrei': "غذای کمکی بچه سگ‌ها برای انتقال آسان از شیر مادر به غذای جامد",
'canina-welpenmilch': "شیر خشک تخصصی توله‌سگ غنی شده با ویتامین‌ها و املاح معدنی فاقد لاکتوز",
'canina-petvital-bio-insect-shocker': "اسپری دافع انگل‌های خارجی کک، کنه و شپش صد در صد طبیعی و بدون سموم شیمیایی",
'canina-mikrosilber-zahngel': "ژل دندان با نقره میکروسیلور جهت مبارزه با بوی بد دهان، پلاک و عفونت لثه",
'canina-novagard-green-augenpflege': "محلول ملایم شستشوی چشم سگ و گربه برای پاک کردن ترشحات و کاهش التهابات",
'canina-novagard-green-pfotenpflege': "مومیایی محافظ پنجه‌ها برای التیام خشکی، قرمزی و ترک خوردگی پنجه‌ها در برف و سرما"
};
async function main() {
console.log('Seeding products from seed-products-data.json...');
@ -67,10 +167,20 @@ async function main() {
for (const item of productsData) {
const baseName = item.name;
const cleanDesc = item.description;
const cleanShortDesc = item.short_description || '';
const cleanIngredients = item.ingredients || '';
const cleanDosage = item.usage_and_dosage || '';
const suitableFor = determineSuitableFor(baseName, cleanDesc);
const baseSlug = slugify(baseName);
// Split baseName into nameFa and nameEn
let baseNameFa = baseName;
let baseNameEn = baseName;
const matchName = baseName.match(/([^(]+)\(([^)]+)\)/);
if (matchName) {
baseNameEn = matchName[1].trim();
baseNameFa = matchName[2].trim();
}
const baseSlug = slugify(baseNameEn);
const categorySlug = item.category_slug || 'supplements';
const category = await findOrCreateCategory(categorySlug);
@ -87,7 +197,8 @@ async function main() {
const slug = `${baseSlug}-${sizeSlug}`;
// Build display name with size
const displayName = `${baseName} - ${sizeLabel}`;
const nameFa = `${baseNameFa}`;
const nameEn = `${baseNameEn} - ${sizeLabel}`;
const priceDisplay = `${sellPrice.toLocaleString()} تومان`;
const product = await prisma.product.upsert({
@ -96,8 +207,11 @@ async function main() {
barcode,
slug,
productGroup: baseSlug,
name: displayName,
nameFa,
nameEn,
scientificTagline: PRODUCT_TAGLINES_MAP[baseSlug] || null,
description: cleanDesc,
shortDescription: cleanShortDesc,
ingredients: cleanIngredients,
categoryId: category.id,
categorySlug: category.slug,
@ -108,15 +222,18 @@ async function main() {
packageSize,
dosageLogic: cleanDosage,
suitableFor,
imageUrl: `/products/${baseSlug}.png`,
imageUrl: getProductImageUrl(baseSlug),
},
create: {
artNo,
barcode,
slug,
productGroup: baseSlug,
name: displayName,
nameFa,
nameEn,
scientificTagline: PRODUCT_TAGLINES_MAP[baseSlug] || null,
description: cleanDesc,
shortDescription: cleanShortDesc,
ingredients: cleanIngredients,
categoryId: category.id,
categorySlug: category.slug,
@ -127,15 +244,14 @@ async function main() {
packageSize,
dosageLogic: cleanDosage,
suitableFor,
imageUrl: `/products/${baseSlug}.png`,
imageUrl: getProductImageUrl(baseSlug),
}
});
// Seed ingredients (split by '،' or '، ')
// Seed ingredients
await prisma.productIngredient.deleteMany({ where: { productId: product.id } });
if (cleanIngredients) {
const parts = cleanIngredients.split(/[،,]/).map((s: string) => s.trim()).filter(Boolean);
// Only take the first meaningful items (up to ~20)
const meaningful = parts.slice(0, 20);
for (const ing of meaningful) {
const truncated = ing.substring(0, 150);
@ -147,6 +263,15 @@ async function main() {
}
}
// Seed symptoms
await prisma.productSymptom.deleteMany({ where: { productId: product.id } });
const symptomsList = PRODUCT_SYMPTOMS_MAP[baseSlug] || [];
for (const sym of symptomsList) {
await prisma.productSymptom.create({
data: { productId: product.id, symptom: sym }
});
}
variantCount++;
}
}

View File

@ -101,7 +101,7 @@ export class AdminService {
const [data, total] = await Promise.all([
this.prisma.product.findMany({
where, skip, take: limit, orderBy: { createdAt: 'desc' },
include: { category: true }
include: { category: true, symptoms: true }
}),
this.prisma.product.count({ where })
]);
@ -114,10 +114,11 @@ export class AdminService {
}
async createProduct(data: any) {
return this.prisma.product.create({
const product = await this.prisma.product.create({
data: {
artNo: data.artNo,
name: data.name,
nameFa: data.nameFa,
nameEn: data.nameEn,
scientificTagline: data.scientificTagline,
description: data.description,
shortDescription: data.shortDescription,
@ -137,14 +138,26 @@ export class AdminService {
slug: data.slug || data.artNo,
}
});
if (data.symptoms && Array.isArray(data.symptoms)) {
await this.prisma.productSymptom.createMany({
data: data.symptoms.map((s: string) => ({
productId: product.id,
symptom: s.trim()
})).filter((s: any) => s.symptom.length > 0)
});
}
return product;
}
async updateProduct(id: string, data: any) {
return this.prisma.product.update({
const product = await this.prisma.product.update({
where: { id },
data: {
artNo: data.artNo,
name: data.name,
nameFa: data.nameFa,
nameEn: data.nameEn,
scientificTagline: data.scientificTagline,
description: data.description,
shortDescription: data.shortDescription,
@ -164,6 +177,20 @@ export class AdminService {
slug: data.slug || data.artNo,
}
});
if (data.symptoms !== undefined && Array.isArray(data.symptoms)) {
await this.prisma.productSymptom.deleteMany({ where: { productId: id } });
if (data.symptoms.length > 0) {
await this.prisma.productSymptom.createMany({
data: data.symptoms.map((s: string) => ({
productId: id,
symptom: s.trim()
})).filter((s: any) => s.symptom.length > 0)
});
}
}
return product;
}
async deleteProduct(id: string) {

View File

@ -54,13 +54,13 @@ export class ReportsService {
const topProductsIds = orderItems.map(item => item.productId).filter(Boolean) as string[];
const productsInfo = await this.prisma.product.findMany({
where: { id: { in: topProductsIds } },
select: { id: true, name: true }
select: { id: true, nameFa: true, nameEn: true }
});
const bestSellers = orderItems.map(item => {
const p = productsInfo.find(prod => prod.id === item.productId);
return {
name: p ? p.name : 'محصول نامشخص',
name: p ? `${p.nameFa} (${p.nameEn})` : 'محصول نامشخص',
quantity: item._sum.quantity || 0
};
});

View File

@ -24,7 +24,8 @@ export class HomeService {
product: {
select: {
id: true,
name: true,
nameFa: true,
nameEn: true,
slug: true,
imageUrl: true,
priceValue: true,

View File

@ -12,4 +12,9 @@ export class GetProductsDto extends PaginationDto {
@IsOptional()
@IsEnum(['سگ', 'گربه', 'all'])
petType?: string;
@ApiPropertyOptional({ description: 'فیلتر بر اساس یک علامت درمانی خاص' })
@IsOptional()
@IsString()
symptom?: string;
}

View File

@ -61,6 +61,41 @@ export class ProductsController {
return this.productsService.findAll(query);
}
@Get('filters')
@ApiOperation({ summary: 'دریافت فیلترهای فعال محصولات (دسته، علائم، نوع پت)' })
@ApiOkResponse({
description: 'لیست فیلترهای پویا استخراج شده از دیتابیس',
schema: {
example: {
categories: [{ id: '1', name: 'مفاصل و استخوان', slug: 'joints' }],
symptoms: ['لنگش', 'ریزش مو'],
petTypes: ['سگ', 'گربه', 'هر دو']
}
}
})
getActiveFilters() {
return this.productsService.getActiveFilters();
}
@Get('navigation-filters')
@ApiOperation({ summary: 'دریافت فیلترها و راهکارهای ناوبری (دسته با علائم مربوطه)' })
@ApiOkResponse({
description: 'ساختار درختی فیلترها و تگ‌های درمانی واقعی متصل به محصولات',
schema: {
example: [
{
id: '1',
name: 'مفاصل و استخوان',
slug: 'joints',
symptoms: ['درد مفاصل', 'لنگش']
}
]
}
})
getNavigationFilters() {
return this.productsService.getNavigationFilters();
}
@Get(':id')
@ApiOperation({ summary: 'دریافت اطلاعات محصول با شناسه یکتا (ID)' })
@ApiOkResponse({

View File

@ -7,7 +7,7 @@ export class ProductsService {
constructor(private prisma: PrismaService) {}
async findAll(filters: GetProductsDto) {
const { category, petType, search, page = 1, limit = 10, sortBy = 'createdAt', sortOrder = 'desc' } = filters;
const { category, petType, search, symptom, page = 1, limit = 10, sortBy = 'createdAt', sortOrder = 'desc' } = filters;
const whereClause: any = {};
@ -19,11 +19,42 @@ export class ProductsService {
whereClause.suitableFor = { in: [petType, 'هر دو'] };
}
// Filter by a specific symptom (from URL param ?symptom=...)
if (symptom) {
whereClause.symptoms = {
some: {
symptom: { contains: symptom, mode: 'insensitive' }
}
};
}
if (search) {
whereClause.OR = [
{ name: { contains: search, mode: 'insensitive' } },
// If symptom filter is already applied, extend via AND to also search names/desc
// If not, use OR across names, description AND symptoms
const searchConditions = [
{ nameFa: { contains: search, mode: 'insensitive' } },
{ nameEn: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
{ shortDescription: { contains: search, mode: 'insensitive' } },
{
symptoms: {
some: {
symptom: { contains: search, mode: 'insensitive' }
}
}
},
];
if (symptom) {
// Already have a symptom filter; combine with AND
whereClause.AND = [
{ symptoms: whereClause.symptoms },
{ OR: searchConditions.filter(c => !('symptoms' in c)) },
];
delete whereClause.symptoms;
} else {
whereClause.OR = searchConditions;
}
}
const skip = (page - 1) * limit;
@ -63,4 +94,74 @@ export class ProductsService {
},
});
}
async getActiveFilters() {
const [categories, symptomsDb, petTypesDb] = await Promise.all([
this.prisma.category.findMany({
where: {
products: {
some: {}
}
},
select: {
id: true,
name: true,
slug: true,
}
}),
this.prisma.productSymptom.findMany({
distinct: ['symptom'],
select: {
symptom: true,
}
}),
this.prisma.product.findMany({
distinct: ['suitableFor'],
select: {
suitableFor: true,
}
})
]);
return {
categories,
symptoms: symptomsDb.map(s => s.symptom).filter(Boolean),
petTypes: petTypesDb.map(p => p.suitableFor).filter(Boolean),
};
}
async getNavigationFilters() {
const categories = await this.prisma.category.findMany({
where: {
products: {
some: {}
}
},
include: {
products: {
include: {
symptoms: true
}
}
}
});
return categories.map(cat => {
const symptomSet = new Set<string>();
cat.products.forEach(p => {
p.symptoms.forEach(s => {
if (s.symptom) {
symptomSet.add(s.symptom);
}
});
});
return {
id: cat.id,
name: cat.name,
slug: cat.slug,
symptoms: Array.from(symptomSet)
};
});
}
}

View File

@ -9,7 +9,7 @@ export default function Layout() {
return (
<div className="flex min-h-screen bg-gray-50 text-gray-900 font-vazir relative overflow-hidden" dir="rtl">
<Sidebar isOpen={isMobileMenuOpen} setIsOpen={setIsMobileMenuOpen} />
<div className="flex-1 flex flex-col min-h-screen w-full lg:w-auto">
<div className="flex-1 flex flex-col min-h-screen w-full lg:w-auto lg:pr-64">
<Topbar toggleMenu={() => setIsMobileMenuOpen(!isMobileMenuOpen)} />
<main className="flex-1 p-6 overflow-y-auto">
<Outlet />

View File

@ -1,5 +1,7 @@
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Users, ShoppingCart, Tag, Settings, LogOut, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image } from 'lucide-react';
import api from '../services/api';
const menuGroups = [
{
@ -50,6 +52,24 @@ interface SidebarProps {
export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
const location = useLocation();
const navigate = useNavigate();
const [newOrdersCount, setNewOrdersCount] = useState(0);
useEffect(() => {
const fetchOrdersCount = async () => {
try {
const response = await api.get('/admin/dashboard/stats');
if (response.data?.success) {
setNewOrdersCount(response.data.data.newOrders);
}
} catch (err) {
console.error('Failed to fetch stats in sidebar', err);
}
};
fetchOrdersCount();
// Poll stats every 60 seconds
const interval = setInterval(fetchOrdersCount, 60000);
return () => clearInterval(interval);
}, []);
const handleLogout = () => {
localStorage.removeItem('adminToken');
@ -66,7 +86,7 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
/>
)}
<aside className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:sticky top-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
<aside className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
}`}>
<div className="h-16 flex items-center justify-center border-b border-gray-200 shrink-0">
<h1 className="text-xl font-black text-purple-600">کانینا | ادمین‌پنل</h1>
@ -92,6 +112,11 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
>
<Icon className="w-5 h-5" />
<span>{item.label}</span>
{item.label === 'سفارشات' && newOrdersCount > 0 && (
<span className="mr-auto bg-purple-600 text-white text-[11px] font-black px-2 py-0.5 rounded-full min-w-[20px] text-center">
{newOrdersCount}
</span>
)}
</Link>
);
})}

View File

@ -1,13 +1,57 @@
import { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Bell, Search, UserCircle, Menu } from 'lucide-react';
interface TopbarProps {
toggleMenu: () => void;
}
const SEARCHABLE_PAGES = [
{ label: 'داشبورد (خلاصه وضعیت فروشگاه)', path: '/' },
{ label: 'گزارشات کامل فروش و بازدیدها', path: '/reports' },
{ label: 'مدیریت سفارشات مشتریان', path: '/orders' },
{ label: 'مدیریت محصولات فروشگاه', path: '/products' },
{ label: 'دسته‌بندی‌های محصولات (Categories)', path: '/categories' },
{ label: 'کدهای تخفیف و کوپن‌های خرید', path: '/coupons' },
{ label: 'مدیریت کاربران و مشتریان سایت', path: '/users' },
{ label: 'مدیریت سگ‌ها و گربه‌ها (Pets)', path: '/pets' },
{ label: 'مدیریت مقالات وبلاگ', path: '/blogs' },
{ label: 'مدیریت دانشنامه و مقالات علمی (Wiki)', path: '/wiki' },
{ label: 'تنظیمات کلی سیستم و درگاه پرداخت', path: '/settings' },
{ label: 'متون رابط کاربری و ترجمه‌ها', path: '/ui-texts' },
{ label: 'مدیریت رسانه، عکس‌ها و گالری', path: '/media' },
];
export default function Topbar({ toggleMenu }: TopbarProps) {
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState('');
const [showResults, setShowResults] = useState(false);
const searchContainerRef = useRef<HTMLDivElement>(null);
// Close search results dropdown on clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchContainerRef.current && !searchContainerRef.current.contains(event.target as Node)) {
setShowResults(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const filteredResults = SEARCHABLE_PAGES.filter(page =>
page.label.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleResultClick = (path: string) => {
navigate(path);
setSearchQuery('');
setShowResults(false);
};
return (
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 sm:px-6 sticky top-0 z-10 shadow-sm font-vazir">
<div className="flex-1 flex items-center gap-3 max-w-xl">
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 sm:px-6 sticky top-0 z-40 shadow-sm font-vazir">
<div className="flex-1 flex items-center gap-3 max-w-xl" ref={searchContainerRef}>
<button
onClick={toggleMenu}
className="lg:hidden p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
@ -20,9 +64,37 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
</div>
<input
type="text"
placeholder="جستجو در پنل..."
className="w-full bg-gray-50 border border-gray-200 text-gray-900 text-sm rounded-xl focus:ring-purple-500 focus:border-purple-500 block pr-10 p-2.5 outline-none font-bold"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
setShowResults(true);
}}
onFocus={() => setShowResults(true)}
placeholder="جستجو در پنل ادمین (مثال: محصولات، سفارشات...)"
className="w-full bg-gray-50 border border-gray-200 text-gray-900 text-sm rounded-xl focus:ring-purple-500 focus:border-purple-500 block pr-10 p-2.5 outline-none font-bold transition-all"
/>
{/* Live Search Results Dropdown */}
{showResults && searchQuery.trim() !== '' && (
<div className="absolute top-full right-0 left-0 mt-2 bg-white border border-gray-100 rounded-xl shadow-2xl z-50 overflow-hidden max-h-60 overflow-y-auto">
{filteredResults.length > 0 ? (
<div className="py-1">
{filteredResults.map((result, idx) => (
<button
key={idx}
onClick={() => handleResultClick(result.path)}
className="w-full text-right px-4 py-3 text-sm font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-600 transition-colors flex items-center justify-between"
>
<span>{result.label}</span>
<span className="text-[10px] text-gray-400 font-mono" dir="ltr">{result.path}</span>
</button>
))}
</div>
) : (
<div className="p-4 text-center text-sm text-gray-400 font-bold">نتیجه‌ای یافت نشد.</div>
)}
</div>
)}
</div>
</div>

View File

@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { DollarSign, ShoppingCart, Users, Activity } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
import api from '../services/api';
@ -43,10 +44,10 @@ export default function Dashboard() {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28'];
const stats = [
{ title: 'درآمد کل', value: `${data.revenue.toLocaleString()} تومان`, icon: DollarSign, color: 'text-green-600', bg: 'bg-green-100' },
{ title: 'سفارشات جدید', value: data.newOrders.toString(), icon: ShoppingCart, color: 'text-blue-600', bg: 'bg-blue-100' },
{ title: 'کاربران فعال', value: data.users.toString(), icon: Users, color: 'text-purple-600', bg: 'bg-purple-100' },
{ title: 'بازدید امروز', value: data.todayVisits.toString(), icon: Activity, color: 'text-orange-600', bg: 'bg-orange-100' },
{ title: 'درآمد کل', value: `${data.revenue.toLocaleString()} تومان`, icon: DollarSign, color: 'text-green-600', bg: 'bg-green-100', link: '/reports' },
{ title: 'سفارشات جدید', value: data.newOrders.toString(), icon: ShoppingCart, color: 'text-blue-600', bg: 'bg-blue-100', link: '/orders' },
{ title: 'کاربران فعال', value: data.users.toString(), icon: Users, color: 'text-purple-600', bg: 'bg-purple-100', link: '/users' },
{ title: 'بازدید امروز', value: data.todayVisits.toString(), icon: Activity, color: 'text-orange-600', bg: 'bg-orange-100', link: '/reports' },
];
return (
@ -60,7 +61,7 @@ export default function Dashboard() {
{stats.map((stat, i) => {
const Icon = stat.icon;
return (
<div key={i} className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-center gap-4">
<Link key={i} to={stat.link} className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-center gap-4 hover:shadow-md hover:border-purple-200 transition-all cursor-pointer">
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center ${stat.bg}`}>
<Icon className={`w-6 h-6 ${stat.color}`} />
</div>
@ -68,7 +69,7 @@ export default function Dashboard() {
<p className="text-sm font-bold text-gray-500">{stat.title}</p>
<p className="text-xl font-black text-gray-900 mt-1">{stat.value}</p>
</div>
</div>
</Link>
);
})}
</div>

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Search, Filter, Box, Plus, Edit2, Trash2, Image as ImageIcon, X } from 'lucide-react';
import { Search, Filter, Box, Plus, Edit2, Trash2, Image as ImageIcon, X, HelpCircle } from 'lucide-react';
import { toast } from 'react-hot-toast';
import api, { BASE_DOMAIN } from '../services/api';
import Spinner from '../components/ui/Spinner';
@ -11,11 +11,43 @@ export default function Products() {
const [categories, setCategories] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
// URL and String Helper Functions
const getProductImageUrl = (url: string) => {
if (!url) return '';
if (url.startsWith('http')) {
const path = url.replace(/^https?:\/\/[^\/]+/, '');
return `${BASE_DOMAIN}${path}`;
}
return url.startsWith('/') ? `${BASE_DOMAIN}${url}` : `${BASE_DOMAIN}/${url}`;
};
const getStoreProductUrl = (slug: string) => {
const isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
if (isDev) {
return `http://localhost:4000/shop/${slug}`;
}
// Production fallback based on actual URL
const storeOrigin = window.location.origin.replace('admin', 'www');
return `${storeOrigin}/shop/${slug}`;
};
const slugify = (text: string) => {
return text
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9\-]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
};
// Filters
const [search, setSearch] = useState('');
const [categoryFilter, setCategoryFilter] = useState('');
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [imageErrors, setImageErrors] = useState<Record<string, boolean>>({});
const [mediaImageError, setMediaImageError] = useState(false);
const limit = 10;
// Modal State
@ -26,7 +58,8 @@ export default function Products() {
const [formData, setFormData] = useState({
artNo: '',
name: '',
nameFa: '',
nameEn: '',
scientificTagline: '',
description: '',
shortDescription: '',
@ -42,7 +75,8 @@ export default function Products() {
metaDescription: '',
keywords: '',
canonicalUrl: '',
slug: ''
slug: '',
symptoms: [] as string[]
});
const fetchData = async () => {
@ -82,11 +116,13 @@ export default function Products() {
}, [search, categoryFilter, page]);
const openModal = (product: any = null) => {
setMediaImageError(false);
if (product) {
setEditingProduct(product);
setFormData({
artNo: product.artNo,
name: product.name,
nameFa: product.nameFa || '',
nameEn: product.nameEn || '',
scientificTagline: product.scientificTagline || '',
description: product.description || '',
shortDescription: product.shortDescription || '',
@ -102,14 +138,16 @@ export default function Products() {
metaDescription: product.metaDescription || '',
keywords: product.keywords || '',
canonicalUrl: product.canonicalUrl || '',
slug: product.slug || ''
slug: product.slug || '',
symptoms: product.symptoms ? product.symptoms.map((s: any) => s.symptom) : []
});
} else {
setEditingProduct(null);
setFormData({
artNo: '', name: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '',
artNo: '', nameFa: '', nameEn: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '',
priceValue: 0, priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه',
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: ''
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '',
symptoms: []
});
}
setActiveTab('general');
@ -119,10 +157,19 @@ export default function Products() {
const handleSave = async (e: React.FormEvent) => {
e.preventDefault();
try {
const derivedSlug = formData.slug.trim() || slugify(formData.nameEn);
const finalPayload = {
...formData,
slug: derivedSlug,
metaTitle: formData.metaTitle.trim() || `${formData.nameFa || 'نام محصول'} | خرید و قیمت مکمل کانینا`,
metaDescription: formData.metaDescription.trim() || formData.shortDescription || 'خرید آنلاین مکمل اصل کانینا آلمان با بالاترین کیفیت بالینی...',
canonicalUrl: formData.canonicalUrl.trim() || `/shop/${derivedSlug}`
};
if (editingProduct) {
await api.put(`/admin/products/${editingProduct.id}`, formData);
await api.put(`/admin/products/${editingProduct.id}`, finalPayload);
} else {
await api.post('/admin/products', formData);
await api.post('/admin/products', finalPayload);
}
setIsModalOpen(false);
fetchData();
@ -212,17 +259,27 @@ export default function Products() {
) : (
products.map((product) => (
<tr key={product.id} className="hover:bg-gray-50/50 transition-colors">
<td className="py-3 px-6">
{product.imageUrl ? (
<img src={product.imageUrl.startsWith('http') ? product.imageUrl : `${BASE_DOMAIN}${product.imageUrl}`} alt={product.name} className="w-12 h-12 rounded-xl object-cover border border-gray-200" />
) : (
<div className="w-12 h-12 rounded-xl bg-gray-100 flex items-center justify-center text-gray-400">
<ImageIcon className="w-5 h-5" />
</div>
)}
</td>
<td className="py-3 px-6">
{product.imageUrl && !imageErrors[product.id] ? (
<img
src={getProductImageUrl(product.imageUrl)}
alt=""
onError={() => setImageErrors(prev => ({ ...prev, [product.id]: true }))}
className="w-12 h-12 rounded-xl object-cover border border-gray-200"
/>
) : (
<div className="w-12 h-12 rounded-xl bg-gray-100 flex items-center justify-center text-gray-400">
<ImageIcon className="w-5 h-5" />
</div>
)}
</td>
<td className="py-4 px-6 text-gray-500 font-mono text-sm">{product.artNo}</td>
<td className="py-4 px-6 font-bold text-gray-900">{product.name}</td>
<td className="py-4 px-6">
<div className="flex flex-col gap-0.5">
<span className="font-bold text-gray-900" dir="rtl">{product.nameFa || product.name || '-'}</span>
{product.nameEn && <span className="text-xs text-gray-400 font-mono" dir="ltr">{product.nameEn}</span>}
</div>
</td>
<td className="py-4 px-6 text-gray-600 text-sm">{product.category?.name || 'بدون دسته'}</td>
<td className="py-4 px-6">
<div className="flex flex-col">
@ -256,14 +313,26 @@ export default function Products() {
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm">
<div className="bg-white rounded-2xl w-full max-w-4xl max-h-[90vh] flex flex-col shadow-2xl animate-in zoom-in duration-200">
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<h3 className="text-xl font-bold text-gray-900">
{editingProduct ? 'ویرایش پیشرفته محصول' : 'افزودن محصول جدید'}
</h3>
<button onClick={() => setIsModalOpen(false)} className="text-gray-400 hover:text-red-500 transition-colors">
<X className="w-6 h-6" />
</button>
</div>
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<div className="flex items-center gap-3">
<h3 className="text-xl font-bold text-gray-900">
{editingProduct ? 'ویرایش پیشرفته محصول' : 'افزودن محصول جدید'}
</h3>
{editingProduct && formData.slug && (
<a
href={getStoreProductUrl(formData.slug)}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-purple-50 border border-purple-100 text-purple-600 hover:bg-purple-100 transition-all cursor-pointer"
>
مشاهده آنلاین در سایت 🔗
</a>
)}
</div>
<button onClick={() => setIsModalOpen(false)} className="text-gray-400 hover:text-red-500 transition-colors">
<X className="w-6 h-6" />
</button>
</div>
<div className="flex border-b border-gray-100 px-6 pt-2 gap-4">
{[
@ -283,33 +352,85 @@ export default function Products() {
))}
</div>
<div className="p-6 overflow-y-auto flex-1 bg-gray-50/30">
<div className="p-6 overflow-y-auto h-[550px] min-h-[550px] max-h-[550px] bg-gray-50/30">
<form id="productForm" onSubmit={handleSave} className="space-y-6">
{/* General Tab */}
<div className={activeTab === 'general' ? 'block space-y-4' : 'hidden'}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">کد محصول (Art No) *</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>کد محصول (Art No) *</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
کد کاتالوگ انحصاری کارخانه Canina آلمان (مانند 123490).
</div>
</div>
</label>
<input required type="text" value={formData.artNo} onChange={(e) => setFormData({...formData, artNo: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono" dir="ltr" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">نام محصول *</label>
<input required type="text" value={formData.name} onChange={(e) => setFormData({...formData, name: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>نام فارسی محصول *</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
عنوان فارسی محصول که در صفحات و کاتالوگ فروشگاه نمایش داده می‌شود.
</div>
</div>
</label>
<input required type="text" value={formData.nameFa} onChange={(e) => setFormData({...formData, nameFa: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="rtl" placeholder="مثال: ژل دندان میکروسیلور" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>نام انگلیسی / آلمانی (nameEn) *</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
نام برند و وزن کالا به انگلیسی جهت استفاده در آدرس، فاکتورها و سئو.
</div>
</div>
</label>
<input required type="text" value={formData.nameEn} onChange={(e) => setFormData({...formData, nameEn: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono" dir="ltr" placeholder="e.g. Canina Mikrosilber Zahngel - 50ml" />
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700">شعار علمی (Tagline)</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>شعار علمی (Tagline)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
شعار کلیدی یا علمی کالا که در بالای صفحه محصول نمایش داده می‌شود.
</div>
</div>
</label>
<input type="text" value={formData.scientificTagline} onChange={(e) => setFormData({...formData, scientificTagline: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">دسته‌بندی *</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>دسته‌بندی *</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
دسته‌بندی اصلی محصول جهت تقسیم‌بندی در منوها و سیستم ناوبری سایت.
</div>
</div>
</label>
<select required value={formData.categoryId} onChange={(e) => setFormData({...formData, categoryId: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none bg-white">
<option value="">انتخاب کنید</option>
{categories.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">مناسب برای</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>مناسب برای</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
تعیین این که کالا برای سگ، گربه یا هر دو گونه مناسب است.
</div>
</div>
</label>
<select value={formData.suitableFor} onChange={(e) => setFormData({...formData, suitableFor: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none bg-white">
<option value="سگ">سگ</option>
<option value="گربه">گربه</option>
@ -317,19 +438,91 @@ export default function Products() {
</select>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700">توضیح کوتاه</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>توضیح کوتاه</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
خلاصه ویژگی‌های کلیدی کالا که در کارت‌های فروشگاه و زیر عنوان محصول نمایش داده می‌شود.
</div>
</div>
</label>
<textarea rows={2} value={formData.shortDescription} onChange={(e) => setFormData({...formData, shortDescription: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700">توضیحات کامل</label>
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>توضیحات کامل</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
توضیحات کامل علمی، نحوه عملکرد مکمل و جزئیات ترکیبات محصول.
</div>
</div>
</label>
<textarea rows={5} value={formData.description} onChange={(e) => setFormData({...formData, description: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700">منطق دوز مصرفی (Dosage Logic)</label>
<textarea rows={2} value={formData.dosageLogic} onChange={(e) => setFormData({...formData, dosageLogic: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
</div>
</div>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>منطق دوز مصرفی (Dosage Logic)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
فرمول نحوه مصرف روزانه کالا بر اساس وزن پت (مثال: ۲ میلی‌لیتر به ازای هر ۵ کیلوگرم وزن).
</div>
</div>
</label>
<textarea rows={2} value={formData.dosageLogic} onChange={(e) => setFormData({...formData, dosageLogic: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>علائم درمانی مرتبط (Symptom Tags)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
علائم متصل به این دارو جهت فیلترینگ و سرچ پویای کاربران (مثال: اسهال، خارش، ریزش مو).
</div>
</div>
</label>
<div className="flex flex-wrap gap-2 p-3 bg-white border border-gray-200 rounded-xl min-h-[50px] items-center focus-within:border-purple-500 focus-within:ring-2 focus-within:ring-purple-200 transition-all">
{formData.symptoms && formData.symptoms.map((symptom, idx) => (
<span key={idx} className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-purple-50 border border-purple-200 text-purple-700">
{symptom}
<button
type="button"
onClick={() => {
const newSymptoms = formData.symptoms.filter((_, i) => i !== idx);
setFormData({ ...formData, symptoms: newSymptoms });
}}
className="text-purple-400 hover:text-purple-600 focus:outline-none transition-colors"
>
<X className="w-3.5 h-3.5" />
</button>
</span>
))}
<input
type="text"
placeholder="تایپ و اینتر کنید..."
className="flex-1 bg-transparent border-none outline-none text-sm min-w-[150px] text-right font-vazir"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
const input = e.currentTarget;
const value = input.value.trim();
if (value && !formData.symptoms.includes(value)) {
setFormData({
...formData,
symptoms: [...(formData.symptoms || []), value]
});
input.value = '';
}
}
}}
/>
</div>
<p className="text-[11px] text-gray-400 font-medium">پس از نوشتن هر علامت درمانی (مثلاً «درد مفاصل»)، کلید Enter را فشار دهید.</p>
</div>
</div>
</div>
{/* Pricing Tab */}
<div className={activeTab === 'pricing' ? 'block space-y-4' : 'hidden'}>
@ -354,38 +547,158 @@ export default function Products() {
</div>
{/* SEO Tab */}
<div className={activeTab === 'seo' ? 'block space-y-4' : 'hidden'}>
<div className="grid grid-cols-1 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">نامک (Slug) محصول</label>
<input type="text" value={formData.slug} onChange={(e) => setFormData({...formData, slug: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" dir="ltr" placeholder="مثال: canhydrox-gag" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">عنوان متا (Meta Title)</label>
<input type="text" value={formData.metaTitle} onChange={(e) => setFormData({...formData, metaTitle: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">توضیحات متا (Meta Description)</label>
<textarea rows={3} value={formData.metaDescription} onChange={(e) => setFormData({...formData, metaDescription: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">کلمات کلیدی (با کاما جدا کنید)</label>
<input type="text" value={formData.keywords} onChange={(e) => setFormData({...formData, keywords: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700">آدرس کانونی (Canonical URL)</label>
<input type="text" value={formData.canonicalUrl} onChange={(e) => setFormData({...formData, canonicalUrl: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-sm" dir="ltr" />
</div>
</div>
<div className={activeTab === 'seo' ? 'block' : 'hidden'}>
{(() => {
const derivedSlug = formData.slug || slugify(formData.nameEn);
const derivedMetaTitle = formData.metaTitle || `${formData.nameFa || 'نام محصول'} | خرید و قیمت مکمل کانینا`;
const derivedMetaDescription = formData.metaDescription || (formData.shortDescription || 'خرید آنلاین مکمل اصل کانینا آلمان با بالاترین کیفیت بالینی و ضمانت اصالت فیزیکی...');
const derivedCanonicalUrl = formData.canonicalUrl || `/shop/${derivedSlug}`;
return (
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
{/* Right Column: SEO Inputs */}
<div className="lg:col-span-7 space-y-4">
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>نامک (Slug) محصول</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
آدرس مستقیم محصول در سایت. در صورت خالی بودن، به طور خودکار از روی نام انگلیسی ساخته می‌شود.
</div>
</div>
</label>
<input
type="text"
value={formData.slug}
onChange={(e) => setFormData({...formData, slug: e.target.value})}
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
dir="ltr"
placeholder={derivedSlug}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>عنوان متا (Meta Title)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
عنوان اصلی صفحه در گوگل (حداکثر ۶۰ کاراکتر). در صورت خالی بودن، از ساختار پیش‌فرض استفاده می‌شود.
</div>
</div>
</label>
<input
type="text"
value={formData.metaTitle}
onChange={(e) => setFormData({...formData, metaTitle: e.target.value})}
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
placeholder={derivedMetaTitle}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>توضیحات متا (Meta Description)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
خلاصه توضیحات صفحه در نتایج جستجو (حداکثر ۱۵۵ کاراکتر). در صورت خالی بودن، از توضیحات کوتاه محصول استفاده می‌شود.
</div>
</div>
</label>
<textarea
rows={3}
value={formData.metaDescription}
onChange={(e) => setFormData({...formData, metaDescription: e.target.value})}
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
placeholder={derivedMetaDescription}
></textarea>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>کلمات کلیدی سئو (Keywords)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
کلمات کلیدی مرتبط با محصول که با کاما (,) از هم جدا شده‌اند.
</div>
</div>
</label>
<input
type="text"
value={formData.keywords}
onChange={(e) => setFormData({...formData, keywords: e.target.value})}
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
placeholder="مثال: مکمل سگ, تقویت مفاصل, کنهیدروکس"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
<span>آدرس کانونی (Canonical URL)</span>
<div className="group relative inline-block">
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-52 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
آدرس اصلی محصول جهت جلوگیری از جریمه محتوای تکراری در گوگل.
</div>
</div>
</label>
<input
type="text"
value={formData.canonicalUrl}
onChange={(e) => setFormData({...formData, canonicalUrl: e.target.value})}
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-sm"
dir="ltr"
placeholder={derivedCanonicalUrl}
/>
</div>
</div>
{/* Left Column: Sticky Google SERP Preview */}
<div className="lg:col-span-5 lg:sticky lg:top-4 bg-gray-100 border border-gray-200 rounded-2xl p-5 space-y-3">
<span className="text-[10px] font-black text-gray-400 uppercase tracking-widest block">پیش‌نمایش نتایج در گوگل (Google SERP Preview)</span>
<div className="bg-white rounded-xl p-4 border border-gray-100 shadow-sm text-right font-sans" dir="rtl">
{/* Domain / Breadcrumb */}
<div className="flex items-center gap-1 text-xs text-[#202124] mb-1">
<span className="font-medium text-gray-800">caninoiran.ir</span>
<span className="text-gray-400">›</span>
<span className="text-gray-500">shop</span>
<span className="text-gray-400">›</span>
<span className="text-gray-500 max-w-[150px] truncate">{derivedSlug}</span>
</div>
{/* Title */}
<h4 className="text-[19px] text-[#1a0dab] hover:underline cursor-pointer leading-tight font-medium font-sans mb-1 select-none">
{derivedMetaTitle}
</h4>
{/* Description */}
<p className="text-sm text-[#4d5156] leading-relaxed font-sans select-none break-words">
{derivedMetaDescription}
</p>
</div>
<p className="text-[10px] text-gray-400 font-medium">تغییرات فیلدهای سمت راست، بلافاصله در پیش‌نمایش بالا منعکس می‌شود.</p>
</div>
</div>
);
})()}
</div>
{/* Media Tab */}
<div className={activeTab === 'media' ? 'block space-y-4' : 'hidden'}>
<div className="flex flex-col items-center justify-center p-8 border-2 border-dashed border-gray-200 rounded-2xl bg-white">
{formData.imageUrl ? (
{formData.imageUrl && !mediaImageError ? (
<div className="w-full flex flex-col items-center">
<div className="relative group">
<img src={formData.imageUrl.startsWith('http') ? formData.imageUrl : `${BASE_DOMAIN}${formData.imageUrl}`} alt="product" className="max-w-xs h-48 object-cover rounded-xl shadow-md" />
<img
src={getProductImageUrl(formData.imageUrl)}
alt=""
onError={() => setMediaImageError(true)}
className="max-w-xs h-48 object-contain rounded-xl shadow-md border border-gray-100"
/>
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded-xl flex items-center justify-center gap-3">
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-white text-gray-800 px-4 py-2 rounded-lg font-bold text-sm">تغییر عکس</button>
</div>
@ -405,7 +718,7 @@ export default function Products() {
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center text-gray-400 mb-4">
<ImageIcon className="w-8 h-8" />
</div>
<p className="text-gray-500 font-medium mb-4">هیچ تصویری انتخاب نشده است</p>
<p className="text-gray-500 font-medium mb-4">تصویر محصول یافت نشد یا انتخاب نشده است</p>
<p className="text-xs text-gray-400 mb-4">از گالری رسانه تصویر انتخاب کنید یا با Ctrl+V از کلیپ‌بورد بچسبانید</p>
<button type="button" onClick={() => setIsMediaSelectorOpen(true)} className="bg-purple-100 text-purple-700 hover:bg-purple-200 px-6 py-2 rounded-xl font-bold transition-colors">
انتخاب از گالری رسانه
@ -434,7 +747,10 @@ export default function Products() {
<MediaSelector
isOpen={isMediaSelectorOpen}
onClose={() => setIsMediaSelectorOpen(false)}
onSelect={(url) => setFormData({...formData, imageUrl: url})}
onSelect={(url) => {
setFormData({...formData, imageUrl: url});
setMediaImageError(false);
}}
selectedUrl={formData.imageUrl.startsWith('http') ? formData.imageUrl : formData.imageUrl ? `${BASE_DOMAIN}${formData.imageUrl}` : undefined}
/>
</div>

View File

@ -0,0 +1,159 @@
"use client";
import { useEffect, useState } from "react";
import Hero from "../components/Hero";
import SmartAdvisor from "../components/SmartAdvisor";
import FeaturedProducts from "../components/FeaturedProducts";
import VetGallery from "../components/VetGallery";
import Link from 'next/link';
import { toPersian } from "../lib/utils";
import { useSettingsStore } from "../lib/store/settingsStore";
import api from "../lib/services/api";
export default function HomeClient() {
const getText = useSettingsStore(state => state.getText);
const [data, setData] = useState<any>(null);
useEffect(() => {
api.get('/home')
.then(res => setData(res.data))
.catch(err => console.error('Home data fetch error:', err));
}, []);
const banners = data?.heroBanners || [];
const testimonials = data?.vetTestimonials || [];
const rules = data?.smartAdvisorRules || [];
return (
<>
<Hero banners={banners} />
<SmartAdvisor rules={rules} />
<FeaturedProducts />
<VetGallery testimonials={testimonials} />
{/* Navigation to Sections */}
<section className="py-12 bg-medical-gray-50 border-y border-medical-gray-100 font-sans">
<div className="max-w-7xl mx-auto px-4 grid md:grid-cols-2 gap-8">
{/* Specialty Shop Card */}
<Link
href="/shop"
className="bg-gradient-to-br from-blue-50/60 to-canina-blue/5 p-12 rounded-[2.5rem] border border-blue-100/60 shadow-md hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group block relative overflow-hidden"
>
<div className="absolute -top-12 -left-12 w-32 h-32 bg-canina-blue/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" />
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 group-hover:text-canina-blue transition-colors font-lalezar tracking-wide">
{getText('nav_card_shop_title', "فروشگاه تخصصی")}
</h3>
<p className="text-medical-gray-600 mb-8 leading-relaxed font-shabnam">
{getText('nav_card_shop_desc', "دسترسی به تمامی محصولات بر اساس علائم بالینی و نیازهای پت شما.")}
</p>
<div className="flex items-center gap-2 text-canina-blue font-black tracking-widest uppercase text-xs font-vazir">
{getText('nav_card_shop_cta', "کاتالوگ کامل محصولات")}
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
</div>
</Link>
{/* Scientific Wiki Card */}
<Link
href="/wiki"
className="bg-gradient-to-br from-emerald-50/60 to-green-50/20 p-12 rounded-[2.5rem] border border-emerald-100/60 shadow-md hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group block relative overflow-hidden"
>
<div className="absolute -top-12 -left-12 w-32 h-32 bg-emerald-500/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" />
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 group-hover:text-emerald-700 transition-colors font-lalezar tracking-wide">
{getText('nav_card_wiki_title', "دانشنامه علمی")}
</h3>
<p className="text-medical-gray-600 mb-8 leading-relaxed font-shabnam">
{getText('nav_card_wiki_desc', "آشنایی با ترکیبات نایاب و استانداردهای دارویی محصولات آلمانی کانینا.")}
</p>
<div className="flex items-center gap-2 text-emerald-700 font-black tracking-widest uppercase text-xs font-vazir">
{getText('nav_card_wiki_cta', "مطالعه مقالات تخصصی")}
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
</div>
</Link>
</div>
</section>
{/* About Section Teaser */}
<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="relative">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-canina-blue/5 rounded-full blur-3xl" />
<img
src="/assets/images/regenerated_image_1779109861747.png"
alt="آزمایشگاه تخصصی تولید مکمل‌های دارویی کانینا آلمان"
className="rounded-[3rem] shadow-2xl relative z-10 grayscale-[10%]"
/>
<div className="absolute -bottom-6 -left-6 bg-canina-blue text-white p-8 rounded-[2rem] shadow-xl z-20">
<div className="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>
</div>
<div>
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-4 font-vazir">
{getText('about_label', "میراث ما از آلمان")}
</div>
<h2 className="text-3xl lg:text-5xl font-black text-medical-gray-900 leading-[1.2] mb-12 font-lalezar tracking-wide">
{getText('about_title', "چرا برند آلمانی Canina مرجع دامپزشکان است؟")}
</h2>
<div className="space-y-6">
{[
{ title: getText('about_item1_title', "مواد اولیه نایاب"), desc: getText('about_item1_desc', "استفاده از پودر صدف لب‌سبز اصل نیوزیلند و مواد ارگانیک با گرید دارویی.") },
{ title: getText('about_item2_title', "فاقد مواد نگهدارنده"), desc: getText('about_item2_desc', "تمامی محصولات ۱۰۰٪ طبیعی و فاقد رنگ‌های مصنوعی و طعم‌دهنده‌های شیمیایی هستند.") },
{ title: getText('about_item3_title', "تاییدیه اروپا"), desc: getText('about_item3_desc', "مطابق با سخت‌گیرانه‌ترین استانداردهای ایمنی مواد غذایی و دارویی در اتحادیه اروپا.") }
].map((item, idx) => (
<div key={idx} className="flex gap-4 items-center">
<div className="w-12 h-12 rounded-2xl bg-medical-gray-50 flex items-center justify-center flex-shrink-0 text-canina-blue font-black font-lalezar text-xl shadow-sm">
{toPersian(idx + 1)}
</div>
<div>
<h4 className="text-lg font-bold text-medical-gray-900 mb-1 font-shabnam">{item.title}</h4>
<p className="text-sm text-medical-gray-500 leading-relaxed font-medium font-sans">{item.desc}</p>
{idx === 0 && (
<span className="text-[10px] text-slate-400 mt-1 block">
* تهیه شده با بالاترین استانداردهای دارویی انسان - کاملاً بی‌خطر در صورت لیسیده شدن توسط حیوان.
</span>
)}
{idx === 2 && (
<span className="text-[10px] text-slate-400 mt-1 block">
* تاییدیه رسمی ایمنی مواد غذایی و دارویی (HACCP & IFS) از اتحادیه اروپا.
</span>
)}
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Call to Action */}
<section className="bg-canina-blue py-12 relative overflow-hidden font-sans">
<div className="absolute inset-0 opacity-10">
<div className="grid grid-cols-10 h-full w-full">
{Array.from({length: 100}).map((_, i) => (
<div key={i} className="border border-white/20" />
))}
</div>
</div>
<div className="max-w-4xl mx-auto px-4 text-center relative z-10">
<h2 className="text-3xl lg:text-5xl font-black text-white mb-8 leading-tight font-lalezar tracking-wide">
{getText('cta_title', "می‌خواهید بدانید کدام محصول برای پت شما مناسب‌تر است؟")}
</h2>
<p className="text-white/80 text-lg mb-10 leading-relaxed font-shabnam">
{getText('cta_desc', "تیم متخصص دامپزشکی کانینا ایران آماده پاسخگویی به سوالات شماست.")}
</p>
<div className="flex flex-wrap justify-center gap-4">
<button className="bg-white text-canina-blue px-12 py-5 rounded-full font-black text-xl hover:scale-105 transition-transform shadow-2xl shadow-black/20 font-shabnam">
{getText('cta_btn_primary', "دریافت رژیم مکمل رایگان")}
</button>
<Link
href="/shop"
className="bg-transparent border-2 border-white text-white px-12 py-5 rounded-full font-black text-xl hover:bg-white hover:text-canina-blue transition-all font-shabnam"
>
{getText('cta_btn_secondary', "پروانه دامپزشکی و اصالت کالا")}
</Link>
</div>
</div>
</section>
</>
);
}

View File

@ -2,8 +2,19 @@ import type { Metadata } from "next";
import { ShieldCheck, History, Award, CheckCircle } from "lucide-react";
export const metadata: Metadata = {
title: "درباره ما",
description: "داستان کانینا ایران و میراث ۴۰ ساله شرکت Canina pharma GmbH آلمان در تولید مکمل‌های فوق‌تخصصی دامپزشکی.",
title: "درباره ما - میراث ۴۰ ساله کانیتا آلمان در ایران",
description: "داستان کانینا ایران و میراث ۴۰ ساله شرکت Canina pharma GmbH آلمان در تولید مکمل‌های فوق‌تخصصی دامپزشکی با گرید دارویی اختصاصی.",
alternates: {
canonical: "/about",
},
openGraph: {
title: "درباره ما - کانینا ایران",
description: "داستان کانینا ایران و میراث ۴۰ ساله شرکت Canina pharma GmbH آلمان در تولید مکمل‌های فوق‌تخصصی دامپزشکی.",
url: "https://canina-iran.com/about",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
export default function AboutPage() {

View File

@ -26,14 +26,30 @@ async function getBlog(slug: string) {
}
}
export const revalidate = 60; // ensure SSR dynamic revalidation works correctly
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params;
const blog = await getBlog(slug);
if (!blog) return { title: 'مقاله یافت نشد' };
if (!blog) return { title: 'مقاله یافت نشد | کانینا ایران' };
const title = `${blog.title} | مجله سلامت کانینا`;
const description = blog.excerpt.substring(0, 150).trim();
return {
title: blog.title,
description: blog.excerpt,
title,
description,
alternates: {
canonical: `/blog/${slug}`,
},
openGraph: {
title,
description,
images: [blog.image],
url: `/blog/${slug}`,
siteName: "کانینا ایران",
type: "article",
}
};
}

View File

@ -1,9 +1,20 @@
import BlogPage from "../../components/BlogPage";
import BlogPage from "../../components/BlogPage";
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'مجله سلامت حیوانات خانگی',
description: 'آخرین مقالات، آموزش‌ها و اخبار علمی در زمینه نگهداری، تغذیه و سلامت سگ و گربه.',
title: "مجله سلامت حیوانات خانگی و مقالات علمی - کانینا ایران",
description: "آخرین مقالات، آموزش‌های دامپزشکی و اخبار علمی روز دنیا در زمینه نگهداری، تغذیه تخصصی و سلامت سگ و گربه.",
alternates: {
canonical: "/blog",
},
openGraph: {
title: "مجله سلامت حیوانات خانگی - کانینا ایران",
description: "آخرین مقالات، آموزش‌ها و اخبار علمی در زمینه نگهداری، تغذیه و سلامت سگ و گربه.",
url: "https://canina-iran.com/blog",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
async function getBlogs() {

View File

@ -2,8 +2,19 @@ import type { Metadata } from "next";
import { Mail, Phone, MapPin, Clock, Send } from "lucide-react";
export const metadata: Metadata = {
title: "تماس با ما",
description: "راه‌های ارتباطی با نمایندگی رسمی مکمل‌های دامپزشکی کانینا در ایران.",
title: "تماس با ما - نمایندگی رسمی مکمل‌های کانینا آلمان در ایران",
description: "راه‌های ارتباطی، تلفن تماس ویژه، آدرس دفتر مرکزی و فرم تماس مستقیم با نمایندگی انحصاری کانیتا آلمان در ایران.",
alternates: {
canonical: "/contact",
},
openGraph: {
title: "تماس با ما - کانینا ایران",
description: "راه‌های ارتباطی با نمایندگی رسمی مکمل‌های دامپزشکی کانینا در ایران.",
url: "https://canina-iran.com/contact",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
export default function ContactPage() {
@ -47,7 +58,7 @@ export default function ContactPage() {
</div>
<div>
<h4 className="text-xs font-black text-medical-gray-400 uppercase tracking-wider mb-1">پست الکترونیک</h4>
<p className="text-sm font-black text-medical-gray-900">info@canino-iran.com</p>
<p className="text-sm font-black text-medical-gray-900">info@canina-iran.com</p>
<p className="text-xs text-medical-gray-500 font-bold">پاسخگویی در کمتر از ۲۴ ساعت کاری</p>
</div>
</div>

View File

@ -1,27 +1,30 @@
import type { Metadata } from 'next';
import type { Metadata } from 'next';
import './globals.css';
import ClientLayout from './ClientLayout';
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://canino-iran.com'),
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://canina-iran.com'),
title: {
template: '%s | کانینا ایران',
default: 'کانینا ایران - مکمل‌های درمانی حیوانات خانگی',
default: 'کانینا ایران - مکمل‌های درمانی حیوانات خانگی با گرید دارویی اختصاصی',
},
description: 'تدارک هوشمندانه سلامت برای همراهان وفادار شما. کانینا ایران واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان.',
keywords: ['مکمل حیوانات خانگی', 'مکمل سگ', 'مکمل گربه', 'کانینا', 'داروی حیوانات', 'Canina Iran'],
authors: [{ name: 'Canina Iran' }],
creator: 'Canina Iran',
publisher: 'Canina Iran',
alternates: {
canonical: './',
},
formatDetection: {
email: false,
address: false,
telephone: false,
},
openGraph: {
title: 'کانینا ایران - مکمل‌های درمانی حیوانات خانگی',
title: 'کانینا ایران - مکمل‌های درمانی حیوانات خانگی با گرید دارویی اختصاصی',
description: 'واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان',
url: 'https://canino-iran.com',
url: 'https://canina-iran.com',
siteName: 'کانینا ایران',
locale: 'fa_IR',
type: 'website',
@ -53,13 +56,13 @@ export default function RootLayout({
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'کانینا ایران',
url: 'https://canino-iran.com',
url: 'https://canina-iran.com',
logo: 'https://canina.de/media/fb/d5/47/1683116813/Logo.png',
contactPoint: {
'@type': 'ContactPoint',
telephone: '+98-21-88884444',
contactType: 'customer service',
email: 'info@canino-iran.com',
email: 'info@canina-iran.com',
areaServed: 'IR',
availableLanguage: 'Persian'
}

View File

@ -1,149 +1,30 @@
"use client";
import type { Metadata } from "next";
import HomeClient from "./HomeClient";
import { useEffect, useState } from "react";
import Hero from "../components/Hero";
import SmartAdvisor from "../components/SmartAdvisor";
import FeaturedProducts from "../components/FeaturedProducts";
import VetGallery from "../components/VetGallery";
import Link from 'next/link';
import { toPersian } from "../lib/utils";
import { useSettingsStore } from "../lib/store/settingsStore";
import api from "../lib/services/api";
export const metadata: Metadata = {
title: "کانینا ایران - مکمل‌های درمانی حیوانات خانگی با گرید دارویی اختصاصی",
description: "تدارک هوشمندانه سلامت برای همراهان وفادار شما. کانینا ایران واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان با پروانه رسمی دامپزشکی.",
alternates: {
canonical: "/",
},
openGraph: {
title: "کانینا ایران - مکمل‌های درمانی حیوانات خانگی با گرید دارویی اختصاصی",
description: "واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان",
url: "https://canina-iran.com",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
images: [
{
url: "/assets/images/regenerated_image_1779109861747.png",
width: 1200,
height: 630,
alt: "کانینا ایران - مکمل‌های درمانی حیوانات خانگی",
}
]
}
};
export default function Home() {
const getText = useSettingsStore(state => state.getText);
const [data, setData] = useState<any>(null);
useEffect(() => {
api.get('/home')
.then(res => setData(res.data))
.catch(err => console.error('Home data fetch error:', err));
}, []);
const banners = data?.heroBanners || [];
const testimonials = data?.vetTestimonials || [];
const rules = data?.smartAdvisorRules || [];
return (
<>
<Hero banners={banners} />
<SmartAdvisor rules={rules} />
<FeaturedProducts />
<VetGallery testimonials={testimonials} />
{/* Navigation to Sections */}
<section className="py-12 bg-medical-gray-50 border-y border-medical-gray-100 font-sans">
<div className="max-w-7xl mx-auto px-4 grid md:grid-cols-2 gap-8">
{/* Specialty Shop Card */}
<Link
href="/shop"
className="bg-gradient-to-br from-blue-50/60 to-canina-blue/5 p-12 rounded-[2.5rem] border border-blue-100/60 shadow-md hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group block relative overflow-hidden"
>
<div className="absolute -top-12 -left-12 w-32 h-32 bg-canina-blue/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" />
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 group-hover:text-canina-blue transition-colors font-lalezar tracking-wide">
{getText('nav_card_shop_title', "فروشگاه تخصصی")}
</h3>
<p className="text-medical-gray-600 mb-8 leading-relaxed font-shabnam">
{getText('nav_card_shop_desc', "دسترسی به تمامی محصولات بر اساس علائم بالینی و نیازهای پت شما.")}
</p>
<div className="flex items-center gap-2 text-canina-blue font-black tracking-widest uppercase text-xs font-vazir">
{getText('nav_card_shop_cta', "کاتالوگ کامل محصولات")}
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
</div>
</Link>
{/* Scientific Wiki Card */}
<Link
href="/wiki"
className="bg-gradient-to-br from-emerald-50/60 to-green-50/20 p-12 rounded-[2.5rem] border border-emerald-100/60 shadow-md hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group block relative overflow-hidden"
>
<div className="absolute -top-12 -left-12 w-32 h-32 bg-emerald-500/5 rounded-full blur-2xl group-hover:scale-150 transition-transform duration-700" />
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 group-hover:text-emerald-700 transition-colors font-lalezar tracking-wide">
{getText('nav_card_wiki_title', "دانشنامه علمی")}
</h3>
<p className="text-medical-gray-600 mb-8 leading-relaxed font-shabnam">
{getText('nav_card_wiki_desc', "آشنایی با ترکیبات نایاب و استانداردهای دارویی محصولات آلمانی کانینا.")}
</p>
<div className="flex items-center gap-2 text-emerald-700 font-black tracking-widest uppercase text-xs font-vazir">
{getText('nav_card_wiki_cta', "مطالعه مقالات تخصصی")}
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
</div>
</Link>
</div>
</section>
{/* About Section Teaser */}
<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="relative">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-canina-blue/5 rounded-full blur-3xl" />
<img
src="/assets/images/regenerated_image_1779109861747.png"
alt="Laboratory"
className="rounded-[3rem] shadow-2xl relative z-10 grayscale-[10%]"
/>
<div className="absolute -bottom-6 -left-6 bg-canina-blue text-white p-8 rounded-[2rem] shadow-xl z-20">
<div className="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>
</div>
<div>
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-4 font-vazir">
{getText('about_label', "میراث ما از آلمان")}
</div>
<h2 className="text-3xl lg:text-5xl font-black text-medical-gray-900 leading-[1.2] mb-12 font-lalezar tracking-wide">
{getText('about_title', "چرا برند آلمانی Canina مرجع دامپزشکان است؟")}
</h2>
<div className="space-y-6">
{[
{ title: getText('about_item1_title', "مواد اولیه نایاب"), desc: getText('about_item1_desc', "استفاده از پودر صدف لب‌سبز اصل نیوزیلند و مواد ارگانیک با گرید دارویی.") },
{ title: getText('about_item2_title', "فاقد مواد نگهدارنده"), desc: getText('about_item2_desc', "تمامی محصولات ۱۰۰٪ طبیعی و فاقد رنگ‌های مصنوعی و طعم‌دهنده‌های شیمیایی هستند.") },
{ title: getText('about_item3_title', "تاییدیه اروپا"), desc: getText('about_item3_desc', "مطابق با سخت‌گیرانه‌ترین استانداردهای ایمنی مواد غذایی و دارویی در اتحادیه اروپا.") }
].map((item, idx) => (
<div key={idx} className="flex gap-4 items-center">
<div className="w-12 h-12 rounded-2xl bg-medical-gray-50 flex items-center justify-center flex-shrink-0 text-canina-blue font-black font-lalezar text-xl shadow-sm">
{toPersian(idx + 1)}
</div>
<div>
<h4 className="text-lg font-bold text-medical-gray-900 mb-1 font-shabnam">{item.title}</h4>
<p className="text-sm text-medical-gray-500 leading-relaxed font-medium font-sans">{item.desc}</p>
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Call to Action */}
<section className="bg-canina-blue py-12 relative overflow-hidden font-sans">
<div className="absolute inset-0 opacity-10">
<div className="grid grid-cols-10 h-full w-full">
{Array.from({length: 100}).map((_, i) => (
<div key={i} className="border border-white/20" />
))}
</div>
</div>
<div className="max-w-4xl mx-auto px-4 text-center relative z-10">
<h2 className="text-3xl lg:text-5xl font-black text-white mb-8 leading-tight font-lalezar tracking-wide">
{getText('cta_title', "می‌خواهید بدانید کدام محصول برای پت شما مناسب‌تر است؟")}
</h2>
<p className="text-white/80 text-lg mb-10 leading-relaxed font-shabnam">
{getText('cta_desc', "تیم متخصص دامپزشکی کانینا ایران آماده پاسخگویی به سوالات شماست.")}
</p>
<div className="flex flex-wrap justify-center gap-4">
<button className="bg-white text-canina-blue px-12 py-5 rounded-full font-black text-xl hover:scale-105 transition-transform shadow-2xl shadow-black/20 font-shabnam">
{getText('cta_btn_primary', "دریافت رژیم مکمل رایگان")}
</button>
<Link
href="/shop"
className="bg-transparent border-2 border-white text-white px-12 py-5 rounded-full font-black text-xl hover:bg-white hover:text-canina-blue transition-all font-shabnam"
>
{getText('cta_btn_secondary', "پروانه دامپزشکی و اصالت کالا")}
</Link>
</div>
</div>
</section>
</>
);
return <HomeClient />;
}

View File

@ -2,8 +2,19 @@ import type { Metadata } from "next";
import { Shield } from "lucide-react";
export const metadata: Metadata = {
title: "حریم خصوصی",
description: "سیاست‌های حفاظت از اطلاعات شخصی کاربران در وب‌سایت کانینا ایران.",
title: "حریم خصوصی کاربران - کانینا ایران",
description: "سیاست‌های حفاظت از اطلاعات شخصی کاربران، امنیت داده‌های پزشکی و پرونده‌های درمانی پت در وب‌سایت کانینا ایران.",
alternates: {
canonical: "/privacy",
},
openGraph: {
title: "حریم خصوصی کاربران - کانینا ایران",
description: "سیاست‌های حفاظت از اطلاعات شخصی کاربران در وب‌سایت کانینا ایران.",
url: "https://canina-iran.com/privacy",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
export default function PrivacyPage() {

View File

@ -18,8 +18,10 @@ export async function generateMetadata(
// Get raw data for custom fields if we added them (like metaTitle)
// Since we don't have local access to raw DB object without modifying productService,
// we can just use the mapped product fields for now.
const title = (product as any).metaTitle || product.name;
const description = (product as any).metaDescription || product.description;
const nameFa = product.nameFa || product.name;
const nameEn = product.nameEn || "";
const title = nameFa.includes("Canina") ? nameFa : `${nameFa} (${nameEn}) | برند کانینا آلمان`;
const description = (product.shortDescription || product.description || '').substring(0, 150).trim();
const keywords = (product as any).keywords ? (product as any).keywords.split(',') : product.main_ingredients;
return {
@ -30,9 +32,12 @@ export async function generateMetadata(
title,
description,
images: [product.image],
url: `/shop/${resolvedParams.slug}`,
siteName: "کانینا ایران",
type: "music.song", // product type equivalent or website
},
alternates: {
canonical: (product as any).canonicalUrl || `/shop/${product.id}`,
canonical: `/shop/${resolvedParams.slug}`,
}
};
}
@ -48,9 +53,9 @@ export default async function ShopProductPage({ params }: { params: Promise<{ sl
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
name: product.nameFa || product.name,
image: product.image,
description: (product as any).metaDescription || product.description,
description: product.shortDescription || product.description,
sku: product.artNo,
brand: {
'@type': 'Brand',
@ -58,7 +63,7 @@ export default async function ShopProductPage({ params }: { params: Promise<{ sl
},
offers: {
'@type': 'Offer',
url: `https://canino-iran.com/shop/${product.id}`,
url: `https://canina-iran.com/shop/${resolvedParams.slug}`,
priceCurrency: 'IRR',
price: product.priceValue * 10, // Assuming priceValue is in Toman, converting to Rial
availability: 'https://schema.org/InStock',

View File

@ -1,9 +1,39 @@
import type { Metadata } from "next";
import ArchivePage from "../../components/ArchivePage";
export const metadata: Metadata = {
title: "فروشگاه تخصصی مکمل‌های دارویی - کانینا ایران",
description: "خرید آنلاین انواع مکمل‌های درمانی، مفاصل و استخوان، گوارش و ایمنی، ویتامین‌ها و مراقبت‌های ویژه سگ و گربه با تضمین اصالت و گواهی سلامت دامپزشکی.",
alternates: {
canonical: "/shop",
},
openGraph: {
title: "فروشگاه تخصصی مکمل‌های دارویی - کانینا ایران",
description: "خرید آنلاین انواع مکمل‌های درمانی، مفاصل و استخوان، گوارش و ایمنی، ویتامین‌ها و مراقبت‌های ویژه سگ و گربه.",
url: "https://canina-iran.com/shop",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
export default async function Shop({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) {
const resolvedParams = await searchParams;
const category = typeof resolvedParams.category === 'string' ? resolvedParams.category : "all";
const search = typeof resolvedParams.search === 'string' ? resolvedParams.search : "";
const petType = typeof resolvedParams.petType === 'string' ? resolvedParams.petType : "all";
return <ArchivePage initialCategory={category} initialSearch={search} />;
// Support both ?symptoms= (multi, from sidebar URL state) and ?symptom= (single, from megamenu/PDP links)
const symptomsParam = typeof resolvedParams.symptoms === 'string' ? resolvedParams.symptoms : "";
const symptomSingle = typeof resolvedParams.symptom === 'string' ? resolvedParams.symptom : "";
const symptoms = symptomsParam || (symptomSingle ? symptomSingle : "");
return (
<ArchivePage
initialCategory={category}
initialSearch={search}
initialPetType={petType}
initialSymptoms={symptoms}
/>
);
}

View File

@ -1,9 +1,20 @@
import IngredientWiki from "../../components/IngredientWiki";
import IngredientWiki from "../../components/IngredientWiki";
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'دانشنامه ترکیبات دارویی',
description: 'اطلاعات کامل در مورد مواد موثره، ویتامین‌ها و ترکیبات گیاهی به کار رفته در محصولات تخصصی کانینا ایران.',
title: "دانشنامه ترکیبات دارویی و علمی - کانینا ایران",
description: "اطلاعات کامل پزشکی در مورد مواد موثره، ویتامین‌ها و ترکیبات گیاهی نایاب به کار رفته در محصولات تخصصی کانیتا آلمان.",
alternates: {
canonical: "/wiki",
},
openGraph: {
title: "دانشنامه ترکیبات دارویی و علمی - کانینا ایران",
description: "اطلاعات کامل در مورد مواد موثره، ویتامین‌ها و ترکیبات گیاهی به کار رفته در محصولات تخصصی کانینا ایران.",
url: "https://canina-iran.com/wiki",
siteName: "کانینا ایران",
locale: "fa_IR",
type: "website",
}
};
export default function WikiPage() {

View File

@ -26,15 +26,14 @@ import {
import { useCartStore } from "../lib/store/cartStore";
import SafeImage from "./SafeImage";
const CATEGORIES = [
{ id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> },
{ id: "joints", label: "مفاصل و استخوان", icon: <HeartPulse className="w-4 h-4" /> },
{ id: "immune", label: "ایمنی و گوارش", icon: <ShieldCheck className="w-4 h-4" /> },
{ id: "energy", label: "ویتامین و انرژی", icon: <Activity className="w-4 h-4" /> },
{ id: "special-care", label: "مراقبت ویژه", icon: <Stethoscope className="w-4 h-4" /> },
{ id: "general", label: "تقویت عمومی", icon: <Heart className="w-4 h-4" /> },
{ id: "nutrition", label: "تغذیه تخصصی", icon: <Sparkles className="w-4 h-4" /> },
];
const ICON_MAP: Record<string, React.ReactNode> = {
joints: <HeartPulse className="w-4 h-4" />,
immune: <ShieldCheck className="w-4 h-4" />,
energy: <Activity className="w-4 h-4" />,
"special-care": <Stethoscope className="w-4 h-4" />,
general: <Heart className="w-4 h-4" />,
nutrition: <Sparkles className="w-4 h-4" />,
};
const CATEGORY_MAP: Record<string, { category?: string; query?: string; symptoms?: string[] }> = {
// Main Categories
@ -67,10 +66,6 @@ const CATEGORY_MAP: Record<string, { category?: string; query?: string; symptoms
"شستشوی چشم": { category: "special-care", query: "چشم" },
};
const SYMPTOMS = [
"لنگیدن", "سختی در بلند شدن", "ریزش مو", "جرم دندان", "بی‌اشتهایی", "اسهال مزمن", "بعد از مصرف آنتی‌بیوتیک"
];
const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) => void }> = ({ product, onClick }) => {
const { addItem } = useCartStore();
const [isAdding, setIsAdding] = useState(false);
@ -112,10 +107,9 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
)}
{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' :
compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600'
}`}>
<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 === '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>
@ -124,40 +118,49 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
<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" />
<div className="absolute bottom-4 left-4 flex gap-2">
{(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">
<Dog className="w-4 h-4" />
</div>
)}
{(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">
<Cat className="w-4 h-4" />
</div>
)}
{(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">
<Dog className="w-4 h-4" />
</div>
)}
{(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">
<Cat className="w-4 h-4" />
</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>
<h3 dir="ltr" className="text-lg font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors mb-3 leading-tight text-left">{product.name}</h3>
<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 className="flex flex-wrap gap-1 mb-6">
{product.symptoms.slice(0, 2).map((s, i) => (
<span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md">
{s}
</span>
))}
<div className="flex flex-wrap gap-1">
{product.symptoms.slice(0, 2).map((s, i) => (
<span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md">
{s}
</span>
))}
</div>
<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>
<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>
</motion.div>
@ -168,40 +171,59 @@ import { useRouter } from 'next/navigation';
export default function ArchivePage({
initialCategory = "all",
initialSearch = ""
initialSearch = "",
initialPetType = "all",
initialSymptoms = ""
}: {
initialCategory?: string,
initialSearch?: string
initialSearch?: string,
initialPetType?: string,
initialSymptoms?: string
}) {
const router = useRouter();
const [selectedCategory, setSelectedCategory] = useState(initialCategory);
const [selectedPet, setSelectedPet] = useState<PetType | "all">("all");
const [activeSymptoms, setActiveSymptoms] = useState<string[]>([]);
const [selectedPet, setSelectedPet] = useState<PetType | "all">(initialPetType as any);
const [activeSymptoms, setActiveSymptoms] = useState<string[]>(initialSymptoms ? initialSymptoms.split(',') : []);
const [searchQuery, setSearchQuery] = useState(initialSearch);
const [isUpdating, setIsUpdating] = useState(false);
const [filteredProducts, setFilteredProducts] = useState<Product[]>([]);
const [page, setPage] = useState(1);
const [meta, setMeta] = useState({ total: 0, lastPage: 1 });
const CATEGORY_LABELS: Record<string, string> = {
all: "همه محصولات",
joints: "مفاصل و استخوان",
immune: "ایمنی و گوارش",
energy: "ویتامین و انرژی",
"special-care": "مراقبت ویژه",
general: "تقویت عمومی",
nutrition: "تغذیه تخصصی",
};
const [categories, setCategories] = useState<{ id: string; label: string; icon: React.ReactNode }[]>([]);
const [symptoms, setSymptoms] = useState<string[]>([]);
// Debug Log for Search Params
useEffect(() => {
console.log("[ArchivePage] URL Params Sync:", { initialCategory, initialSearch });
}, [initialCategory, initialSearch]);
const loadFilters = async () => {
try {
const filters = await productService.getActiveFilters();
const mapped = [
{ id: "all", label: "همه محصولات", icon: <Activity className="w-4 h-4" /> },
...filters.categories.map(c => ({
id: c.slug,
label: c.name,
icon: ICON_MAP[c.slug] || <Activity className="w-4 h-4" />
}))
];
setCategories(mapped);
setSymptoms(filters.symptoms);
} catch (e) {
console.error("Failed to load active filters:", e);
}
};
loadFilters();
}, []);
const CATEGORY_LABELS = useMemo(() => {
const labels: Record<string, string> = { all: "همه محصولات" };
categories.forEach(c => {
labels[c.id] = c.label;
});
return labels;
}, [categories]);
// Sync state with prop change (e.g. from Header menu)
useEffect(() => {
setIsUpdating(true);
// Normalize Input (Trim and lowercase)
const normSearch = (initialSearch || "").trim().toLowerCase();
const normCat = initialCategory.trim().toLowerCase();
@ -209,7 +231,11 @@ export default function ArchivePage({
const mapKey = normSearch || normCat;
const mapping = CATEGORY_MAP[mapKey];
console.log("[ArchivePage] Processing Mapping for:", mapKey);
if (selectedCategory === initialCategory && searchQuery === initialSearch) {
return;
}
setIsUpdating(true);
// Reset ALL other filters when a new category/solution is selected from menu
setSelectedPet("all");
@ -226,7 +252,7 @@ export default function ArchivePage({
setSelectedCategory(initialCategory);
setSearchQuery(initialSearch);
if (initialSearch && SYMPTOMS.includes(initialSearch)) {
if (initialSearch && symptoms.includes(initialSearch)) {
setActiveSymptoms([initialSearch]);
}
}
@ -236,37 +262,53 @@ export default function ArchivePage({
}, 400);
return () => clearTimeout(timer);
}, [initialCategory, initialSearch]);
}, [initialCategory, initialSearch, symptoms]);
// Update URL Query Parameters
useEffect(() => {
const params = new URLSearchParams();
if (selectedCategory && selectedCategory !== "all") params.set('category', selectedCategory);
if (selectedPet && selectedPet !== "all") params.set('petType', selectedPet);
if (searchQuery) params.set('search', searchQuery);
if (activeSymptoms.length > 0) params.set('symptoms', activeSymptoms.join(','));
const queryString = params.toString();
const newUrl = `/shop${queryString ? `?${queryString}` : ''}`;
router.push(newUrl, { scroll: false });
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
const fetchProducts = async (resetPage = true) => {
setIsUpdating(true);
try {
const res = await productService.getProducts({
category: selectedCategory,
petType: selectedPet,
query: searchQuery,
// Send the first active symptom to the backend for server-side filtering
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
page: resetPage ? 1 : page,
limit: 20,
});
let result = res.data;
// If multiple symptoms selected, do client-side intersection (rare case)
if (activeSymptoms.length > 1) {
result = result.filter(p => activeSymptoms.every(s => p.symptoms.includes(s)));
}
setFilteredProducts(result);
setMeta(res.meta);
if (resetPage) setPage(1);
} catch (error) {
console.error("Failed to fetch products:", error);
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
} finally {
setIsUpdating(false);
}
};
useEffect(() => {
const fetchProducts = async () => {
setIsUpdating(true);
try {
const res = await productService.getProducts({
category: selectedCategory,
petType: selectedPet,
query: searchQuery,
page: 1,
limit: 20,
});
let result = res.data;
if (activeSymptoms.length > 0) {
result = result.filter(p => p.symptoms.some(s => activeSymptoms.includes(s)));
}
setFilteredProducts(result);
setMeta(res.meta);
setPage(1);
} catch (error) {
console.error("Failed to fetch products:", error);
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
} finally {
setIsUpdating(false);
}
};
fetchProducts();
fetchProducts(true);
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
const loadMore = async () => {
@ -276,6 +318,7 @@ export default function ArchivePage({
category: selectedCategory,
petType: selectedPet,
query: searchQuery,
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
page: page + 1,
limit: 20,
});
@ -317,158 +360,173 @@ export default function ArchivePage({
</div>
{/* Desktop Breadcrumbs */}
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
<ChevronLeft className="w-2.5 h-2.5" />
<span className="cursor-pointer hover:text-canina-blue" onClick={() => { setSelectedCategory("all"); }}>کاتالوگ</span>
{selectedCategory !== "all" && (
<>
<ChevronLeft className="w-2.5 h-2.5" />
<span className="text-canina-blue">{CATEGORY_LABELS[selectedCategory] || selectedCategory}</span>
</>
)}
</div>
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
<ChevronLeft className="w-2.5 h-2.5" />
<span className="cursor-pointer hover:text-canina-blue" onClick={() => { setSelectedCategory("all"); }}>کاتالوگ</span>
{selectedCategory !== "all" && (
<>
<ChevronLeft className="w-2.5 h-2.5" />
<span className="text-canina-blue">{CATEGORY_LABELS[selectedCategory] || selectedCategory}</span>
</>
)}
</div>
<div className="flex flex-col lg:flex-row gap-10">
{/* Sidebar Filters */}
<aside className="lg:w-72 flex-shrink-0 space-y-8">
<div className="bg-white rounded-[2rem] p-6 border border-medical-gray-200 shadow-sm">
<div className="flex items-center gap-2 mb-6">
<Filter className="w-5 h-5 text-canina-blue" />
<h3 className="font-black text-medical-gray-900">فیلترهای تخصصی</h3>
<div className="flex items-center gap-2 mb-6">
<Filter className="w-5 h-5 text-canina-blue" />
<h3 className="font-black text-medical-gray-900">فیلترهای تخصصی</h3>
</div>
<div className="space-y-6">
{/* Pet Type */}
<div>
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">نوع پت</span>
<div className="grid grid-cols-3 gap-2">
{[
{ id: "all", label: "هر دو", icon: <Activity className="w-3 h-3" /> },
{ id: "سگ", label: "سگ", icon: <Dog className="w-3 h-3" /> },
{ id: "گربه", label: "گربه", icon: <Cat className="w-3 h-3" /> },
].map(pet => (
<button
key={pet.id}
onClick={() => setSelectedPet(pet.id as any)}
className={`py-2 rounded-xl text-[10px] font-black border transition-all flex flex-col items-center gap-1 ${selectedPet === pet.id ? 'bg-canina-blue border-canina-blue text-white shadow-lg shadow-canina-blue/20' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}
>
{pet.icon}
{pet.label}
</button>
))}
</div>
</div>
<div className="space-y-6">
{/* Pet Type */}
<div>
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">نوع پت</span>
<div className="grid grid-cols-3 gap-2">
{[
{ id: "all", label: "هر دو", icon: <Activity className="w-3 h-3" /> },
{ id: "سگ", label: "سگ", icon: <Dog className="w-3 h-3" /> },
{ id: "گربه", label: "گربه", icon: <Cat className="w-3 h-3" /> },
].map(pet => (
<button
key={pet.id}
onClick={() => setSelectedPet(pet.id as any)}
className={`py-2 rounded-xl text-[10px] font-black border transition-all flex flex-col items-center gap-1 ${selectedPet === pet.id ? 'bg-canina-blue border-canina-blue text-white shadow-lg shadow-canina-blue/20' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}
>
{pet.icon}
{pet.label}
</button>
))}
</div>
</div>
{/* Solutions */}
<div>
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">راهکار درمانی</span>
<div className="flex flex-col gap-2">
{CATEGORIES.map(cat => (
<button
key={cat.id}
onClick={() => setSelectedCategory(cat.id)}
className={`flex items-center gap-3 px-4 py-3 rounded-2xl text-xs font-bold transition-all border ${selectedCategory === cat.id ? 'bg-canina-blue border-canina-blue text-white' : 'bg-white border-medical-gray-100 text-medical-gray-600 hover:border-canina-blue/30'}`}
>
{cat.icon}
{cat.label}
</button>
))}
</div>
</div>
{/* Symptoms Checklist */}
<div>
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">جستجو بر اساس علائم</span>
<div className="flex flex-wrap gap-2">
{SYMPTOMS.map(s => (
<button
key={s}
onClick={() => toggleSymptom(s)}
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold transition-all border ${activeSymptoms.includes(s) ? 'bg-medical-gray-900 border-medical-gray-900 text-white' : 'bg-medical-gray-50 border-medical-gray-100 text-medical-gray-500 hover:border-medical-gray-300'}`}
>
{s}
</button>
))}
</div>
</div>
{/* Solutions */}
<div>
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">راهکار درمانی</span>
<div className="flex flex-col gap-2">
{categories.map(cat => (
<button
key={cat.id}
onClick={() => setSelectedCategory(cat.id)}
className={`flex items-center gap-3 px-4 py-3 rounded-2xl text-xs font-bold transition-all border ${selectedCategory === cat.id ? 'bg-canina-blue border-canina-blue text-white' : 'bg-white border-medical-gray-100 text-medical-gray-600 hover:border-canina-blue/30'}`}
>
{cat.icon}
{cat.label}
</button>
))}
</div>
</div>
{/* Symptoms Pill Tags */}
<div>
<div className="flex items-center justify-between mb-3">
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">جستجو بر اساس علائم</span>
{activeSymptoms.length > 0 && (
<button
onClick={() => setActiveSymptoms([])}
className="text-[10px] font-bold text-canina-blue hover:underline"
>
پاک کردن
</button>
)}
</div>
<div className="flex flex-wrap gap-1.5 max-h-52 overflow-y-auto scrollbar-thin scrollbar-thumb-medical-gray-200 scrollbar-track-transparent pr-0.5">
{symptoms.length === 0 ? (
<span className="text-[10px] text-medical-gray-400">در حال بارگذاری...</span>
) : symptoms.map(s => (
<button
key={s}
onClick={() => toggleSymptom(s)}
className={`px-2.5 py-1 rounded-full text-[10px] font-bold transition-all border whitespace-nowrap ${activeSymptoms.includes(s)
? 'bg-canina-blue border-canina-blue text-white shadow-sm shadow-canina-blue/30'
: 'bg-medical-gray-50 border-medical-gray-200 text-medical-gray-500 hover:border-canina-blue/40 hover:text-canina-blue'
}`}
>
{s}
</button>
))}
</div>
</div>
</div>
</div>
{/* Promo Card */}
<div className="bg-medical-gray-900 rounded-[2rem] p-6 text-white relative overflow-hidden group">
<div className="absolute -top-10 -left-10 w-32 h-32 bg-canina-blue rounded-full blur-3xl opacity-20 group-hover:opacity-40 transition-opacity" />
<h4 className="text-lg font-black italic mb-4 relative z-10 underline decoration-canina-blue underline-offset-8">مشاوره رایگان</h4>
<p className="text-xs text-white/60 leading-relaxed mb-6 relative z-10">اگر نمی‌دانید کدام ترکیب برای پت شما مناسب است، همین حالا با دامپزشکان ما تماس بگیرید.</p>
<a href="tel:0218888" className="w-full bg-white text-medical-gray-900 py-3 rounded-xl text-xs font-black hover:scale-105 transition-transform relative z-10 flex items-center justify-center">شماره تماس: ۰۲۱-۸۸۸۸</a>
<div className="absolute -top-10 -left-10 w-32 h-32 bg-canina-blue rounded-full blur-3xl opacity-20 group-hover:opacity-40 transition-opacity" />
<h4 className="text-lg font-black italic mb-4 relative z-10 underline decoration-canina-blue underline-offset-8">مشاوره رایگان</h4>
<p className="text-xs text-white/60 leading-relaxed mb-6 relative z-10">اگر نمی‌دانید کدام ترکیب برای پت شما مناسب است، همین حالا با دامپزشکان ما تماس بگیرید.</p>
<a href="tel:0218888" className="w-full bg-white text-medical-gray-900 py-3 rounded-xl text-xs font-black hover:scale-105 transition-transform relative z-10 flex items-center justify-center">شماره تماس: ۰۲۱-۸۸۸۸</a>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 relative">
<div className="flex flex-col md:flex-row items-center justify-between mb-8 gap-4">
<h2 className="text-3xl font-black text-medical-gray-900">کاتولوگ دارویی <span className="text-canina-blue">Canina</span></h2>
<div className="relative w-full md:w-80">
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
<input
type="text"
placeholder="جستجوی محصول..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-white border border-medical-gray-200 rounded-full py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
/>
</div>
<h2 className="text-3xl font-black text-medical-gray-900">کاتولوگ دارویی <span className="text-canina-blue">Canina</span></h2>
<div className="relative w-full md:w-80">
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
<input
type="text"
placeholder="جستجوی محصول..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-white border border-medical-gray-200 rounded-full py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
/>
</div>
</div>
<AnimatePresence mode="wait">
{isUpdating ? (
{isUpdating ? (
<div key="skeleton" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
{Array.from({ length: 8 }).map((_, i) => (
<ProductCardSkeleton key={i} />
))}
{Array.from({ length: 8 }).map((_, i) => (
<ProductCardSkeleton key={i} />
))}
</div>
) : filteredProducts.length > 0 ? (
) : filteredProducts.length > 0 ? (
<>
<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">
{filteredProducts.map((p: Product) => (
<ArchiveProductCard key={p.id} product={p} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} />
))}
{filteredProducts.map((p: Product) => (
<ArchiveProductCard key={p.id} product={p} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} />
))}
</AnimatePresence>
</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 className="w-20 h-20 bg-medical-gray-50 rounded-full flex items-center justify-center text-medical-gray-300 mb-6">
<Search className="w-10 h-10" />
</div>
<h3 className="text-2xl font-black text-medical-gray-900 mb-2">محصولی یافت نشد!</h3>
<p className="text-medical-gray-400 max-w-sm mb-8 font-bold">با فیلترهای فعلی محصولی مطابق با نیاز شما پیدا نکردیم. لطفاً فیلترها را گسترده‌تر کنید یا دکمه ریست را بزنید.</p>
<button
onClick={() => {
router.push('/shop');
setSelectedCategory("all");
setSelectedPet("all");
setActiveSymptoms([]);
setSearchQuery("");
}}
{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>
>
بارگذاری محصولات بیشتر
</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 className="w-20 h-20 bg-medical-gray-50 rounded-full flex items-center justify-center text-medical-gray-300 mb-6">
<Search className="w-10 h-10" />
</div>
<h3 className="text-2xl font-black text-medical-gray-900 mb-2">محصولی یافت نشد!</h3>
<p className="text-medical-gray-400 max-w-sm mb-8 font-bold">با فیلترهای فعلی محصولی مطابق با نیاز شما پیدا نکردیم. لطفاً فیلترها را گسترده‌تر کنید یا دکمه ریست را بزنید.</p>
<button
onClick={() => {
router.push('/shop');
setSelectedCategory("all");
setSelectedPet("all");
setActiveSymptoms([]);
setSearchQuery("");
}}
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>
)}
)}
</AnimatePresence>
</main>

View File

@ -103,10 +103,10 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
{items.map((item) => (
<div key={item.product.id} className="flex gap-4 p-4 bg-medical-gray-50 rounded-3xl border border-medical-gray-100 group">
<div className="w-20 h-20 bg-white rounded-2xl p-2 flex-shrink-0 flex items-center justify-center shadow-sm">
<SafeImage src={item.product.image} alt={item.product.name} className="w-full h-full" imgClassName="object-contain" />
<SafeImage src={item.product.image} alt={item.product.nameFa || item.product.name} className="w-full h-full" imgClassName="object-contain" />
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-black text-medical-gray-900 truncate mb-1">{item.product.name}</h4>
<h4 className="text-sm font-black text-medical-gray-900 truncate mb-1">{item.product.nameFa || item.product.name}</h4>
<p className="text-[10px] text-canina-blue font-bold mb-3">{item.product.category}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 bg-white rounded-lg border border-medical-gray-200 p-1">

View File

@ -3,71 +3,74 @@ import { useState, useEffect, useMemo } from "react";
import { Product } from "../lib/data/products";
import { usePetStore } from "../lib/store/usePetStore";
import { productService } from "../lib/services/productService";
import { motion, AnimatePresence } from "motion/react";
import { Eye, ShoppingCart, ShieldCheck, Heart, AlertCircle, Sparkles } from "lucide-react";
import { motion } from "motion/react";
import { Eye, ShoppingCart, Heart, AlertCircle, Sparkles } from "lucide-react";
import SafeImage from "./SafeImage";
import { ProductCardSkeleton } from "./Skeleton";
import Link from 'next/link';
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSettingsStore } from "../lib/store/settingsStore";
import { useCartStore } from "../lib/store/cartStore";
import { toast } from "sonner";
function ProductCard({ product, onClick }: { product: Product; key?: string | number; onClick: (p: Product) => void }) {
function ProductCard({ product }: { product: Product }) {
const router = useRouter();
const getText = useSettingsStore(state => state.getText);
const getText = useSettingsStore((state) => state.getText);
const { getActivePet } = usePetStore();
const activePet = getActivePet();
const { addItem } = useCartStore();
const nameFa = product.nameFa || product.name;
const nameEn = product.nameEn || "";
const compatibility = useMemo(() => {
if (!activePet) return null;
const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو";
const helpsSymptom = (activePet.medicalConditions || []).some(s => product.symptoms.includes(s));
if (!sameSpecies) return { type: 'alert', text: `مخصوص ${product.suitableFor}` };
if (helpsSymptom) return { type: 'success', text: `توصیه شده برای ${activePet.name}` };
return { type: 'neutral', text: `مناسب برای ${activePet.name}` };
const helpsSymptom = (activePet.medicalConditions || []).some((s) => product.symptoms.includes(s));
if (!sameSpecies) return { type: "alert", text: `مخصوص ${product.suitableFor}` };
if (helpsSymptom) return { type: "success", text: `توصیه شده برای ${activePet.name}` };
return { type: "neutral", text: `مناسب برای ${activePet.name}` };
}, [product, activePet]);
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="group bg-white rounded-3xl border border-medical-gray-200 overflow-hidden hover:shadow-2xl hover:shadow-canina-blue/10 transition-all duration-500 flex flex-col h-full cursor-pointer relative"
transition={{ duration: 0.45 }}
className="group bg-white rounded-3xl border border-medical-gray-200 overflow-hidden hover:shadow-2xl hover:shadow-canina-blue/10 transition-all duration-500 flex flex-col cursor-pointer relative"
>
{/* Category Badge */}
<div className="absolute top-4 right-4 z-30 px-3 py-1 bg-white/95 backdrop-blur-md rounded-full border border-medical-gray-200 text-[10px] font-bold text-medical-gray-600 uppercase tracking-widest shadow-sm">
{product.category}
</div>
{/* Compatibility Tag */}
{compatibility && (
<div className={`absolute top-16 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'
<div className={`absolute top-[3.25rem] 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 === '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}
</div>
)}
{/* Product Image Area */}
<div className="relative aspect-square bg-medical-gray-50 p-10 flex items-center justify-center overflow-hidden">
{/* Image */}
<div className="relative aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-5">
<SafeImage
src={product.image}
alt={product.name}
className="w-full h-full group-hover:scale-105 transition-transform duration-500 drop-shadow-2xl"
imgClassName="object-contain"
alt={nameFa}
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
imgClassName="object-contain max-h-[170px] mx-auto"
/>
<div className="absolute top-4 right-4 px-3 py-1 bg-white/80 backdrop-blur-md rounded-full border border-medical-gray-200 text-[10px] font-bold text-medical-gray-500 uppercase tracking-widest">
{product.category}
</div>
{/* Main PDP Link */}
{/* PDP Link overlay */}
<Link href={`/shop/${product.slug || product.id}`} className="absolute inset-0 z-10" />
{/* Hover Actions (z-20 to be clickable over the main PDP link) */}
{/* Hover Actions */}
<div className="absolute inset-0 bg-canina-blue/60 opacity-0 group-hover:opacity-100 backdrop-blur-sm transition-all duration-300 flex items-center justify-center gap-4 z-20">
<div
onClick={(e) => {
e.stopPropagation();
router.push(`/shop/${product.slug || product.id}`);
}}
className="w-12 h-12 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
onClick={(e) => { e.stopPropagation(); router.push(`/shop/${product.slug || product.id}`); }}
className="w-11 h-11 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
>
<Eye className="w-5 h-5" />
</div>
@ -75,111 +78,94 @@ function ProductCard({ product, onClick }: { product: Product; key?: string | nu
onClick={(e) => {
e.stopPropagation();
addItem(product, 1);
toast.success(`${product.name} به سبد خرید شما اضافه شد.`);
toast.success(`${nameFa} به سبد خرید اضافه شد.`);
}}
className="w-12 h-12 rounded-full bg-canina-blue text-white flex items-center justify-center hover:scale-110 transition-transform shadow-lg border border-white/20 cursor-pointer"
className="w-11 h-11 rounded-full bg-canina-blue text-white flex items-center justify-center hover:scale-110 transition-transform shadow-lg border border-white/20 cursor-pointer"
>
<ShoppingCart className="w-5 h-5" />
</div>
</div>
</div>
{/* Content Area */}
<div className="p-8 flex flex-col flex-1" onClick={() => router.push(`/shop/${product.slug || product.id}`)}>
<div className="flex items-start justify-between mb-4 gap-2">
<h3 dir="ltr" className="text-base md:text-lg font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-tight text-left line-clamp-2 flex-1">
{product.name}
{/* Content */}
<Link href={`/shop/${product.slug || product.id}`} className="px-5 py-4 flex flex-col flex-1 gap-3">
{/* Dual-language Name */}
<div className="flex flex-col gap-1">
<h3 dir="rtl" className="text-[14px] font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-2 font-vazir">
{nameFa}
</h3>
<ShieldCheck className="w-6 h-6 text-canina-blue flex-shrink-0" />
{nameEn && (
<span dir="ltr" className="text-[11px] font-semibold text-slate-400 font-sans text-left block line-clamp-1">
{nameEn}
</span>
)}
</div>
<p className="text-sm text-medical-gray-500 leading-relaxed mb-6 flex-1 line-clamp-2">
{/* Short description */}
<p className="text-xs text-medical-gray-500 leading-relaxed line-clamp-2 flex-1">
{product.shortDescription || product.description}
</p>
<div className="space-y-4 pt-4 border-t border-medical-gray-100 mt-auto">
{product.benefits && (
<div className="flex items-center gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue" />
<span className="text-xs font-semibold text-medical-gray-700 italic">{product.benefits}</span>
</div>
)}
<div className="flex items-center justify-between">
<div className="text-lg font-black text-medical-gray-900 font-vazir whitespace-nowrap">{product.price}</div>
<button
onClick={(e) => {
e.stopPropagation();
router.push(`/shop/${product.slug || product.id}`);
}}
className="text-xs font-black text-canina-blue uppercase tracking-widest border-b-2 border-canina-blue pb-1 hover:text-medical-gray-900 hover:border-medical-gray-900 transition-all whitespace-nowrap"
>
{getText('product_view_details', "مشاهده جزئیات")}
</button>
</div>
{/* Footer: Price + CTA */}
<div className="flex items-center justify-between pt-3 border-t border-medical-gray-100">
<div className="text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">{product.price}</div>
<span className="text-[11px] font-black text-canina-blue uppercase tracking-widest border-b-2 border-canina-blue pb-0.5 hover:text-medical-gray-900 hover:border-medical-gray-900 transition-all whitespace-nowrap">
{getText("product_view_details", "مشاهده جزئیات")}
</span>
</div>
</div>
</Link>
</motion.div>
);
}
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
export default function FeaturedProducts() {
const router = useRouter();
const getText = useSettingsStore(state => state.getText);
const getText = useSettingsStore((state) => state.getText);
const [products, setProducts] = useState<Product[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchFeatured = async () => {
try {
const data = await productService.getFeaturedProducts();
setProducts(data);
} catch (error) {
console.error("Failed to fetch featured products:", error);
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
} finally {
setIsLoading(false);
}
};
fetchFeatured();
productService.getFeaturedProducts()
.then(setProducts)
.catch(() => toast.error("خطا در دریافت لیست محصولات از سرور."))
.finally(() => setIsLoading(false));
}, []);
return (
<section className="py-16 bg-medical-gray-50">
<div className="max-w-7xl mx-auto px-4">
{/* Header - Correct RTL alignment with primary and secondary elements */}
<div className="flex items-center justify-between w-full mb-12 pb-6 border-b border-medical-gray-200">
<div>
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-2 font-vazir">{getText('featured_badge', "بیشترین انتخاب توسط دامپزشکان")}</div>
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-2 font-vazir">
{getText("featured_badge", "بیشترین انتخاب توسط دامپزشکان")}
</div>
<h2 className="text-2xl md:text-3xl lg:text-4xl font-black text-medical-gray-900 font-lalezar tracking-wide">
{getText('featured_title_line1', "محصولات برگزیده و راهکارهای")} <span className="text-canina-blue">{getText('featured_title_line2', "درمان تخصصی")}</span>
{getText("featured_title_line1", "محصولات برگزیده و راهکارهای")}{" "}
<span className="text-canina-blue">{getText("featured_title_line2", "درمان تخصصی")}</span>
</h2>
</div>
<button
onClick={() => router.push('/shop')}
onClick={() => router.push("/shop")}
className="inline-flex items-center gap-2 text-canina-blue font-black text-sm hover:translate-x-1 transition-all group font-vazir"
>
{getText('featured_view_all', "مشاهده تمامی محصولات")}
{getText("featured_view_all", "مشاهده تمامی محصولات")}
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{isLoading ? (
Array.from({ length: 4 }).map((_, i) => <ProductCardSkeleton key={i} />)
Array.from({ length: 4 }).map((_, i) => <ProductCardSkeleton key={i} />)
) : products.length > 0 ? (
products.map((p) => (
<ProductCard key={p.id} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} product={p} />
))
products.map((p) => <ProductCard key={p.id} product={p} />)
) : (
<div className="col-span-full py-16 px-8 flex flex-col items-center justify-center text-center bg-white rounded-[2.5rem] border border-medical-gray-200 shadow-xl max-w-lg mx-auto">
<AlertCircle className="w-12 h-12 text-canina-teal mb-4" />
<h3 className="text-xl font-black text-medical-gray-900 mb-2 font-vazir">{getText('featured_no_products_title', "هیچ محصولی یافت نشد")}</h3>
<h3 className="text-xl font-black text-medical-gray-900 mb-2 font-vazir">
{getText("featured_no_products_title", "هیچ محصولی یافت نشد")}
</h3>
<p className="text-sm text-medical-gray-500 font-medium font-vazir leading-relaxed">
{getText('featured_no_products_desc', "در حال حاضر هیچ محصول برجسته‌ای برای نمایش وجود ندارد. لطفاً در پنل ادمین محصولات برگزیده را تنظیم کنید.")}
{getText("featured_no_products_desc", "در حال حاضر هیچ محصول برجسته‌ای برای نمایش وجود ندارد.")}
</p>
</div>
)}

View File

@ -2,6 +2,7 @@
import React from "react";
import { Phone, Mail, MapPin, Instagram, ShieldCheck, Globe } from "lucide-react";
import { useSettingsStore } from "../lib/store/settingsStore";
import Link from "next/link";
export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
onNavigate: (v: any) => void,
@ -16,13 +17,13 @@ export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
{/* Brand Presence */}
<div className="space-y-8">
<div className="flex items-center gap-3 cursor-pointer group" onClick={() => onNavigate("home")}>
<Link href="/" className="flex items-center gap-3 cursor-pointer group">
<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="flex flex-col">
<span className="text-white font-extrabold text-2xl tracking-tighter leading-none italic font-sans">Canina <span className="text-sm not-italic font-medium border-l-2 border-white/20 pl-2 ml-2 font-shabnam">{getText('brand_name_fa', "ایران")}</span></span>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-widest leading-none mt-1.5 font-shabnam">{getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")}</span>
</div>
</div>
</Link>
<p className="text-sm text-slate-300 leading-relaxed font-medium">
{getText('footer_brand_desc', "تدارک هوشمندانه سلامت برای همراهان وفادار شما. واردکننده انحصاری مکمل‌های درمانی با گرید دارویی اختصاصی از آلمان با سابقه ۴۰ سال نوآوری.")}
</p>
@ -39,25 +40,33 @@ export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
<div className="space-y-8">
<h4 className="text-xl font-bold text-white border-b-2 border-canina-blue pb-2 max-w-max font-lalezar">{getText('footer_links_title', "دسترسی سریع")}</h4>
<ul className="space-y-4 text-sm font-bold text-slate-300">
<li onClick={() => onShopNavigate()} className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_products', "محصولات تخصصی ۲۰۲۴")}
<li>
<Link href="/shop" className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_products', "محصولات تخصصی ۲۰۲۴")}
</Link>
</li>
<li onClick={() => onNavigate("blog")} className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_blog', "مجله سلامت پت (وبلاگ)")}
<li>
<Link href="/blog" className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_blog', "مجله سلامت پت (وبلاگ)")}
</Link>
</li>
<li onClick={() => onNavigate("profile")} className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_profiles', "شناسنامه پتها")}
<li>
<Link href="/profile" className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_profiles', "شناسنامه پت‌ها")}
</Link>
</li>
<li onClick={() => onNavigate("wiki")} className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_wiki', "دانشنامه ترکیبات")}
<li>
<Link href="/wiki" className="hover:text-white hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_wiki', "دانشنامه ترکیبات")}
</Link>
</li>
<li onClick={onB2BOpen} className="hover:text-canina-blue hover:translate-x-[-4px] transition-all cursor-pointer flex items-center gap-2 group text-blue-200">
<div className="w-1.5 h-1.5 rounded-full bg-canina-blue group-hover:w-4 transition-all" />
{getText('footer_link_b2b', "پنل سفارش عمده (B2B)")}
{getText('footer_link_b2b', "همکاری با کلینیک‌ها و پت‌شاپ‌ها (B2B)")}
</li>
</ul>
</div>
@ -82,7 +91,7 @@ export default function Footer({ onNavigate, onShopNavigate, onB2BOpen }: {
</div>
<div>
<p className="text-[11px] text-slate-400 font-extrabold uppercase mb-1">{getText('footer_email_label', "مکاتبات رسمی")}</p>
<p className="text-sm font-bold text-slate-300">{getText('footer_email', "info@canino-iran.com")}</p>
<p className="text-sm font-bold text-slate-300">{getText('footer_email', "info@canina-iran.com")}</p>
</div>
</div>
</div>
@ -117,9 +126,9 @@ 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="text-slate-400 font-shabnam">{getText('footer_copyright', "© ۲۰۲۶ کانینا ایران. تمامی حقوق محفوظ است.")}</div>
<div className="flex gap-8 font-shabnam text-slate-400">
<button onClick={() => onNavigate("about")} className="font-extrabold hover:text-white transition-colors">{getText('footer_about', "درباره ما")}</button>
<button onClick={() => onNavigate("contact")} className="font-extrabold hover:text-white transition-colors">{getText('footer_contact', "تماس با ما")}</button>
<button onClick={() => onNavigate("privacy")} className="font-extrabold hover:text-white transition-colors">{getText('footer_privacy', "حریم خصوصی")}</button>
<Link href="/about" className="font-extrabold hover:text-white transition-colors">{getText('footer_about', "درباره ما")}</Link>
<Link href="/contact" className="font-extrabold hover:text-white transition-colors">{getText('footer_contact', "تماس با ما")}</Link>
<Link href="/privacy" className="font-extrabold hover:text-white transition-colors">{getText('footer_privacy', "حریم خصوصی")}</Link>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@
import React, { useState, useRef, useEffect } from "react";
import { Search, ChevronDown, Menu, X, Pill, ShieldCheck, HeartPulse, Sparkles, ShoppingBag, User, PlusCircle, Check, Building2, LogIn, Wallet, LogOut, MapPin, FileHeart, Dog, Cat } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import Link from "next/link";
import { useCartStore } from "../lib/store/cartStore";
import { usePetStore } from "../lib/store/usePetStore";
import { useUserStore } from "../lib/store/userStore";
@ -10,33 +11,14 @@ import { toast } from "sonner";
import HeaderButton from "./HeaderButton";
import AuthModal from "./AuthModal";
import { cn } from "../lib/utils";
import { productService } from "../lib/services/productService";
const MENU_ITEMS = [
{
id: "joints",
title: "مفاصل و استخوان",
icon: <Pill className="w-5 h-5" />,
solutions: ["آرتروز سگ‌های پیر", "رشد استخوانی توله‌سگ", "تقویت رباط و تاندون"]
},
{
id: "immune",
title: "تقویت سیستم ایمنی و گوارش",
icon: <ShieldCheck className="w-5 h-5" />,
solutions: ["پیشگیری از بیماری", "رفع اسهال و یبوست", "پروبیوتیک‌ها"]
},
{
id: "energy",
title: "ویتامین‌ها و انرژی‌بخش‌ها",
icon: <HeartPulse className="w-5 h-5" />,
solutions: ["مکمل‌های مولتی‌ویتامین", "افزایش اشتها", "رشد و بلوغ"]
},
{
id: "special-care",
title: "مراقبت‌های ویژه (پوست، دندان و چشم)",
icon: <Sparkles className="w-5 h-5" />,
solutions: ["سلامت پوست و مفاصل", "رفع جرم دندان", "شستشوی چشم"]
}
];
const MENU_ICONS: Record<string, React.ReactNode> = {
joints: <Pill className="w-5 h-5" />,
immune: <ShieldCheck className="w-5 h-5" />,
energy: <HeartPulse className="w-5 h-5" />,
"special-care": <Sparkles className="w-5 h-5" />,
};
export default function Header({
onNavigate,
@ -60,6 +42,12 @@ export default function Header({
const [searchQuery, setSearchQuery] = useState("");
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isMobileSolutionsOpen, setIsMobileSolutionsOpen] = useState(false);
const [menuItems, setMenuItems] = useState<{
id: string;
title: string;
icon: React.ReactNode;
solutions: string[];
}[]>([]);
const petMenuRef = useRef<HTMLDivElement>(null);
@ -69,6 +57,24 @@ export default function Header({
const activePet = getActivePet();
const getText = useSettingsStore(state => state.getText);
useEffect(() => {
const loadNavFilters = async () => {
try {
const data = await productService.getNavigationFilters();
const mapped = data.map(item => ({
id: item.slug,
title: item.name,
icon: MENU_ICONS[item.slug] || <Pill className="w-5 h-5" />,
solutions: item.symptoms
}));
setMenuItems(mapped);
} catch (e) {
console.error("Failed to load navigation filters:", e);
}
};
loadNavFilters();
}, []);
// Click Outside logic
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
@ -110,19 +116,24 @@ export default function Header({
</button>
{/* Logo Section */}
<div
<Link
href="/"
className="flex-shrink-0 flex items-center gap-3 cursor-pointer group"
onClick={() => {
onNavigate("home");
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="flex flex-col">
<span className="text-canina-blue font-black text-2xl tracking-tighter leading-none italic font-sans">Canina <span className="text-sm not-italic font-medium border-l-2 border-medical-gray-100 pl-2 ml-2 font-vazir">{getText('brand_name_fa', "ایران")}</span></span>
<span className="text-[10px] text-medical-gray-400 font-bold uppercase tracking-widest leading-none mt-1.5 font-vazir">{getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")}</span>
<div className="flex flex-col justify-center gap-1">
<span className="text-canina-blue font-black text-2xl tracking-tighter italic font-sans flex items-center gap-2 leading-none">
Canina
<span className="text-sm not-italic font-medium border-r-2 border-medical-gray-200 pr-2 font-vazir">
{getText('brand_name_fa', "ایران")}
</span>
</span>
<span className="text-[9px] text-medical-gray-400 font-bold uppercase tracking-wider font-vazir leading-none">
{getText('brand_subtitle', "نماینده رسمی Canina Pharma GmbH آلمان")}
</span>
</div>
</div>
</Link>
</div>
{/* Navigation Items */}
@ -133,7 +144,7 @@ export default function Header({
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`}>
{getText('nav_solutions', "راهکارهای درمانی")}
{getText('nav_solutions', "دسته بندی درمانی")}
<ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isMegaMenuOpen ? 'rotate-180' : ''}`} />
</button>
@ -143,38 +154,45 @@ export default function Header({
initial={{ opacity: 0, y: 15, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 15, scale: 0.98 }}
className="absolute top-[80%] right-0 w-[640px] bg-white border border-medical-gray-100 shadow-2xl rounded-[2.5rem] p-10 grid grid-cols-2 gap-10 z-50 pointer-events-auto"
className="absolute top-[80%] right-0 w-[680px] bg-white border border-medical-gray-100 shadow-2xl rounded-[2.5rem] p-8 grid grid-cols-2 gap-8 z-50 pointer-events-auto"
>
{MENU_ITEMS.map((item, idx) => (
<div
key={idx}
className="group/item cursor-pointer"
onClick={() => {
onShopNavigate(item.id);
setIsMegaMenuOpen(false);
}}
>
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-medical-gray-50 rounded-2xl text-canina-blue group-hover/item:bg-canina-blue group-hover/item:text-white transition-all shadow-sm">
{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-4 cursor-pointer block"
>
<div className="p-2.5 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-sm font-vazir whitespace-nowrap">{item.title}</h4>
</div>
<ul className="space-y-2.5 border-r-2 border-medical-gray-50 pr-4">
{item.solutions.map((sol, sIdx) => (
<li
key={sIdx}
className="text-[12px] text-medical-gray-400 hover:text-canina-blue hover:translate-x-[-4px] transition-all font-bold cursor-pointer font-vazir whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
onShopNavigate(item.id, sol);
setIsMegaMenuOpen(false);
}}
>
{sol}
</li>
))}
</ul>
<h4 className="font-black text-medical-gray-900 text-sm font-vazir">{item.title}</h4>
</Link>
{item.solutions.length > 0 ? (
<div className="flex flex-wrap gap-1.5 border-r-2 border-medical-gray-100 pr-3">
{item.solutions.slice(0, 8).map((sol, sIdx) => (
<Link
key={sIdx}
href={`/shop?category=${item.id}&symptom=${encodeURIComponent(sol)}`}
className="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] 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>
))}
{item.solutions.length > 8 && (
<Link
href={`/shop?category=${item.id}`}
className="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold text-canina-blue hover:underline font-vazir whitespace-nowrap"
onClick={() => setIsMegaMenuOpen(false)}
>
+{item.solutions.length - 8} بیشتر
</Link>
)}
</div>
) : (
<p className="text-[11px] text-medical-gray-400 border-r-2 border-medical-gray-100 pr-3 font-vazir">مشاهده همه محصولات</p>
)}
</div>
))}
</motion.div>
@ -183,18 +201,18 @@ export default function Header({
</div>
{[
{ id: 'shop', label: getText('nav_products', 'محصولات تخصصی'), action: () => onShopNavigate() },
{ id: 'wiki', label: getText('nav_wiki', 'دانشنامه علمی'), action: () => onNavigate('wiki') },
{ id: 'blog', label: getText('nav_blog', 'مجله سلامت پت'), action: () => onNavigate('blog') },
{ id: 'profile', label: getText('nav_pet_profiles', 'شناسنامه پتها'), action: () => onNavigate({ view: 'profile', subview: 'index' }) }
{ 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) => (
<button
<Link
key={item.id}
onClick={item.action}
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] ${currentView === item.id ? 'bg-canina-blue/5 text-canina-blue' : 'text-medical-gray-600 hover:bg-medical-gray-50 hover:text-canina-blue'}`}
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'}`}
>
{item.label}
</button>
</Link>
))}
</nav>
@ -207,10 +225,10 @@ export default function Header({
{isSearchFocused && (
<motion.div
initial={{ width: 0, opacity: 0 }}
animate={{ width: 350, opacity: 1 }}
animate={{ width: 320, opacity: 1 }}
exit={{ width: 0, opacity: 0 }}
transition={{ type: "spring", damping: 20, stiffness: 200 }}
className="absolute right-0 top-1/2 -translate-y-1/2 bg-white border-2 border-canina-blue rounded-2xl shadow-xl overflow-hidden h-12 flex items-center pr-10"
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"
>
<form onSubmit={handleSearch} className="w-full flex items-center">
<input
@ -219,12 +237,11 @@ export default function Header({
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onBlur={() => !searchQuery && setIsSearchFocused(false)}
placeholder={getText('nav_search_placeholder', "جستجوی محصولات، مقالات سلامت و...")}
className="w-full bg-transparent px-4 py-2 text-[12px] font-bold outline-none font-vazir text-medical-gray-900"
placeholder="جستجوی محصولات، مقالات سلامت و غیره…"
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"
/>
<button type="submit" className="px-4 py-2 bg-canina-blue text-white text-[10px] font-black hover:bg-medical-gray-900 transition-colors">
{getText('nav_search_button', "جستجو")}
</button>
<input type="submit" className="hidden" />
</form>
</motion.div>
)}
@ -232,10 +249,22 @@ export default function Header({
<HeaderButton
variant={isSearchFocused ? "primary" : "secondary"}
className={cn("w-12 h-12 transition-all duration-300", isSearchFocused ? "z-30 bg-canina-blue text-white" : "")}
onClick={() => setIsSearchFocused(!isSearchFocused)}
className={cn(
"w-12 h-12 transition-all duration-300",
isSearchFocused
? "z-30 bg-canina-blue text-white rounded-r-2xl rounded-l-none border border-canina-blue border-l-0 shadow-none"
: "rounded-2xl"
)}
onClick={() => {
if (isSearchFocused && searchQuery.trim()) {
onSearch(searchQuery);
setIsSearchFocused(false);
} else {
setIsSearchFocused(!isSearchFocused);
}
}}
>
<Search className="w-5 h-5" />
<Search className={cn("w-5 h-5", isSearchFocused ? "text-white" : "text-canina-blue")} />
</HeaderButton>
</div>
@ -361,9 +390,10 @@ export default function Header({
) : (
<HeaderButton
variant="outline"
className="px-6 h-12 text-sm font-black"
layout="row"
className="px-6 h-12 text-sm font-black flex items-center justify-center"
onClick={() => setIsAuthModalOpen(true)}
icon={<LogIn className="w-5 h-5" />}
icon={<LogIn className="w-5 h-5 text-canina-blue" />}
>
{getText('nav_login', "ورود / ثبت‌نام")}
</HeaderButton>
@ -405,10 +435,10 @@ export default function Header({
dir="rtl"
>
<div className="flex items-center justify-between border-b border-medical-gray-100 pb-4">
<div className="flex items-center gap-3">
<Link href="/" className="flex items-center gap-3" onClick={() => setIsMobileMenuOpen(false)}>
<div className="w-10 h-10 bg-canina-blue rounded-xl flex items-center justify-center text-white font-bold text-xl italic">C</div>
<span className="text-canina-blue font-black text-xl italic font-sans">Canina</span>
</div>
</Link>
<button
onClick={() => setIsMobileMenuOpen(false)}
className="p-2 rounded-xl hover:bg-medical-gray-50 text-medical-gray-600 transition-all"
@ -425,7 +455,7 @@ export default function Header({
onClick={() => setIsMobileSolutionsOpen(!isMobileSolutionsOpen)}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl text-sm font-black text-medical-gray-600 hover:bg-medical-gray-50 transition-all font-vazir"
>
<span>{getText('nav_solutions', "راهکارهای درمانی")}</span>
<span>{getText('nav_solutions', "دسته بندی درمانی")}</span>
<ChevronDown className={`w-4 h-4 transition-transform duration-300 ${isMobileSolutionsOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
@ -436,30 +466,26 @@ export default function Header({
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden mr-4 pr-2 border-r-2 border-medical-gray-100 mt-1 space-y-3"
>
{MENU_ITEMS.map((item, idx) => (
{menuItems.map((item, idx) => (
<div key={idx} className="py-1">
<div
className="text-[13px] font-bold text-medical-gray-800 font-vazir hover:text-canina-blue cursor-pointer flex items-center gap-2"
onClick={() => {
onShopNavigate(item.id);
setIsMobileMenuOpen(false);
}}
<Link
href={`/shop?category=${item.id}`}
onClick={() => setIsMobileMenuOpen(false)}
className="text-[13px] font-bold text-medical-gray-800 font-vazir hover:text-canina-blue cursor-pointer flex items-center gap-2 block"
>
<span className="text-canina-blue/80">{item.icon}</span>
<span>{item.title}</span>
</div>
</Link>
<ul className="mt-1 space-y-1 mr-6">
{item.solutions.map((sol, sIdx) => (
<li
key={sIdx}
className="text-[11px] text-medical-gray-500 hover:text-canina-blue cursor-pointer py-1 font-vazir"
onClick={(e) => {
e.stopPropagation();
onShopNavigate(item.id, sol);
setIsMobileMenuOpen(false);
}}
>
{sol}
<li key={sIdx}>
<Link
href={`/shop?category=${item.id}&symptoms=${sol}`}
className="text-[11px] text-medical-gray-500 hover:text-canina-blue cursor-pointer py-1 font-vazir block"
onClick={() => setIsMobileMenuOpen(false)}
>
{sol}
</Link>
</li>
))}
</ul>
@ -471,24 +497,22 @@ export default function Header({
</div>
{[
{ id: 'shop', label: getText('nav_products', 'محصولات تخصصی'), action: () => onShopNavigate() },
{ id: 'wiki', label: getText('nav_wiki', 'دانشنامه علمی'), action: () => onNavigate('wiki') },
{ id: 'blog', label: getText('nav_blog', 'مجله سلامت پت'), action: () => onNavigate('blog') },
{ id: 'profile', label: getText('nav_pet_profiles', 'شناسنامه پتها'), action: () => onNavigate({ view: 'profile', subview: 'index' }) }
{ 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) => (
<button
<Link
key={item.id}
onClick={() => {
item.action();
setIsMobileMenuOpen(false);
}}
className={`w-full text-right px-4 py-3 rounded-xl text-sm font-black transition-all font-vazir ${currentView === item.id
href={item.href}
onClick={() => setIsMobileMenuOpen(false)}
className={`w-full block text-right px-4 py-3 rounded-xl text-sm font-black transition-all font-vazir ${currentView === item.id
? 'bg-canina-blue/5 text-canina-blue'
: 'text-medical-gray-600 hover:bg-medical-gray-50'
}`}
>
{item.label}
</button>
</Link>
))}
</nav>
</motion.div>

View File

@ -1,7 +1,7 @@
"use client";
import React from "react";
import { motion } from "motion/react";
import { toPersian } from "../lib/utils";
import { toPersian, cn } from "../lib/utils";
interface HeaderButtonProps {
onClick?: () => void;
@ -10,6 +10,7 @@ interface HeaderButtonProps {
className?: string;
badge?: number;
icon?: React.ReactNode;
layout?: "row" | "col";
}
export default function HeaderButton({
@ -18,9 +19,15 @@ export default function HeaderButton({
variant = "primary",
className = "",
badge,
icon
icon,
layout = "col"
}: HeaderButtonProps) {
const baseStyles = "relative flex flex-col items-center justify-center rounded-xl transition-all duration-300 font-vazir select-none cursor-pointer overflow-visible focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none";
const baseStyles = "relative flex items-center justify-center rounded-2xl transition-all duration-300 font-vazir select-none cursor-pointer overflow-visible focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none";
const layouts = {
col: "flex-col",
row: "flex-row gap-2"
};
const variants = {
primary: "bg-canina-blue text-white hover:bg-medical-gray-900 shadow-lg shadow-canina-blue/20",
@ -39,10 +46,10 @@ export default function HeaderButton({
whileHover={{ y: -2 }}
whileTap={{ scale: 0.95 }}
onClick={onClick}
className={`${baseStyles} ${variants[variant]} ${className}`}
className={cn(baseStyles, layouts[layout], variants[variant], className)}
>
{icon && <div className="mb-0.5">{icon}</div>}
<div className="whitespace-nowrap">{children}</div>
{icon && <div className={cn(layout === "col" ? "mb-0.5" : "flex-shrink-0 flex items-center")}>{icon}</div>}
<div className="whitespace-nowrap flex items-center">{children}</div>
{mounted && badge !== undefined && badge > 0 && (
<motion.div

View File

@ -112,7 +112,7 @@ export default function Hero({ banners = [] }: { banners?: any[] }) {
}}
className="bg-canina-blue text-white px-10 py-5 rounded-full font-bold text-lg hover:bg-canina-blue/90 hover:shadow-2xl focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center gap-2 group font-vazir min-h-[58px] shadow-lg shadow-canina-blue/20 cursor-pointer"
>
{getText('hero_btn_advisor', "دستیار سلامت پت")}
{getText('hero_btn_advisor', "دستیار سلامت کانینا")}
<ChevronLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
</button>
<button
@ -157,6 +157,12 @@ export default function Hero({ banners = [] }: { banners?: any[] }) {
<span className="text-[7px] lg:text-[8px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">{getText('hero_quality_standard', "استاندارد کیفی آلمان")}</span>
</div>
</motion.div>
{/* 3D Action Badge clarifying the 3D mark */}
<div className="absolute bottom-28 right-6 bg-canina-gold text-medical-gray-900 px-4 py-2 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
<span className="text-sm">✨</span>
<span>فرمول ۳کاره (تاثیر سه‌بعدی)</span>
</div>
</div>
</motion.div>
</div>

View File

@ -242,9 +242,16 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
</div>
)}
</div>
<h1 dir="ltr" className="text-2xl md:text-3xl lg:text-4xl font-black text-medical-gray-900 leading-tight mb-4 italic font-sans text-left">
{product.name}
</h1>
<div className="flex flex-col gap-2 mb-4">
<h1 dir="rtl" className="text-2xl md:text-3xl lg:text-4xl font-black text-medical-gray-900 leading-tight font-vazir text-right">
{product.nameFa || product.name}
</h1>
{product.nameEn && (
<h2 dir="ltr" className="text-base md:text-lg text-slate-400 font-bold italic font-sans text-left">
{product.nameEn}
</h2>
)}
</div>
<p className="text-xl text-canina-blue font-bold italic opacity-70 leading-relaxed max-w-2xl font-vazir mb-6 text-right" dir="rtl">
{isMounted && pets.length > 0 ? (
product.optimisticTemplate?.replace("[PetName]", activePet?.name || "").replace("[OnSet]", product.onSetOfAction || "به زودی")
@ -260,6 +267,30 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
</div>
</div>
</div>
{/* ─── Symptoms / Therapeutic Indications Section ─── */}
{fullProduct.symptoms && fullProduct.symptoms.length > 0 && (
<section className="bg-canina-blue/[0.03] border border-canina-blue/10 rounded-[2rem] p-6 md:p-8 space-y-4">
<div className="flex items-center gap-3">
<div className="w-9 h-9 bg-canina-blue/10 text-canina-blue rounded-xl flex items-center justify-center">
<Stethoscope className="w-5 h-5" />
</div>
<h3 className="text-base font-black text-medical-gray-900 font-vazir">علائم و موارد درمانی مرتبط</h3>
</div>
<p className="text-[12px] text-medical-gray-400 font-vazir font-bold">این محصول برای رفع علائم زیر توسط دامپزشکان توصیه می‌شود:</p>
<div className="flex flex-wrap gap-2">
{fullProduct.symptoms.map((symptom: string, idx: number) => (
<a
key={idx}
href={`/shop?symptom=${encodeURIComponent(symptom)}`}
className="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-full text-[11px] font-bold bg-white border border-canina-blue/20 text-canina-blue hover:bg-canina-blue hover:text-white hover:border-canina-blue transition-all shadow-sm cursor-pointer font-vazir whitespace-nowrap"
>
<span className="w-1.5 h-1.5 rounded-full bg-current opacity-60" />
{symptom}
</a>
))}
</div>
</section>
)}
{/* Key Benefits Section */}
{fullProduct.keyBenefits && fullProduct.keyBenefits.length > 0 && (

View File

@ -70,7 +70,7 @@ export default function SearchResultsPage({ query }: { query: string }) {
<div className="relative aspect-square mb-8 bg-medical-gray-50 rounded-[2rem] overflow-hidden p-6 flex items-center justify-center">
<img
src={product.image}
alt={product.name}
alt={product.nameFa || product.name}
className="w-full h-full object-contain group-hover:scale-110 transition-transform duration-500"
referrerPolicy="no-referrer"
/>
@ -92,7 +92,7 @@ export default function SearchResultsPage({ query }: { query: string }) {
<div className="flex-1">
<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.name}</h3>
<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>

View File

@ -59,6 +59,8 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
const [name, setName] = useState("");
const [type, setType] = useState<"سگ" | "گربه">("سگ");
const [breed, setBreed] = useState("");
const [showError, setShowError] = useState(false);
const [shake, setShake] = useState(false);
// Step 2 Data
const [age, setAge] = useState<number>(3);
@ -68,7 +70,17 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
// Step 3 Data
const [medicalConditions, setMedicalConditions] = useState<string[]>([]);
const handleNext = () => setStep(s => s + 1);
const handleNext = () => {
if (step === 1) {
if (!name.trim() || !breed.trim()) {
setShowError(true);
setShake(true);
setTimeout(() => setShake(false), 500);
return;
}
}
setStep(s => s + 1);
};
const handleBack = () => setStep(s => s - 1);
const handleSubmit = () => {
@ -153,9 +165,17 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
onChange={e => {
setName(e.target.value);
if (e.target.value.trim() && breed.trim()) setShowError(false);
}}
placeholder={getText('advisor_pet_name_placeholder', "لوسی، تدی...")}
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-black text-lg font-vazir placeholder-medical-gray-500 text-medical-gray-900"
className={cn(
"w-full bg-white border rounded-2xl py-4 px-6 focus:ring-2 outline-none font-black text-lg font-vazir placeholder-medical-gray-500 text-medical-gray-900 transition-all",
showError && !name.trim()
? "border-red-500 focus:ring-red-500/30"
: "border-medical-gray-300 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40"
)}
/>
</div>
<div className="space-y-2">
@ -163,23 +183,35 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
<input
type="text"
value={breed}
onChange={e => setBreed(e.target.value)}
onChange={e => {
setBreed(e.target.value);
if (name.trim() && e.target.value.trim()) setShowError(false);
}}
placeholder={getText('advisor_pet_breed_placeholder', "ژرمن، پرشین...")}
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-bold font-vazir placeholder-medical-gray-500 text-medical-gray-900"
className={cn(
"w-full bg-white border rounded-2xl py-4 px-6 focus:ring-2 outline-none font-bold font-vazir placeholder-medical-gray-500 text-medical-gray-900 transition-all",
showError && !breed.trim()
? "border-red-500 focus:ring-red-500/30"
: "border-medical-gray-300 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40"
)}
/>
</div>
</div>
<button
<motion.button
onClick={handleNext}
disabled={!name || !breed}
className="w-full py-6 rounded-3xl font-black text-lg flex items-center justify-center gap-3 font-vazir min-h-[48px] transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:shadow-none disabled:border-2 disabled:border-gray-400 bg-medical-gray-900 text-white hover:bg-canina-blue shadow-xl shadow-medical-gray-900/10"
animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }}
transition={{ duration: 0.4 }}
className={cn(
"w-full py-6 rounded-3xl font-black text-lg flex items-center justify-center gap-3 font-vazir min-h-[48px] transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none bg-medical-gray-900 text-white hover:bg-canina-blue shadow-xl shadow-medical-gray-900/10",
(!name.trim() || !breed.trim()) ? "opacity-60 cursor-pointer" : ""
)}
>
{getText('advisor_submit_btn', "ثبت هویت و ادامه")}
<ChevronLeft className="w-5 h-5" />
</button>
{(!name || !breed) && (
<p className="text-center text-xs font-black text-amber-600 mt-2 font-vazir animate-pulse">
</motion.button>
{showError && (!name.trim() || !breed.trim()) && (
<p className="text-center text-xs font-black text-red-500 mt-2 font-vazir animate-pulse">
{getText('advisor_validation_warning', "⚠️ لطفاً ابتدا نام و نژاد پت را در بالا وارد کنید.")}
</p>
)}

View File

@ -2,6 +2,7 @@
import React, { useState } from "react";
import { Play, PlayCircle, Star, ShieldCheck, X } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSettingsStore } from "../lib/store/settingsStore";
@ -29,7 +30,7 @@ const VIDEOS = [
title: "تغذیه هوشمند توله‌سگ‌ها",
doctor: "دکتر هلگا فیشر",
duration: "۳:۱۰",
thumbnail: "https://images.unsplash.com/photo-1583337130417-3346a1be7dee?auto=format&fit=crop&q=80&w=400",
thumbnail: "/assets/images/regenerated_image_1779109861747.png",
videoUrl: "https://www.w3schools.com/html/mov_bbb.mp4",
description: "دوز مصرفی مکمل‌های کلسیم در نژادهای بزرگ برای جلوگیری از بدشکلی‌های استخوانی."
}
@ -54,16 +55,16 @@ export default function VetGallery({ testimonials = [] }: { testimonials?: any[]
{getText('videos_title', "مشاوره ویدئویی")} <span className="text-canina-blue underline decoration-4 decoration-canina-blue/20 underline-offset-8">{getText('videos_title_highlight', "دوست دامپزشک پت شما")}</span>
</h2>
<p className="text-lg text-medical-gray-500 font-medium mt-6">
{getText('videos_desc', "متخصصین آلمانی کانینا مستقیماً با شما صحبت می‌کنند. نحوه مصرف، مزایای رقابتی و دانش تخصصی پشت هر محصول.")}
</p>
{getText('videos_desc', "تیم علمی و فرموله‌کنندگان کمپانی کانینا در آلمان مستقیماً با شما صحبت می‌کنند؛ آشنایی با نحوه مصرف، مزایای بالینی و دانش پزشکی پشت هر محصول.")}
</p>
</div>
<button
onClick={() => router.push('/videos')}
<Link
href="/videos"
className="inline-flex items-center gap-2 text-canina-blue font-bold text-sm hover:translate-x-1 transition-transform rtl:hover:-translate-x-1"
>
{getText('videos_view_all', "مشاهده همه ویدئوها")}
<Play className="w-4 h-4" />
</button>
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

View File

@ -28,7 +28,7 @@ const ALL_VIDEOS = [
title: "تغذیه هوشمند توله‌سگ‌های نژاد بزرگ",
doctor: "دکتر هلگا فیشر",
duration: "۳:۱۰",
thumbnail: "https://images.unsplash.com/photo-1583337130417-3346a1be7dee?auto=format&fit=crop&q=80&w=600",
thumbnail: "/assets/images/regenerated_image_1779109861747.png",
videoUrl: "https://www.w3schools.com/html/mov_bbb.mp4",
description: "دوز مصرفی مکمل‌های کلسیم در نژادهای بزرگ برای جلوگیری از بدشکلی‌های استخوانی."
},

View File

@ -30,6 +30,8 @@ export interface Product {
slug?: string;
artNo: string;
name: string;
nameFa?: string;
nameEn?: string;
scientificTagline: string;
description: string;
shortDescription?: string;
@ -119,8 +121,8 @@ export const PRODUCTS: Product[] = [
],
category: "مفاصل و استخوان",
categorySlug: "joints",
price: "۸,۴۸۰,۰۰۰ تومان",
priceValue: 8480000,
price: "۴,۲۴۰,۰۰۰ تومان",
priceValue: 4240000,
unit: "100g (60 Tablets)",
packageSize: 100,
main_ingredients: ["تری کلسیم فسفات 30%", "هیدروکسی آپاتیت", "پودر صدف لب‌سبز 10%", "مخمر آبجو", "جلبک دریایی", "ویتامین C", "ویتامین E", "سلنیوم", "متیونین"],
@ -1131,14 +1133,14 @@ export const PRODUCTS: Product[] = [
{
id: "novagard-augenpflege",
artNo: "NG0013",
name: "Canina NovaGard Green Augenpflege",
name: "Canina NovaGard Green Augenpflege (مراقبت چشم)",
scientificTagline: "قطره چشم ضدالتهاب با نقره کلوئیدی و هیالورونات",
description: "قطره چشمی استریل با هیالورونات سدیم (مرطوب‌کننده و محافظ قرنیه) و نقره کلوئیدی (ضدالتهاب و ضدعفونی‌کننده). ایده‌آل برای تمیز کردن چشم‌های گرفته و گریان.",
shortDescription: "قطره چشم ضدالتهاب برای تمیز کردن چشم‌های گریان و گرفته.",
category: "مراقبت‌های ویژه (پوست، دندان و چشم)",
categorySlug: "special-care",
price: "۶,۳۶۰,۰۰۰ تومان",
priceValue: 6360000,
price: "۳,۱۸۰,۰۰۰ تومان",
priceValue: 3180000,
unit: "10 ml",
packageSize: 10,
main_ingredients: ["هیالورونات سدیم", "نقره کلوئیدی"],
@ -1162,14 +1164,14 @@ export const PRODUCTS: Product[] = [
{
id: "novagard-pfotenpflege",
artNo: "NG0016",
name: "Canina NovaGard Green Pfotenpflege",
name: "Canina NovaGard Green Pfotenpflege (مراقبت پنجه)",
scientificTagline: "کرم ۱۰۰٪ طبیعی برای ترمیم پنجه‌های ترک خورده",
description: "کرم ۱۰۰٪ طبیعی با روغن پالپ سنجد دریایی (Sea Buckthorn)، روغن کالاندولا و پانتنول (پروویتامین B5) برای تسکین پوست خارش‌دار، التیام زخم‌ها و خراشیدگی پنجه در تابستان و زمستان.",
shortDescription: "کرم طبیعی پنجه با سنجد دریایی و کالاندولا برای زمستان و تابستان.",
category: "مراقبت‌های ویژه (پوست، دندان و چشم)",
categorySlug: "special-care",
price: "۵,۰۰۰,۰۰۰ تومان",
priceValue: 5000000,
price: "۲,۵۰۰,۰۰۰ تومان",
priceValue: 2500000,
unit: "50 ml",
packageSize: 50,
main_ingredients: ["روغن پالپ سنجد دریایی", "روغن کالاندولا", "پانتنول", "وازلین"],
@ -1199,8 +1201,8 @@ export const PRODUCTS: Product[] = [
shortDescription: "شیر خشک مخصوص بچه‌گربه‌های بی‌مادر با تورین بالا از بدو تولد.",
category: "تغذیه تخصصی",
categorySlug: "nutrition",
price: "۴,۶۳۰,۰۰۰ تومان",
priceValue: 4630000,
price: "۲,۳۱۵,۰۰۰ تومان",
priceValue: 2315000,
unit: "150 g",
packageSize: 150,
main_ingredients: ["لاکتوز", "پودر آب پنیر", "چربی نارگیل", "روغن ماهی", "تورین"],

View File

@ -129,5 +129,17 @@ export const SCIENTIFIC_TERMS: Record<string, ScientificTerm> = {
term: "تورین (Taurine)",
definition: "آمینو اسید بسیار ضروری برای گربه‌ها جهت جلوگیری از دژنراسیون شبکیه (نابینایی) و نارسایی قلبی.",
wikiId: "taurin"
},
"گرید دارویی": {
term: "گرید دارویی (Pharmaceutical Grade)",
definition: "بالاترین سطح خلوص مواد اولیه؛ تهیه شده با استانداردهای دارویی انسان و کاملاً بی‌خطر در صورت لیسیده شدن توسط حیوان."
},
"IFS": {
term: "استاندارد بین‌المللی IFS",
definition: "سخت‌گیرانه‌ترین استاندارد ایمنی و کیفیت محصولات دارویی و غذایی در اروپا."
},
"HACCP": {
term: "سیستم کنترل بحران HACCP",
definition: "تضمین‌کننده عدم وجود هرگونه آلودگی فیزیکی، شیمیایی یا میکروبی در زنجیره تولید محصولات."
}
};

View File

@ -25,7 +25,11 @@ export class ProductService {
private mapBackendToFrontend(data: any): Product {
// Find the local static product to inherit functions like calculateDosage
const local = PRODUCTS.find(p => p.artNo === data.artNo);
const local = PRODUCTS.find(p =>
p.artNo === data.artNo ||
p.id === data.productGroup ||
(data.slug && data.slug.includes(p.id))
);
const safeParse = (val: any, fallback: any) => {
if (!val) return fallback;
@ -38,10 +42,12 @@ export class ProductService {
return {
id: data.id,
artNo: data.artNo,
name: data.name,
name: data.nameFa || data.name || '',
nameFa: data.nameFa || data.name || '',
nameEn: data.nameEn || '',
scientificTagline: data.scientificTagline,
description: data.description,
shortDescription: data.shortDescription,
shortDescription: data.shortDescription || '',
price: data.priceDisplay || `${data.priceValue.toLocaleString('fa-IR')} تومان`,
priceValue: Number(data.priceValue),
category: data.category?.name || data.category || CATEGORY_SLUG_TO_NAME[data.categorySlug] || data.categorySlug || '',
@ -56,7 +62,10 @@ export class ProductService {
onSetOfAction: data.onSetOfAction,
optimisticTemplate: data.optimisticTemplate,
feedingAdvice: data.dosageLogic || data.feedingAdvice || '',
image: data.imageUrl?.startsWith('http') ? data.imageUrl : data.imageUrl ? `/api${data.imageUrl}` : data.image,
slug: data.slug,
image: (data.imageUrl && data.imageUrl.startsWith('http'))
? data.imageUrl
: (local?.image || (data.imageUrl ? `/api${data.imageUrl}` : data.image || '')),
main_ingredients: data.ingredientList?.map((i: any) => i.ingredient) || data.ingredients?.split(/[،,-]/).map((s: string) => s.trim()).filter(Boolean) || [],
symptoms: data.symptoms?.map((s: any) => s.symptom || s) || [],
@ -77,6 +86,7 @@ export class ProductService {
category?: string;
petType?: PetType | "all";
query?: string;
symptom?: string;
page?: number;
limit?: number;
}): Promise<{ data: Product[]; meta: { total: number; page: number; lastPage: number; limit: number } }> {
@ -84,6 +94,7 @@ export class ProductService {
if (filters?.category && filters.category !== "all") params.append('category', filters.category);
if (filters?.petType && filters.petType !== "all") params.append('petType', filters.petType);
if (filters?.query) params.append('search', filters.query);
if (filters?.symptom) params.append('symptom', filters.symptom);
params.append('page', String(filters?.page || 1));
params.append('limit', String(filters?.limit || 12));
@ -121,13 +132,62 @@ export class ProductService {
public async getFeaturedProducts(): Promise<Product[]> {
try {
const response = await api.get('/products');
return response.data.data.slice(0, 4).map((item: any) => this.mapBackendToFrontend(item));
const response = await api.get('/products?limit=30');
const all = response.data.data.map((item: any) => this.mapBackendToFrontend(item));
const featured: Product[] = [];
const categoriesSeen = new Set<string>();
for (const p of all) {
if (p.category && !categoriesSeen.has(p.category)) {
featured.push(p);
categoriesSeen.add(p.category);
}
if (featured.length === 4) break;
}
if (featured.length < 4) {
for (const p of all) {
if (!featured.some(f => f.id === p.id)) {
featured.push(p);
}
if (featured.length === 4) break;
}
}
return featured;
} catch (error) {
console.error("[ProductService] Failed to fetch featured products:", error);
return [];
}
}
public async getActiveFilters(): Promise<{
categories: { id: string; name: string; slug: string }[];
symptoms: string[];
petTypes: string[];
}> {
try {
const response = await api.get('/products/filters');
return response.data;
} catch (error) {
console.error("[ProductService] Failed to fetch active filters:", error);
return { categories: [], symptoms: [], petTypes: [] };
}
}
public async getNavigationFilters(): Promise<{
id: string;
name: string;
slug: string;
symptoms: string[];
}[]> {
try {
const response = await api.get('/products/navigation-filters');
return response.data;
} catch (error) {
console.error("[ProductService] Failed to fetch navigation filters:", error);
return [];
}
}
}
export const productService = ProductService.getInstance();