Compare commits
No commits in common. "81a2837c04b820e93355831133bf035e1697efa1" and "b6453d05b9b96118f72b068806e6c28904a81496" have entirely different histories.
81a2837c04
...
b6453d05b9
@ -177,20 +177,76 @@
|
||||
|
||||
## 📍 بخش ۲۴: آیکون راهنما و Tooltip کدهای تخفیف (Discount Code Field Info Tooltip)
|
||||
- [x] **TASK-24.1 [Admin Panel & Frontend UI]:** اضافه کردن آیکون راهنما (Info Tooltip) به فیلدهای کد تخفیف با توضیحات شفاف درباره کارکرد فیلد و تغییرات ناشی از ویرایش آن.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۲۵: بازبینی، اصلاح خطاها و توسعه قابلیتهای درخواستی جدید (Batch Tasks 25.1 - 25.14)
|
||||
- [ ] **TASK-25.1 [Frontend Header Dropdown]:** بازگرداندن Dropdown سریع اطلاعات کاربری و لیست پتها در هدر اصلی فرانتاند (Header Profile Dropdown with Pets List).
|
||||
- [ ] **TASK-25.2 [Admin Panel Topbar]:** اصلاح کارکرد دکمه «خروج از حساب» در Dropdown پروفایل ادمین پنل (Fix Logout Handler in Admin Topbar).
|
||||
- [ ] **TASK-25.3 [Admin Panel Users Page]:** تکمیل مدیریت کامل کاربران (مشاهده تمامی فیلدها، ویرایش اطلاعات کاربر، تغییر نقش، مسدودی و...).
|
||||
- [ ] **TASK-25.4 [Health Calculator UI]:** اصلاح فیلدهای گام دوم پایش فیزیکی در چکآپ سلامت جهت امکان تایپ مستقیم عددی علاوه بر دکمههای کم و زیاد.
|
||||
- [ ] **TASK-25.5 [Health Supplement Tracking Logic]:** پیادهسازی کامل منطق و الگوریتم پایش موجودی مکملها براساس تاریخ مصرف و سفارشات واقعی پت.
|
||||
- [ ] **TASK-25.6 [Backend Exception & Error Handling System]:** اصلاح منطق پاسخدهی خطاهای بهینهشده به تفکیک پارامترها و جلوگیری از نمایش پیامهای کلی غیرمنتظره و توستهای تکراری.
|
||||
- [ ] **TASK-25.7 [Wallet Checkout Error Handling]:** اصلاح هندلینگ خطای ورودی هنگام پرداخت با کیف پول و نمایش دقیق فیلد یا دلیل خطای پرداخت.
|
||||
- [ ] **TASK-25.8 [Address Add & Refresh Bug Fix]:** رفع مشکل خطای اضافه کردن آدرس، جلوگیری از نمایش توستهای تکراری و بروزرسانی خودکار لیست آدرسهای کاربر.
|
||||
- [ ] **TASK-25.9 [Shipping Fee Calculation Fix]:** برطرف کردن خطای محاسبه هزینه ثابت ارسال در فاکتور و فاز تسویهحساب براساس تنظیمات پنل ادمین.
|
||||
- [ ] **TASK-25.10 [Admin Panel UI Cleanup]:** پاکسازی پسوند کد تپکها یا کدهای درون پرانتز (مانند TASK-x) از عناوین صفحات و منوهای پنل ادمین.
|
||||
- [ ] **TASK-25.11 [Maintenance Mode Protection Guard]:** اصلاح و فعالسازی عملکرد گارد «حالت تعمیرات» (Maintenance Mode) در فرانتاند برای مسدودسازی دسترسی عمومی.
|
||||
- [ ] **TASK-25.12 [Flexible Catalog-Only Mode Settings]:** توسعه تنظیمات چندحالته «حالت کاتالوگ» (نمایش/عدم نمایش قیمت، فعال/غیرفعالسازی سبد خرید و فاکتور، تبدیل به پیشخرید).
|
||||
- [ ] **TASK-25.13 [Product Pre-order System]:** ایجاد امکان پیشخرید کالا در پنل ادمین (رایگان یا با بیعانه) و ثبت نهایی بعد از موجود شدن.
|
||||
- [ ] **TASK-25.14 [Stock Alert Queue & SMS Notification]:** ایجاد قابلیت «موجود شد اطلاع بده» برای کالاهای ناموجود، صف خودکار اطلاعرسانی و ارسال پیامک به محض شارژ موجودی.
|
||||
## 📍 بخش ۲۵: بازگرداندن Dropdown منوی پروفایل کاربر در هدر (User Profile Pet Dropdown Header)
|
||||
- [ ] **TASK-25.1 [Frontend Site UI]:** بازگرداندن منوی کشویی پروفایل کاربر در هدر سایت شامل نمایش لیست پتهای ثبتشده، دسترسی سریع به مدیریت پتها و خروج از حساب.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۲۶: اصلاح خروج از حساب کاربری در پنل ادمین (Admin Panel Logout & Profile Dropdown)
|
||||
- [ ] **TASK-26.1 [Admin Panel UI]:** برطرف کردن خطای منوی کشویی پروفایل ادمین در پنل مدیریت و فعالسازی کامل دکمه «خروج از حساب».
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۲۷: مشاهده و ویرایش کامل اطلاعات کاربر در پنل ادمین (Admin Full User Profile View & Edit)
|
||||
- [ ] **TASK-27.1 [Admin Panel & Backend]:** فراهم کردن امکان مشاهده کلیه اطلاعات کاربر (مشخصات، شماره تماس، آدرسها، پتها) و ویرایش کامل آنها در پنل مدیریت بهجای تغییر صرف نقش.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۲۸: ورود تایپی و مستقیم اعداد در پایش فیزیکی (Direct Numeric Input in Physical Monitoring)
|
||||
- [ ] **TASK-28.1 [Frontend Site UI]:** اضافه کردن قابلیت ورود تایپی و مستقیم عدد به تمام اینپوتهای گام دوم پایش فیزیکی در کنار دکمههای + و -.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۲۹: منطق واقعی و کاربردی پایش هوشمند موجودی مکملها (Smart Supplement Consumption Tracking Logic)
|
||||
- [ ] **TASK-29.1 [Frontend & Backend]:** پیادهسازی منطق واقعی محاسبه میزان مصرف روزانه براساس سفارشات ثبتشده، دوز مصرفی و تعداد باقیمانده مکمل کاربر و امکان ثبت دستی مکمل جهت پایش.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۰: شفافسازی و هندلینگ استاندارد خطاهای پردازش پرداخت (Detailed Checkout Payment Error Toast Fix)
|
||||
- [ ] **TASK-30.1 [Frontend Site]:** رفع دوبار نمایش داده شدن Toast خطا در زمان ثبت سفارش، دریافت دقیق پیام خطا از پاسخ backend و نمایش پیام شفاف و کاربردی.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۱: اصلاح عیبیابی پرداخت با کیف پول (Wallet Payment Validation & Error Handling)
|
||||
- [ ] **TASK-31.1 [Backend & Frontend]:** بررسی دقیق پارامترهای پردازش پرداخت کیف پول و نمایش دقیق فیلد یا علت نامعتبر بودن درخواست به کاربر.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۲: اصلاح فرآیند ذخیره و نمایش آدرس جدید (Address Creation Double Toast & List Sync Fix)
|
||||
- [ ] **TASK-32.1 [Frontend Site]:** برطرف کردن خطای `property addresses should not exist` در ثبت آدرس، حذف Toastهای تکراری و بهروزرسانی آنی لیست آدرسهای منتخب کاربر در تسویهحساب.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۳: اعمال هزینه واقعی ارسال سفارشی و پیک در تسویهحساب (Shipping Cost Admin Sync)
|
||||
- [ ] **TASK-33.1 [Backend & Frontend]:** خواندن هزینه ارسال تنظیمشده در پنل ادمین (مثلاً ۱۰۰,۰۰۰ تومان) و محاسبه دقیق آن در فاکتور نهایی تسویهحساب بهجای رایگان فرض کردن آن.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۴: پاکسازی عبارتهای فنی (TASK-X) از عنوانهای پنل ادمین (Admin Titles Clean-up)
|
||||
- [ ] **TASK-34.1 [Admin Panel UI]:** حذف کدهای شناسه تاسک (`TASK-2.15` و غیره) از تمام عنوانها و صفحات پنل مدیریت ادمین.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۵: اصلاح عملکرد واقعی حالت تعمیرات (Real Maintenance Mode Enforcement)
|
||||
- [ ] **TASK-35.1 [Frontend Site]:** اصلاح بررسی حالت تعمیرات (Maintenance Mode) در `ClientLayout` بهطوریکه حتی کاربران لاگینشده معمولی نیز صفحه Coming Soon را مشاهده کنند و صرفاً ادمین مجاز به عبور باشد.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۶: تفکیک دقیق گزینههای حالت کاتالوگ و پیشخرید (Granular Catalog Mode Sub-options & Pre-order Button)
|
||||
- [ ] **TASK-36.1 [Admin Panel & Frontend]:** پیادهسازی زیرگزینههای کاتالوگمود (غیرفعالسازی ثبت سفارش، جایگزینی دکمه پیشخرید بهجای خرید، غیرفعالسازی قیمت و سبد) در تنظیمات ادمین و اعمال آن در فرانتاند.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۷: سیستم پیشخرید محصولات رایگان یا بیعانهای (Pre-order System with SMS Notification)
|
||||
- [ ] **TASK-37.1 [DB, Backend & Frontend]:** ساخت سیستم پیشخرید محصولات Coming Soon (رایگان یا با بیعانه) و ارسال پیامک خودکار به کاربر هنگام موجود شدن و قیمتگذاری کالا جهت تکمیل سفارش.
|
||||
|
||||
---
|
||||
|
||||
## 📍 بخش ۳۸: صف خودکار «موجود شد مطلعم کن» با ارسال پیامک (Out-of-Stock Notification Queue & SMS)
|
||||
- [ ] **TASK-38.1 [DB, Backend & Frontend]:** ایجاد دکمه «موجود شد خبرم کن» برای کالاهای ناموجود، ثبت کاربر در صف (Queue) و ارسال خودکار پیامک هنگام افزایش موجودی کالا به بالای ۰.
|
||||
|
||||
|
||||
|
||||
|
||||
@ -190,10 +190,7 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
||||
</button>
|
||||
<div className="border-t border-gray-100 my-1"></div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowUserMenu(false);
|
||||
handleLogout();
|
||||
}}
|
||||
onClick={handleLogout}
|
||||
className="w-full text-right px-3 py-2 text-xs font-bold text-red-600 hover:bg-red-50 rounded-xl transition-colors flex items-center justify-between"
|
||||
>
|
||||
<span>خروج از حساب</span>
|
||||
|
||||
@ -11,10 +11,6 @@ export default function Settings() {
|
||||
B2B_DISCOUNT_PERCENT: '',
|
||||
MAINTENANCE_MODE: 'false',
|
||||
CATALOG_ONLY_MODE: 'false',
|
||||
CATALOG_SHOW_PRICES: 'true',
|
||||
CATALOG_ALLOW_CART: 'false',
|
||||
CATALOG_ALLOW_CHECKOUT: 'false',
|
||||
CATALOG_PREORDER_BTN: 'false',
|
||||
CHARITY_ROUND_STEP: '10000',
|
||||
PAY_GATEWAY_CARD_ENABLE: 'true',
|
||||
PAY_GATEWAY_WALLET_ENABLE: 'true',
|
||||
@ -45,10 +41,6 @@ export default function Settings() {
|
||||
B2B_DISCOUNT_PERCENT: response.data.data.B2B_DISCOUNT_PERCENT || '0',
|
||||
MAINTENANCE_MODE: response.data.data.MAINTENANCE_MODE || 'false',
|
||||
CATALOG_ONLY_MODE: response.data.data.CATALOG_ONLY_MODE || 'false',
|
||||
CATALOG_SHOW_PRICES: response.data.data.CATALOG_SHOW_PRICES || 'true',
|
||||
CATALOG_ALLOW_CART: response.data.data.CATALOG_ALLOW_CART || 'false',
|
||||
CATALOG_ALLOW_CHECKOUT: response.data.data.CATALOG_ALLOW_CHECKOUT || 'false',
|
||||
CATALOG_PREORDER_BTN: response.data.data.CATALOG_PREORDER_BTN || 'false',
|
||||
CHARITY_ROUND_STEP: response.data.data.CHARITY_ROUND_STEP || '10000',
|
||||
PAY_GATEWAY_CARD_ENABLE: response.data.data.PAY_GATEWAY_CARD_ENABLE || 'true',
|
||||
PAY_GATEWAY_WALLET_ENABLE: response.data.data.PAY_GATEWAY_WALLET_ENABLE || 'true',
|
||||
@ -328,11 +320,11 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Brand Identity & Theme Settings */}
|
||||
{/* Brand Identity & Theme Settings (TASK-2.15) */}
|
||||
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6 lg:col-span-2">
|
||||
<h3 className="text-lg font-bold text-gray-900 border-b border-gray-100 pb-4 flex items-center gap-2">
|
||||
<SettingsIcon className="w-5 h-5 text-purple-600" />
|
||||
مدیریت هویت برند، نمادها و ظاهر سایت
|
||||
مدیریت هویت برند، نمادها و ظاهر سایت (TASK-2.15)
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@ -394,11 +386,11 @@ export default function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* System Settings */}
|
||||
{/* System Settings (TASK-2.14) */}
|
||||
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6 lg:col-span-2">
|
||||
<h3 className="text-lg font-bold text-gray-900 border-b border-gray-100 pb-4 flex items-center gap-2">
|
||||
<ShieldAlert className="w-5 h-5 text-orange-500" />
|
||||
وضعیت سیستم و کنترل حالت کاتالوگ
|
||||
وضعیت سیستم و کنترل حالت کاتالوگ (TASK-2.14)
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
@ -421,69 +413,23 @@ export default function Settings() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 rounded-xl border border-purple-100 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="w-6 h-6 text-purple-600" />
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 text-sm">حالت کاتالوگ بدون خرید (Catalog Only Mode)</h4>
|
||||
<p className="text-xs text-gray-600 mt-0.5">با فعالسازی این گزینه، فروش مستقیم کالا غیرفعال شده و سایت به کاتالوگ هوشمند آنلاین تبدیل میشود.</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 bg-purple-50 rounded-xl border border-purple-100">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="w-6 h-6 text-purple-600" />
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900 text-sm">حالت کاتالوگ بدون خرید (Catalog Only Mode)</h4>
|
||||
<p className="text-xs text-gray-600 mt-0.5">با فعالسازی این گزینه، دکمههای افزودن به سبد خرید غیرفعال شده و سایت به کاتالوگ آنلاین تبدیل میشود.</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={settings.CATALOG_ONLY_MODE === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_ONLY_MODE: e.target.checked ? 'true' : 'false' })}
|
||||
/>
|
||||
<div className="w-14 h-7 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-purple-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{settings.CATALOG_ONLY_MODE === 'true' && (
|
||||
<div className="pt-3 border-t border-purple-200/60 grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
||||
<label className="flex items-center gap-2 font-bold text-gray-800 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.CATALOG_SHOW_PRICES === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_SHOW_PRICES: e.target.checked ? 'true' : 'false' })}
|
||||
className="rounded text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<span>نمایش قیمت محصولات در حالت کاتالوگ</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2 font-bold text-gray-800 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.CATALOG_ALLOW_CART === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_ALLOW_CART: e.target.checked ? 'true' : 'false' })}
|
||||
className="rounded text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<span>امکان افزودن به سبد خرید</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2 font-bold text-gray-800 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.CATALOG_ALLOW_CHECKOUT === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_ALLOW_CHECKOUT: e.target.checked ? 'true' : 'false' })}
|
||||
className="rounded text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<span>امکان ورود به صفحه تسویهحساب (ثبت فاکتور)</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2 font-bold text-gray-800 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.CATALOG_PREORDER_BTN === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_PREORDER_BTN: e.target.checked ? 'true' : 'false' })}
|
||||
className="rounded text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<span>نمایش دکمه «ثبت پیشخرید» به جای ثبت سفارش</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={settings.CATALOG_ONLY_MODE === 'true'}
|
||||
onChange={(e) => setSettings({ ...settings, CATALOG_ONLY_MODE: e.target.checked ? 'true' : 'false' })}
|
||||
/>
|
||||
<div className="w-14 h-7 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-purple-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,7 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
|
||||
}, [fetchProfile, fetchSettings]);
|
||||
|
||||
// Check Maintenance Mode (Admin / Partner bypass)
|
||||
const isMaintenanceMode = getText('MAINTENANCE_MODE', 'false') === 'true' || getText('maintenance_mode', 'false') === 'true';
|
||||
const isMaintenanceMode = getText('maintenance_mode', 'false') === 'true';
|
||||
const isAdmin = role === 'User_Partner' || (typeof window !== 'undefined' && Boolean(localStorage.getItem('adminToken')));
|
||||
|
||||
if (isMaintenanceMode && !isAdmin) {
|
||||
|
||||
@ -76,8 +76,6 @@ export default function CheckoutPage() {
|
||||
}, [profile, isLoggedIn]);
|
||||
|
||||
const roundStep = Number(useSettingsStore((state) => state.getText('CHARITY_ROUND_STEP', '10000'))) || 10000;
|
||||
const shippingFeeSetting = useSettingsStore((state) => state.getText('shipping_fee', '0'));
|
||||
const shippingFee = Number(shippingFeeSetting) || 0;
|
||||
const subtotal = getSubtotal();
|
||||
const remainder = subtotal % roundStep;
|
||||
const roundUpDiff = remainder === 0 ? roundStep : roundStep - remainder;
|
||||
@ -93,7 +91,7 @@ export default function CheckoutPage() {
|
||||
};
|
||||
|
||||
const handleFinalize = async () => {
|
||||
const totalAmount = getTotal() + shippingFee;
|
||||
const totalAmount = getTotal();
|
||||
setLoading(true);
|
||||
const activePet = getActivePet();
|
||||
|
||||
@ -620,16 +618,12 @@ export default function CheckoutPage() {
|
||||
)}
|
||||
<div className="flex justify-between text-sm font-bold text-white/80">
|
||||
<span>هزینه ارسال</span>
|
||||
{shippingFee > 0 ? (
|
||||
<span className="font-vazir font-bold text-white">{toPersian(shippingFee.toLocaleString())} تومان</span>
|
||||
) : (
|
||||
<span className="text-green-400 uppercase tracking-widest text-[10px] font-black">رایگان</span>
|
||||
)}
|
||||
<span className="text-green-400 uppercase tracking-widest text-[10px] font-black">رایگان</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-end pt-4">
|
||||
<span className="text-xl font-black italic text-white">مبلغ نهایی</span>
|
||||
<div className="text-left">
|
||||
<p className="text-3xl font-black text-canina-gold leading-none font-vazir">{toPersian((getTotal() + shippingFee).toLocaleString())}</p>
|
||||
<p className="text-3xl font-black text-canina-gold leading-none font-vazir">{toPersian(getTotal().toLocaleString())}</p>
|
||||
<p className="text-[10px] text-white/50 font-bold mt-1">تومان</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -734,17 +728,13 @@ export default function CheckoutPage() {
|
||||
<AddressModal
|
||||
isOpen={showAddressModal}
|
||||
onClose={() => setShowAddressModal(false)}
|
||||
onSave={async (newAddr) => {
|
||||
try {
|
||||
await useUserStore.getState().addAddress(newAddr);
|
||||
const updatedProfile = useUserStore.getState().profile;
|
||||
if (updatedProfile.addresses && updatedProfile.addresses.length > 0) {
|
||||
setSelectedAddressId(updatedProfile.addresses[0].id);
|
||||
}
|
||||
setShowAddressModal(false);
|
||||
} catch (e) {
|
||||
console.error("Failed to add address", e);
|
||||
}
|
||||
onSave={(newAddr) => {
|
||||
const currentAddresses = profile.addresses || [];
|
||||
const updatedAddresses = [newAddr, ...currentAddresses];
|
||||
updateProfile({ addresses: updatedAddresses });
|
||||
setSelectedAddressId(newAddr.id);
|
||||
setShowAddressModal(false);
|
||||
toast.success('آدرس جدید اضافه شد و به عنوان آدرس تحویل انتخاب گردید.');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -43,8 +43,6 @@ export default function Header({
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
|
||||
const [isPrescriptionModalOpen, setIsPrescriptionModalOpen] = useState(false);
|
||||
const [isPetSwitcherOpen, setIsPetSwitcherOpen] = useState(false);
|
||||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
|
||||
const userProfileRef = useRef<HTMLDivElement>(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
@ -116,9 +114,6 @@ export default function Header({
|
||||
if (petMenuRef.current && !petMenuRef.current.contains(event.target as Node)) {
|
||||
setIsPetSwitcherOpen(false);
|
||||
}
|
||||
if (userProfileRef.current && !userProfileRef.current.contains(event.target as Node)) {
|
||||
setIsProfileMenuOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
@ -198,100 +193,16 @@ export default function Header({
|
||||
<span>ثبت نسخه دامپزشک</span>
|
||||
</button>
|
||||
|
||||
{/* User Account Button with Dropdown & Pets List */}
|
||||
{/* User Account Button */}
|
||||
{isLoggedIn ? (
|
||||
<div className="relative" ref={userProfileRef}>
|
||||
<button
|
||||
onClick={() => setIsProfileMenuOpen(!isProfileMenuOpen)}
|
||||
className="flex items-center gap-1.5 p-2 sm:px-3 sm:py-2 bg-medical-gray-50 hover:bg-medical-gray-100 border border-medical-gray-200 rounded-xl text-xs font-black text-medical-gray-900 transition-all cursor-pointer"
|
||||
>
|
||||
<User className="w-4 h-4 text-canina-blue" />
|
||||
<span className="hidden sm:inline">{profile.firstName || 'حساب کاربری'}</span>
|
||||
<ChevronDown className={cn("w-3.5 h-3.5 text-medical-gray-500 transition-transform duration-200", isProfileMenuOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isProfileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8, scale: 0.96 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 8, scale: 0.96 }}
|
||||
className="absolute left-0 mt-2 w-64 bg-white rounded-2xl shadow-xl border border-medical-gray-100 overflow-hidden z-50 p-2 text-xs font-vazir"
|
||||
>
|
||||
{/* User info header */}
|
||||
<div className="px-3 py-2.5 bg-medical-gray-50 rounded-xl mb-1">
|
||||
<p className="font-black text-medical-gray-900 truncate">
|
||||
{profile.firstName ? `${profile.firstName} ${profile.lastName || ''}` : 'کاربر عزیز'}
|
||||
</p>
|
||||
<p className="text-[10px] text-medical-gray-500 font-mono mt-0.5 truncate">{profile.mobile || profile.email}</p>
|
||||
</div>
|
||||
|
||||
{/* Pets List Section */}
|
||||
<div className="py-1">
|
||||
<div className="flex items-center justify-between px-3 py-1.5 text-[11px] font-black text-medical-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<HeartPulse className="w-3.5 h-3.5 text-canina-blue" />
|
||||
پتهای من ({pets.length})
|
||||
</span>
|
||||
<Link href="/dashboard" onClick={() => setIsProfileMenuOpen(false)} className="text-canina-blue hover:underline text-[10px]">
|
||||
مدیریت
|
||||
</Link>
|
||||
</div>
|
||||
{pets.length > 0 ? (
|
||||
<div className="space-y-1 max-h-32 overflow-y-auto">
|
||||
{pets.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
onClick={() => {
|
||||
setActivePet(p.id);
|
||||
setIsProfileMenuOpen(false);
|
||||
toast.success(`پت فعال به ${p.name} تغییر یافت`);
|
||||
}}
|
||||
className={cn(
|
||||
"w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-right transition-colors",
|
||||
p.id === activePetId ? "bg-canina-blue/10 text-canina-blue font-bold" : "hover:bg-medical-gray-50 text-medical-gray-700"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 truncate">
|
||||
<span className="text-sm">{(p.type === 'سگ' || p.type === ('dog' as any)) ? '🐶' : '🐱'}</span>
|
||||
<span className="truncate">{p.name}</span>
|
||||
</div>
|
||||
{p.id === activePetId && <Check className="w-3.5 h-3.5 shrink-0" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="px-3 py-2 text-[10px] text-medical-gray-400 text-center">پتی ثبت نشده است</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-medical-gray-100 my-1" />
|
||||
|
||||
{/* Direct Navigation Links */}
|
||||
<Link
|
||||
href="/dashboard"
|
||||
onClick={() => setIsProfileMenuOpen(false)}
|
||||
className="flex items-center gap-2 px-3 py-2 hover:bg-medical-gray-50 text-medical-gray-700 rounded-xl font-bold transition-colors"
|
||||
>
|
||||
<User className="w-4 h-4 text-canina-blue" />
|
||||
<span>داشبورد کاربری</span>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
logout();
|
||||
setIsProfileMenuOpen(false);
|
||||
toast.success('از حساب کاربری خارج شدید');
|
||||
}}
|
||||
className="w-full flex items-center gap-2 px-3 py-2 hover:bg-rose-50 text-rose-600 rounded-xl font-bold transition-colors text-right"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<span>خروج از حساب</span>
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="flex items-center justify-center p-2.5 sm:px-3 sm:py-2 bg-medical-gray-50 hover:bg-medical-gray-100 border border-medical-gray-200 rounded-xl text-xs font-black text-medical-gray-900 transition-all"
|
||||
title="حساب کاربری"
|
||||
>
|
||||
<User className="w-4 h-4 text-canina-blue" />
|
||||
<span className="hidden sm:inline">{profile.firstName || 'حساب کاربری'}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setIsAuthModalOpen(true)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user