feat: add refill toggle in admin settings and compact checkout UI layout
All checks were successful
Deploy Canina / deploy (push) Successful in 1m31s

This commit is contained in:
parsa aghaei 2026-08-22 17:37:50 +03:30
parent 6454c18071
commit 4e768d66a9
14 changed files with 3445 additions and 3422 deletions

View File

@ -15,6 +15,8 @@ export default function FinancialSettingsPage() {
walletWithdrawalEnabled: false,
});
const [charityStep, setCharityStep] = useState('10000');
const [refillEnabled, setRefillEnabled] = useState(false);
const [refillPercent, setRefillPercent] = useState('5');
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [donationInput, setDonationInput] = useState('');
@ -41,6 +43,8 @@ export default function FinancialSettingsPage() {
}
if (resAdmin.data?.success) {
setCharityStep(resAdmin.data.data.CHARITY_ROUND_STEP || '10000');
setRefillEnabled(resAdmin.data.data.REFILL_SUBSCRIPTION_ENABLED === 'true');
setRefillPercent(resAdmin.data.data.REFILL_REWARD_PERCENT || '5');
}
})
.catch((err) => {
@ -63,6 +67,8 @@ export default function FinancialSettingsPage() {
api.put('/admin/settings', {
TAX_PERCENTAGE: String(financial.taxPercentage),
CHARITY_ROUND_STEP: charityStep,
REFILL_SUBSCRIPTION_ENABLED: refillEnabled ? 'true' : 'false',
REFILL_REWARD_PERCENT: refillPercent,
}),
]);
toast.success('تنظیمات مالی با موفقیت بروزرسانی شد');
@ -191,6 +197,52 @@ export default function FinancialSettingsPage() {
</div>
</div>
{/* Refill Subscription and Reward Cashback */}
<div className="md:col-span-2 pt-4 border-t border-gray-100 space-y-4">
<label className="flex items-center justify-between cursor-pointer p-4 bg-indigo-50/50 hover:bg-indigo-50 rounded-2xl border border-indigo-100 transition-colors">
<div className="flex items-start gap-3">
<input
type="checkbox"
checked={refillEnabled}
onChange={(e) => setRefillEnabled(e.target.checked)}
className="w-5 h-5 mt-0.5 rounded-lg text-indigo-600 focus:ring-indigo-500 border-gray-300 cursor-pointer"
/>
<div>
<span className="text-sm font-black text-gray-900 block flex items-center gap-2">
<Percent className="w-4 h-4 text-indigo-600" />
فعالسازی سرویس «رزرو هوشمند تمدید و پاداش خرید بعدی» (Refill Subscription)
</span>
<span className="text-xs text-gray-500 font-medium block mt-0.5">
در صورت فعال بودن، گزینهای در سبد خرید و تسویهحساب به مشتری پیشنهاد میشود تا با فعالسازی یادآور تمدید، درصد مشخصی پاداش (اعتبار خرید بعدی) رزرو کند.
</span>
</div>
</div>
</label>
{refillEnabled && (
<div className="p-4 bg-white rounded-2xl border border-indigo-100 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">
درصد پاداش رزرو تمدید (Cashback Reward Percent)
</label>
<div className="relative">
<input
type="number"
min="1"
max="100"
value={refillPercent}
onChange={(e) => setRefillPercent(e.target.value)}
className="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-indigo-500 outline-none font-bold text-xs"
dir="ltr"
/>
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold text-xs">%</span>
</div>
<p className="text-[11px] text-gray-400 mt-1">پیش‌فرض: ۵ درصد</p>
</div>
</div>
)}
</div>
{/* Wallet Withdrawal Feature Toggle */}
<div className="md:col-span-2 pt-4 border-t border-gray-100">
<label className="flex items-center gap-3 cursor-pointer p-4 bg-emerald-50/50 hover:bg-emerald-50 rounded-2xl border border-emerald-100 transition-colors">

View File

@ -8,6 +8,7 @@ import { Product } from "../lib/data/products";
import { productService } from "../lib/services/productService";
import SafeImage from "./SafeImage";
import { useUserStore } from "../lib/store/userStore";
import { useSettingsStore } from "../lib/store/settingsStore";
import AuthModal from "./AuthModal";
import { toast } from "sonner";
@ -19,6 +20,8 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
}) {
const { items, updateQuantity, removeItem, isSubscribed, toggleSubscription, getTotal, getSubtotal, getDiscount, coupon, applyCoupon, addItem } = useCartStore();
const { isLoggedIn } = useUserStore();
const isRefillEnabled = useSettingsStore((state) => state.getText('REFILL_SUBSCRIPTION_ENABLED', 'false') === 'true');
const refillPercent = useSettingsStore((state) => state.getText('REFILL_REWARD_PERCENT', '5'));
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [couponCode, setCouponCode] = useState("");
const [couponStatus, setCouponStatus] = useState<"idle" | "success" | "error">("idle");
@ -271,6 +274,7 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
</div>
{/* Subscription Toggle */}
{isRefillEnabled && (
<div
onClick={() => {
if (!isLoggedIn && !isSubscribed) {
@ -287,15 +291,16 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
<ShieldCheck className="w-6 h-6" />
</div>
<div className="flex-1">
<h5 className="text-xs font-black text-medical-gray-900">سامانه تمدید هوشمند و ۵٪ پاداش Cashback</h5>
<p className="text-[10px] text-canina-blue font-black mt-0.5">۵٪ شارژ هدیه کیف پول روی خرید تمدید بعدی همین محصول + ارسال اولویتدار</p>
<p className="text-[9px] text-medical-gray-500 font-bold mt-1">برنامه هوشمند وفاداری یادآوری دقیق زمان اتمام مکمل پت بر اساس دوز دامپزشک + ۵٪ اعتبار هدیه بازگشت وجه</p>
<h5 className="text-xs font-black text-medical-gray-900">سامانه تمدید هوشمند و {toPersian(refillPercent)}٪ پاداش Cashback</h5>
<p className="text-[10px] text-canina-blue font-black mt-0.5">{toPersian(refillPercent)}٪ شارژ هدیه کیف پول روی خرید تمدید بعدی همین محصول + ارسال اولویتدار</p>
<p className="text-[9px] text-medical-gray-500 font-bold mt-1">برنامه هوشمند وفاداری یادآوری دقیق زمان اتمام مکمل پت بر اساس دوز دامپزشک + {toPersian(refillPercent)}٪ اعتبار هدیه بازگشت وجه</p>
</div>
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${isSubscribed ? 'bg-canina-blue border-canina-blue' : 'border-medical-gray-200'}`}>
{isSubscribed && <X className="w-3 h-3 text-white" />}
</div>
</div>
</div>
)}
{/* Official Germany Catalog Authenticity Guarantee Banner */}
<div className="p-3.5 bg-amber-50/80 border border-amber-200/80 rounded-2xl flex items-center justify-between gap-3 text-amber-900">

View File

@ -243,7 +243,6 @@ export default function CheckoutPage() {
</div>
);
}
if (items.length === 0) {
return (
<div className="min-h-screen bg-medical-gray-50 flex items-center justify-center p-6" dir="rtl">
@ -258,66 +257,67 @@ export default function CheckoutPage() {
);
}
const isRefillEnabled = useSettingsStore((state) => state.getText('REFILL_SUBSCRIPTION_ENABLED', 'false') === 'true');
const refillPercent = Number(useSettingsStore((state) => state.getText('REFILL_REWARD_PERCENT', '5'))) || 5;
return (
<div className="min-h-screen bg-medical-gray-50 py-6 sm:py-12 px-3 sm:px-6 font-vazir w-full max-w-full overflow-x-hidden" dir="rtl">
<div className="min-h-screen bg-medical-gray-50/60 py-4 sm:py-8 px-3 sm:px-6 font-vazir w-full max-w-full overflow-x-hidden text-right" dir="rtl">
<div className="max-w-7xl mx-auto w-full">
{/* Mobile Navigation & Breadcrumbs */}
<div className="lg:hidden mb-6 sm:mb-10 space-y-4">
{/* Navigation & Breadcrumbs */}
<div className="flex items-center justify-between gap-2 mb-5">
<div className="flex items-center gap-2 text-xs font-bold text-medical-gray-500">
<button
onClick={() => router.back()}
className="flex items-center gap-1.5 text-medical-gray-500 font-bold hover:text-canina-blue transition-colors font-vazir"
className="flex items-center gap-1 hover:text-canina-blue transition-colors cursor-pointer"
>
<span>بازگشت به سبد خرید</span>
<ChevronLeft className="w-5 h-5" />
<ChevronRight className="w-4 h-4" />
<span>بازگشت</span>
</button>
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest overflow-x-auto whitespace-nowrap pb-2">
<span className="text-medical-gray-300">/</span>
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/shop')}>سبد خرید</span>
<ChevronLeft className="w-2.5 h-2.5" />
<span className="text-canina-blue">نهاییسازی سفارش</span>
<span className="text-medical-gray-300">/</span>
<span className="text-canina-blue font-black">تسویهحساب و پرداخت</span>
</div>
<div className="hidden sm:flex items-center gap-1.5 text-xs text-emerald-700 bg-emerald-50 border border-emerald-200 px-3 py-1 rounded-full font-bold">
<ShieldCheck className="w-3.5 h-3.5" />
<span>پرداخت ۱۰۰٪ امن با پروتکل SSL</span>
</div>
</div>
{/* Desktop Breadcrumbs (Hidden on Mobile) */}
<div className="hidden lg: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="text-canina-blue">درگاه پرداخت امن و نهاییسازی</span>
</div>
<div className="grid lg:grid-cols-12 gap-5 w-full items-start">
{/* Main Content (8 cols) */}
<div className="lg:col-span-8 space-y-4 w-full min-w-0">
<div className="grid lg:grid-cols-12 gap-6 lg:gap-12 w-full">
{/* Main Form */}
<div className="lg:col-span-8 space-y-6 sm:space-y-8 w-full min-w-0">
{/* Step 1: Shipping */}
<section className="bg-white rounded-[2rem] sm:rounded-[3rem] border border-medical-gray-200 p-4 sm:p-10 overflow-hidden relative w-full">
<div className="absolute top-0 right-0 w-2.5 sm:w-3 h-full bg-canina-blue opacity-20" />
<div className="flex items-center gap-3 sm:gap-4 mb-6 sm:mb-10">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-medical-gray-900 text-white rounded-2xl flex items-center justify-center font-black text-sm sm:text-base">۱</div>
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">اطلاعات ارسال</h2>
<section className="bg-white rounded-2xl border border-medical-gray-200 p-4 sm:p-6 shadow-xs relative w-full overflow-hidden">
<div className="flex items-center justify-between pb-3 mb-4 border-b border-medical-gray-100">
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 bg-canina-blue text-white rounded-lg flex items-center justify-center font-black text-xs">۱</div>
<h2 className="text-base sm:text-lg font-black text-medical-gray-900">اطلاعات تحویل و گیرنده</h2>
</div>
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
<div className="space-y-4 w-full">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-2">
<p className="text-xs font-bold text-medical-gray-500">انتخاب آدرس جهت تحویل سفارش:</p>
{isLoggedIn && (
<button
type="button"
onClick={() => setShowAddressModal(true)}
className="self-start sm:self-auto flex items-center gap-1.5 text-canina-blue font-black text-xs hover:underline bg-canina-blue/5 border border-canina-blue/20 px-3 py-1.5 rounded-xl transition-all"
className="flex items-center gap-1 text-canina-blue font-bold text-xs hover:underline bg-canina-blue/5 border border-canina-blue/20 px-2.5 py-1 rounded-lg transition-all cursor-pointer"
>
<PlusCircle className="w-4 h-4" />
<span>+ افزودن آدرس جدید</span>
<PlusCircle className="w-3.5 h-3.5" />
<span>+ افزودن آدرس</span>
</button>
)}
</div>
<div className="grid gap-3 w-full">
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
<div className="space-y-3">
<div className="grid sm:grid-cols-2 gap-2.5">
{profile.addresses.map((addr) => (
<div
key={addr.id}
onClick={() => setSelectedAddressId(addr.id)}
className={cn(
"p-3 sm:p-4 rounded-2xl border-2 cursor-pointer transition-all flex items-start gap-2.5 sm:gap-3 w-full min-w-0 max-w-full overflow-hidden",
selectedAddressId === addr.id ? "border-canina-blue bg-canina-blue/5" : "border-medical-gray-100 hover:border-medical-gray-200"
"p-3 rounded-xl border transition-all flex items-start gap-2.5 cursor-pointer",
selectedAddressId === addr.id ? "border-canina-blue bg-canina-blue/5 shadow-xs" : "border-medical-gray-200 hover:border-medical-gray-300"
)}
>
<input
@ -325,39 +325,26 @@ export default function CheckoutPage() {
name="selectedAddress"
checked={selectedAddressId === addr.id}
onChange={() => setSelectedAddressId(addr.id)}
className="mt-1 shrink-0"
className="mt-1 shrink-0 cursor-pointer"
/>
<div className="flex-1 min-w-0 break-words">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<span className="font-black text-sm text-medical-gray-900">{addr.title}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-0.5">
<span className="font-black text-xs text-medical-gray-900">{addr.title}</span>
{addr.isDefault && (
<span className="bg-canina-blue/10 text-canina-blue text-[9px] font-black px-2 py-0.5 rounded-full shrink-0">پیشفرض</span>
<span className="bg-canina-blue/10 text-canina-blue text-[9px] font-bold px-1.5 py-0.2 rounded-md">پیشفرض</span>
)}
</div>
<p className="text-xs text-medical-gray-600 leading-relaxed break-words line-clamp-3 sm:line-clamp-none">{addr.province}، {addr.city}، {addr.detail}</p>
<p className="text-[10px] text-medical-gray-400 font-bold mt-1 break-words">گیرنده: {addr.receptorName} | همراه: {toPersian(addr.phone)}</p>
<p className="text-[11px] text-medical-gray-600 line-clamp-2 leading-relaxed">{addr.province}، {addr.city}، {addr.detail}</p>
<p className="text-[10px] text-medical-gray-400 font-bold mt-1">گیرنده: {addr.receptorName} ({toPersian(addr.phone)})</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className="space-y-4">
{isLoggedIn && (
<div className="flex justify-end mb-2">
<button
type="button"
onClick={() => setShowAddressModal(true)}
className="flex items-center gap-1.5 text-canina-blue font-black text-xs hover:underline bg-canina-blue/5 border border-canina-blue/20 px-3 py-1.5 rounded-xl transition-all"
>
<PlusCircle className="w-4 h-4" />
<span>افزودن و ذخیره آدرس جدید</span>
</button>
</div>
)}
<div className="grid md:grid-cols-2 gap-4 sm:gap-6">
<div className="space-y-2">
<label htmlFor="chk-fullname" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">نام و نام خانوادگی گیرنده *</label>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
<div>
<label htmlFor="chk-fullname" className="text-[11px] font-bold text-medical-gray-700 block mb-1">نام و نام خانوادگی *</label>
<input
id="chk-fullname"
name="name"
@ -365,12 +352,12 @@ export default function CheckoutPage() {
autoComplete="name"
value={manualName}
onChange={(e) => setManualName(e.target.value)}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none"
placeholder="مثلاً: پارسا آقایی"
/>
</div>
<div className="space-y-2">
<label htmlFor="chk-tel" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شماره تماس گیرنده *</label>
<div>
<label htmlFor="chk-tel" className="text-[11px] font-bold text-medical-gray-700 block mb-1">شماره همراه *</label>
<input
id="chk-tel"
name="tel"
@ -378,12 +365,12 @@ export default function CheckoutPage() {
autoComplete="tel"
value={manualPhone}
onChange={(e) => setManualPhone(e.target.value.replace(/[^0-9]/g, ''))}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 dir-ltr text-right"
className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none dir-ltr text-right"
placeholder="۰۹۱۲XXXXXXX"
/>
</div>
<div className="space-y-2">
<label htmlFor="chk-province" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">استان *</label>
<div>
<label htmlFor="chk-province" className="text-[11px] font-bold text-medical-gray-700 block mb-1">استان *</label>
<SearchableSelect
id="chk-province"
options={IRAN_PROVINCES}
@ -397,8 +384,8 @@ export default function CheckoutPage() {
}}
/>
</div>
<div className="space-y-2">
<label htmlFor="chk-city" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شهر *</label>
<div>
<label htmlFor="chk-city" className="text-[11px] font-bold text-medical-gray-700 block mb-1">شهر *</label>
{manualProvince && PROVINCE_CITIES[manualProvince] ? (
<SearchableSelect
id="chk-city"
@ -414,63 +401,56 @@ export default function CheckoutPage() {
type="text"
value={manualCity}
onChange={(e) => setManualCity(e.target.value)}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none"
placeholder="نام شهر..."
/>
)}
</div>
<div className="md:col-span-2 space-y-2">
<label htmlFor="chk-street" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">آدرس دقیق پستی *</label>
<textarea
<div className="sm:col-span-2 md:col-span-3">
<label htmlFor="chk-street" className="text-[11px] font-bold text-medical-gray-700 block mb-1">آدرس دقیق پستی *</label>
<input
id="chk-street"
name="street-address"
autoComplete="street-address"
value={manualAddress}
onChange={(e) => setManualAddress(e.target.value)}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 h-24 sm:h-28"
className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none"
placeholder="خیابان، کوچه، پلاک، واحد..."
/>
</div>
<div className="md:col-span-2 space-y-2">
<label htmlFor="chk-zipcode" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">کد پستی ۱۰ رقمی</label>
<div>
<label htmlFor="chk-zipcode" className="text-[11px] font-bold text-medical-gray-700 block mb-1">کد پستی (۱۰ رقمی)</label>
<input
id="chk-zipcode"
type="text"
maxLength={10}
value={manualZipCode}
onChange={(e) => setManualZipCode(e.target.value.replace(/[^0-9]/g, ''))}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-2xl py-3 sm:py-4 px-4 sm:px-6 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20 dir-ltr text-right"
className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none dir-ltr text-right"
placeholder="۱۲۳۴۵۶۷۸۹۰"
/>
</div>
</div>
</div>
)}
</section>
{/* Pet Selection */}
{/* Pet Selection (if user has pets) */}
{pets.length > 0 && (
<section className="bg-white rounded-[2rem] sm:rounded-[3rem] border border-medical-gray-200 p-5 sm:p-8 overflow-hidden relative">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-purple-100 text-purple-700 rounded-2xl flex items-center justify-center font-black">🐾</div>
<div>
<h2 className="text-lg font-black text-medical-gray-900 italic">انتخاب همدم (حیوان خانگی)</h2>
<p className="text-xs text-medical-gray-500">برای ثبت سابقه خرید در پرونده سلامت پت، همدم موردنظر را انتخاب کنید:</p>
<section className="bg-white rounded-2xl border border-medical-gray-200 p-4 shadow-xs">
<div className="flex items-center gap-2 mb-2.5">
<span className="text-base">🐾</span>
<h3 className="text-xs font-black text-medical-gray-900">اتصال سفارش به پرونده سلامت حیوان خانگی:</h3>
</div>
</div>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => setSelectedPetId(null)}
className={cn(
"p-3 rounded-2xl border-2 text-xs font-black transition-all flex items-center justify-center gap-2 cursor-pointer",
selectedPetId === null ? "border-canina-blue bg-canina-blue/10 text-canina-blue shadow-xs" : "border-medical-gray-100 text-medical-gray-500 hover:bg-medical-gray-50"
"px-3 py-1.5 rounded-xl border text-xs font-bold transition-all cursor-pointer",
selectedPetId === null ? "border-canina-blue bg-canina-blue/10 text-canina-blue" : "border-medical-gray-200 text-medical-gray-600 hover:bg-medical-gray-50"
)}
>
<span>هیچکدام (خرید عمومی)</span>
خرید عمومی (بدون پت)
</button>
{pets.map(p => (
<button
@ -478,55 +458,54 @@ export default function CheckoutPage() {
type="button"
onClick={() => setSelectedPetId(p.id)}
className={cn(
"p-3 rounded-2xl border-2 text-xs font-black transition-all flex items-center justify-center gap-2 cursor-pointer",
selectedPetId === p.id ? "border-canina-blue bg-canina-blue/10 text-canina-blue shadow-xs" : "border-medical-gray-100 text-medical-gray-600 hover:bg-medical-gray-50"
"px-3 py-1.5 rounded-xl border text-xs font-bold transition-all cursor-pointer",
selectedPetId === p.id ? "border-canina-blue bg-canina-blue/10 text-canina-blue" : "border-medical-gray-200 text-medical-gray-600 hover:bg-medical-gray-50"
)}
>
<span>🐾 {p.name} ({p.breed || p.type})</span>
{p.name} ({p.breed || p.type})
</button>
))}
</div>
</section>
)}
{/* Step 2: Payment */}
<section className="bg-white rounded-[2rem] sm:rounded-[3rem] border border-medical-gray-200 p-5 sm:p-10 overflow-hidden relative">
<div className="absolute top-0 right-0 w-2.5 sm:w-3 h-full bg-medical-gray-900 opacity-20" />
<div className="flex items-center gap-3 sm:gap-4 mb-6 sm:mb-10">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-medical-gray-900 text-white rounded-2xl flex items-center justify-center font-black text-sm sm:text-base">۲</div>
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">شیوه پرداخت</h2>
{/* Step 2: Payment Methods */}
<section className="bg-white rounded-2xl border border-medical-gray-200 p-4 sm:p-6 shadow-xs relative">
<div className="flex items-center gap-2.5 pb-3 mb-4 border-b border-medical-gray-100">
<div className="w-7 h-7 bg-canina-blue text-white rounded-lg flex items-center justify-center font-black text-xs">۲</div>
<h2 className="text-base sm:text-lg font-black text-medical-gray-900">شیوه پرداخت</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{(() => {
const getText = useSettingsStore.getState().getText;
const allGateways = [
{
id: 'card',
label: getText('PAY_GATEWAY_CARD_LABEL', 'کارت به کارت (واریز مستقیم)'),
desc: getText('PAY_GATEWAY_CARD_DESC', 'واریز به شماره کارت بانک سامان'),
icon: <CreditCard className="w-5 h-5 sm:w-6 sm:h-6" />,
enabled: isCardToCardEnabled
id: 'online',
label: getText('PAY_GATEWAY_ONLINE_LABEL', 'درگاه پرداخت اینترنتی (زیبال / شتاب)'),
desc: 'پرداخت آنلاین و آنی با کلیه کارت‌های شتاب',
icon: <ShieldCheck className="w-5 h-5" />,
enabled: getText('PAY_GATEWAY_ONLINE_ENABLE', 'true') === 'true'
},
{
id: 'wallet',
label: getText('PAY_GATEWAY_WALLET_LABEL', 'اعتبار حساب (کیف پول)'),
desc: `موجودی: ${toPersian((profile.walletBalance || 0).toLocaleString())} تومان`,
icon: <Wallet className="w-5 h-5 sm:w-6 sm:h-6" />,
icon: <Wallet className="w-5 h-5" />,
enabled: getText('PAY_GATEWAY_WALLET_ENABLE', 'true') === 'true'
},
{
id: 'online',
label: getText('PAY_GATEWAY_ONLINE_LABEL', 'درگاه پرداخت اینترنتی (زیبال / شتاب)'),
desc: getText('PAY_GATEWAY_ONLINE_DESC', 'پرداخت امن و آنی با کلیه کارت‌های عضو شتاب'),
icon: <ShieldCheck className="w-5 h-5 sm:w-6 sm:h-6" />,
enabled: getText('PAY_GATEWAY_ONLINE_ENABLE', 'true') === 'true'
id: 'card',
label: getText('PAY_GATEWAY_CARD_LABEL', 'کارت به کارت (واریز مستقیم)'),
desc: getText('PAY_GATEWAY_CARD_DESC', 'واریز به شماره کارت بانک سامان'),
icon: <CreditCard className="w-5 h-5" />,
enabled: isCardToCardEnabled
},
{
id: 'cod',
label: getText('PAY_GATEWAY_COD_LABEL', 'پرداخت در محل (کارتخوان)'),
desc: getText('PAY_GATEWAY_COD_DESC', 'تحویل کالا و پرداخت درب منزل'),
icon: <Truck className="w-5 h-5 sm:w-6 sm:h-6" />,
icon: <Truck className="w-5 h-5" />,
enabled: getText('PAY_GATEWAY_COD_ENABLE', 'false') === 'true'
}
];
@ -544,26 +523,25 @@ export default function CheckoutPage() {
}
}}
className={cn(
"p-4 sm:p-6 rounded-2xl sm:rounded-3xl border-2 transition-all group flex sm:flex-col items-center sm:items-start gap-4 sm:gap-0 relative",
"p-3.5 rounded-xl border-2 transition-all flex items-center justify-between gap-3 relative cursor-pointer",
pay.enabled
? (paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5 cursor-pointer" : "border-medical-gray-100 hover:border-canina-blue cursor-pointer")
: "border-medical-gray-100 bg-medical-gray-50/60 opacity-60 cursor-not-allowed"
? (paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5 shadow-xs" : "border-medical-gray-200 hover:border-canina-blue/40")
: "border-medical-gray-100 bg-medical-gray-50 opacity-60 cursor-not-allowed"
)}
>
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-medical-gray-50 rounded-xl sm:rounded-2xl flex items-center justify-center text-medical-gray-400 group-hover:text-canina-blue group-hover:bg-canina-blue/10 sm:mb-4 transition-all flex-shrink-0">
<div className="flex items-center gap-3 min-w-0">
<div className={cn(
"w-9 h-9 rounded-xl flex items-center justify-center shrink-0",
paymentMethod === pay.id ? "bg-canina-blue text-white" : "bg-medical-gray-100 text-medical-gray-500"
)}>
{pay.icon}
</div>
<div className="min-w-0 flex-1 w-full">
<div className="flex items-center justify-between gap-2 mb-0.5 sm:mb-1">
<h4 className="font-black text-xs sm:text-sm text-medical-gray-900 truncate">{pay.label}</h4>
{!pay.enabled && (
<span className="text-[9px] font-black bg-amber-100 text-amber-800 px-2 py-0.5 rounded-full shrink-0 font-vazir">
به زودی
</span>
)}
<div className="min-w-0">
<h4 className="font-bold text-xs text-medical-gray-900 truncate">{pay.label}</h4>
<p className="text-[10px] text-medical-gray-500 truncate mt-0.5">{pay.desc}</p>
</div>
<div className="flex items-center justify-between gap-2">
<p className={cn("text-[10px] font-bold truncate", pay.id === 'wallet' ? "text-canina-blue" : "text-medical-gray-400")}>{pay.desc}</p>
</div>
{pay.id === 'wallet' && pay.enabled && (
<button
type="button"
@ -571,179 +549,137 @@ export default function CheckoutPage() {
e.stopPropagation();
setShowTopUpModal(true);
}}
className="text-[10px] bg-canina-blue text-white px-2 py-0.5 rounded-lg font-bold hover:bg-blue-700 transition-colors flex items-center gap-1 shrink-0 font-vazir"
className="text-[10px] bg-canina-blue text-white px-2.5 py-1 rounded-lg font-bold hover:bg-blue-700 transition-colors shrink-0 shadow-xs cursor-pointer"
>
<PlusCircle className="w-3 h-3" />
<span>افزایش موجودی</span>
+ افزایش
</button>
)}
</div>
</div>
</div>
));
})()}
</div>
{isCardToCardEnabled && paymentMethod === 'card' && (
<div className="mt-6 bg-blue-50/80 border border-blue-200 rounded-2xl p-5 space-y-3 font-vazir">
<div className="flex items-center gap-2 text-canina-blue font-bold text-sm">
<CreditCard className="w-5 h-5" />
<div className="mt-4 bg-blue-50/80 border border-blue-200 rounded-xl p-3.5 space-y-2">
<div className="flex items-center gap-2 text-canina-blue font-bold text-xs">
<CreditCard className="w-4 h-4" />
<span>اطلاعات کارت جهت واریز وجه سفارش:</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs bg-white p-4 rounded-xl border border-blue-100 shadow-sm">
{/* Card Number Box */}
<div className="bg-gray-50/80 p-3 rounded-xl border border-gray-200/60 flex items-center justify-between group hover:border-canina-blue transition-all">
<div className="space-y-1">
<span className="text-[11px] text-gray-500 font-bold block">شماره کارت (بانک سامان):</span>
<span
onClick={() => handleCopy("6219861974012071", "شماره کارت")}
className="font-mono text-sm sm:text-base font-extrabold text-gray-900 tracking-wider dir-ltr block cursor-pointer hover:text-canina-blue transition-colors"
>
6219 - 8619 - 7401 - 2071
</span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-xs bg-white p-3 rounded-lg border border-blue-100">
<div className="flex items-center justify-between p-2 bg-gray-50 rounded-lg">
<div>
<span className="text-[10px] text-gray-500 block">شماره کارت سامان:</span>
<span className="font-mono font-bold text-xs" dir="ltr">6219 8619 7401 2071</span>
</div>
<button
type="button"
onClick={() => handleCopy("6219861974012071", "شماره کارت")}
className="p-2 rounded-lg bg-white border border-gray-200 text-gray-500 hover:text-canina-blue hover:border-canina-blue transition-all"
title="کپی شماره کارت"
className="text-[10px] text-canina-blue hover:underline font-bold"
>
<Copy className="w-4 h-4" />
کپی
</button>
</div>
{/* Sheba Box */}
<div className="bg-gray-50/80 p-3 rounded-xl border border-gray-200/60 flex items-center justify-between group hover:border-canina-blue transition-all">
<div className="space-y-1">
<span className="text-[11px] text-gray-500 font-bold block">شماره شبا:</span>
<span
onClick={() => handleCopy("IR650560611828005725101501", "شماره شبا")}
className="font-mono text-xs sm:text-sm font-extrabold text-gray-900 tracking-wider dir-ltr block cursor-pointer hover:text-canina-blue transition-colors"
>
IR-65 0560 6118 2800 5725 1015 01
</span>
<div className="flex items-center justify-between p-2 bg-gray-50 rounded-lg">
<div>
<span className="text-[10px] text-gray-500 block">صاحب حساب:</span>
<span className="font-bold text-xs text-gray-900">پارسادرمانی کنینا (نوواگارد)</span>
</div>
<button
type="button"
onClick={() => handleCopy("IR650560611828005725101501", "شماره شبا")}
className="p-2 rounded-lg bg-white border border-gray-200 text-gray-500 hover:text-canina-blue hover:border-canina-blue transition-all"
title="کپی شماره شبا"
>
<Copy className="w-4 h-4" />
</button>
</div>
<div className="md:col-span-2 pt-2 border-t border-gray-100 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 text-gray-700 font-bold">
<span className="text-xs">به نام: <strong className="text-gray-900">پارسا آقایی</strong></span>
<span className="text-canina-blue text-[11px]">پس از ثبت سفارش، کارشناسان فروش جهت تایید فیش با شما تماس میگیرند.</span>
</div>
</div>
</div>
)}
</section>
{/* Step 3: Charity Section */}
<section className="bg-white rounded-[2rem] sm:rounded-[3rem] border border-medical-gray-200 p-5 sm:p-10 overflow-hidden relative">
<div className="absolute top-0 right-0 w-2.5 sm:w-3 h-full bg-pink-500 opacity-20" />
<div className="flex items-center justify-between mb-6 sm:mb-8">
<div className="flex items-center gap-3 sm:gap-4">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-pink-500 text-white rounded-2xl flex items-center justify-center flex-shrink-0">
<Heart className="w-5 h-5 sm:w-6 sm:h-6 fill-white" />
{/* Charity Donation Banner (Compact) */}
<section className="bg-pink-50/70 border border-pink-200/80 rounded-2xl p-3.5 flex items-center justify-between gap-3">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 bg-pink-500 text-white rounded-xl flex items-center justify-center shrink-0">
<Heart className="w-4 h-4 fill-white" />
</div>
<div>
<h2 className="text-xl sm:text-2xl font-black text-medical-gray-900 italic">ردپای مهربانی</h2>
<p className="text-[10px] sm:text-xs font-bold text-pink-500 mt-0.5">سهم شما در حمایت از حیوانات بیپناه</p>
<h4 className="text-xs font-bold text-medical-gray-900">کمک به حیوانات پناهگاه (ردپای مهربانی)</h4>
<p className="text-[10px] text-medical-gray-500">رند کردن مبلغ فاکتور تا {toPersian(roundStep.toLocaleString())} تومان بعدی</p>
</div>
</div>
</div>
<div className="bg-pink-50 rounded-[2rem] sm:rounded-[2.5rem] p-5 sm:p-8 border border-pink-100">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 sm:gap-6">
<div className="flex-1">
<h4 className="text-base sm:text-lg font-black text-medical-gray-900 italic mb-1 sm:mb-2">رند کردن مبلغ و کمک به پناهگاه</h4>
<p className="text-xs sm:text-sm text-medical-gray-600 leading-relaxed font-medium"> مابقی مبلغ تا {toPersian(roundStep.toLocaleString())} تومان بعدی صرف تامین غذا و دارو برای حیوانات بیسرپرست میشود.</p>
</div>
<button
type="button"
onClick={handleCharityToggle}
className={cn(
"w-14 h-14 sm:w-20 sm:h-20 rounded-full flex items-center justify-center transition-all shadow-lg self-end sm:self-center",
useRoundUp ? "bg-pink-500 text-white shadow-pink-200" : "bg-white text-medical-gray-300 border-2 border-medical-gray-100"
"px-3 py-1.5 rounded-xl text-xs font-bold transition-all shrink-0 cursor-pointer",
useRoundUp ? "bg-pink-500 text-white shadow-xs" : "bg-white text-pink-600 border border-pink-200 hover:bg-pink-50"
)}
>
<PlusCircle className={cn("w-7 h-7 sm:w-10 sm:h-10", useRoundUp ? "rotate-45" : "rotate-0")} />
{useRoundUp ? `اهدای ${toPersian(charityDonation.toLocaleString())} ت` : 'مشارکت در خیریه'}
</button>
</div>
</div>
{useRoundUp && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-6 pt-6 border-t border-pink-100 flex items-center justify-between"
>
<span className="text-sm font-black text-pink-600 italic">مبلغ اهدایی شما:</span>
<span className="text-lg font-black font-vazir text-pink-500">{toPersian(charityDonation.toLocaleString())} تومان</span>
</motion.div>
)}
<p className="mt-6 text-[10px] text-center text-medical-gray-400 font-bold">«با خرید این محصول، شما هم در درمان یک حیوان پناهگاهی سهیم شدید.»</p>
</section>
</div>
{/* Sidebar Summary */}
<div className="lg:col-span-4 space-y-8">
<div className="bg-medical-gray-900 rounded-[3rem] p-10 text-white shadow-2xl sticky top-32">
<h3 className="text-2xl font-black italic mb-8 border-b border-white/20 pb-4 text-white">خلاصه سفارش</h3>
<div className="space-y-6 mb-10 overflow-y-auto max-h-60 pr-2">
{items.map((item) => (
<div key={item.product.id} className="flex justify-between items-start gap-4">
<div className="flex-1">
<p className="text-sm font-black text-white leading-tight">{item.product.name}</p>
<p className="text-[10px] text-white/60 font-bold mt-1">تعداد: {toPersian(item.quantity)} بسته</p>
{/* Sticky Order Summary Sidebar (4 cols) */}
<div className="lg:col-span-4 w-full">
<div className="bg-medical-gray-900 rounded-2xl p-5 text-white shadow-xl sticky top-24 space-y-4">
<div className="flex items-center justify-between border-b border-white/10 pb-3">
<h3 className="text-base font-black text-white">خلاصه سفارش</h3>
<span className="text-[11px] text-white/60 font-bold">{toPersian(items.length)} قلم کالا</span>
</div>
<span className="text-xs font-mono font-bold font-vazir text-white">{toPersian((item.product.priceValue * item.quantity).toLocaleString())} تومان</span>
{/* Items List (Compact) */}
<div className="space-y-2.5 max-h-48 overflow-y-auto pr-1">
{items.map((item) => (
<div key={item.product.id} className="flex justify-between items-center text-xs gap-2">
<div className="min-w-0 flex-1">
<p className="font-bold text-white/90 truncate">{item.product.name}</p>
<p className="text-[10px] text-white/50">{toPersian(item.quantity)} بسته</p>
</div>
<span className="font-bold font-vazir text-white whitespace-nowrap">
{toPersian((item.product.priceValue * item.quantity).toLocaleString())} ت
</span>
</div>
))}
</div>
<div className="space-y-4 pt-8 border-t border-white/20">
<div className="flex justify-between text-sm font-bold text-white/80">
{/* Cost Breakdown */}
<div className="space-y-2 pt-3 border-t border-white/10 text-xs">
<div className="flex justify-between text-white/80">
<span>مجموع اقلام</span>
<span className="font-vazir text-white">{toPersian(getSubtotal().toLocaleString())} تومان</span>
</div>
{isSubscribed && (
<div className="flex justify-between text-xs font-bold text-green-400 bg-green-500/10 p-2.5 rounded-xl border border-green-500/20">
<span>رزرو هوشمند تمدید (پاداش ۵٪ خرید بعدی):</span>
<span className="font-vazir font-black text-green-400">رزرو شد ({toPersian(Math.round(getSubtotal() * 0.05).toLocaleString())} تومان)</span>
{isRefillEnabled && isSubscribed && (
<div className="flex justify-between text-xs font-bold text-emerald-400 bg-emerald-500/10 p-2 rounded-xl border border-emerald-500/20">
<span>رزرو تمدید ({toPersian(refillPercent)}٪ پاداش خرید بعد):</span>
<span className="font-vazir">{toPersian(Math.round(getSubtotal() * (refillPercent / 100)).toLocaleString())} ت</span>
</div>
)}
{charityDonation > 0 && (
<div className="flex justify-between text-sm font-bold text-pink-400">
<div className="flex justify-between text-pink-400 font-bold">
<span>ردپای مهربانی</span>
<span className="font-vazir text-pink-400">{toPersian(charityDonation.toLocaleString())} تومان+</span>
<span className="font-vazir">{toPersian(charityDonation.toLocaleString())} تومان+</span>
</div>
)}
<div className="flex justify-between text-sm font-bold text-white/80">
<div className="flex justify-between text-white/80">
<span>هزینه ارسال</span>
{shippingFee > 0 ? (
<span className="font-vazir font-bold text-white">{toPersian(shippingFee.toLocaleString())} تومان</span>
<span className="font-vazir text-white">{toPersian(shippingFee.toLocaleString())} تومان</span>
) : (
<span className="text-green-400 uppercase tracking-widest text-[10px] font-black">رایگان</span>
<span className="text-emerald-400 font-bold">رایگان</span>
)}
</div>
<div className="flex justify-between items-end pt-4">
<span className="text-xl font-black italic text-white">مبلغ نهایی</span>
<div className="flex justify-between items-center pt-3 border-t border-white/10">
<span className="text-sm font-black 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-[10px] text-white/50 font-bold mt-1">تومان</p>
<span className="text-2xl font-black text-canina-gold leading-none font-vazir">
{toPersian((getTotal() + shippingFee).toLocaleString())}
</span>
<span className="text-[10px] text-white/60 font-bold mr-1">تومان</span>
</div>
</div>
</div>
{/* Discount Code */}
<div className="mt-8 pt-8 border-t border-white/10">
{/* Discount Code Input */}
<div className="pt-1">
<form
className="flex gap-2"
onSubmit={async (e) => {
@ -751,9 +687,9 @@ export default function CheckoutPage() {
const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value;
const success = await useCartStore.getState().applyCoupon(code);
if (success) {
toast.success("تبریک! کد تخفیف با موفقیت اعمال شد.");
toast.success("کد تخفیف با موفقیت اعمال شد.");
} else {
toast.error("کد تخفیف معتبر نیست.");
toast.error("کد تخفیف نامعتبر است.");
}
}}
>
@ -762,63 +698,35 @@ export default function CheckoutPage() {
name="coupon"
type="text"
autoComplete="off"
placeholder="کد تخفیف (مثلاً: CANINO2024)"
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none"
placeholder="کد تخفیف..."
className="flex-1 bg-white/10 border border-white/10 rounded-xl px-3 py-2 text-xs focus:ring-1 focus:ring-canina-blue outline-none text-white placeholder-white/40 font-mono"
/>
<button type="submit" className="bg-white/10 px-4 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest hover:bg-white/20 transition-all">اعمال</button>
<button type="submit" className="bg-white/15 px-3 py-2 rounded-xl text-xs font-bold hover:bg-white/25 transition-all cursor-pointer">اعمال</button>
</form>
</div>
{/* Refill Estimates */}
<div className="mt-10 p-6 bg-white/5 rounded-3xl border border-white/10 space-y-4">
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-canina-blue" />
<h4 className="text-xs font-black uppercase tracking-widest font-vazir">تخمین اتمام مصرف (هوش مصنوعی)</h4>
</div>
<div className="space-y-2">
{items.map(item => {
const activePet = usePetStore.getState().getActivePet();
// Re-hydrate product to ensure methods like calculateDosage exist from static config
const staticProduct = PRODUCTS.find(p => p.id === item.product.id || p.artNo === item.product.artNo);
const calculateFn = staticProduct?.calculateDosage || item.product.calculateDosage;
const dose = activePet && typeof calculateFn === 'function'
? calculateFn(activePet.weight, activePet.age <= 1)
: { quantity: 1 };
const days = Math.floor((staticProduct?.packageSize || item.product.packageSize || 50) / (dose.quantity || 1));
return (
<div key={item.product.id} className="flex items-center justify-between text-[10px] font-medium font-vazir">
<span className="text-white/40">{item.product.name}</span>
<span className="text-canina-blue">~ {toPersian(days)} روز دیگر</span>
</div>
);
})}
</div>
</div>
{/* Submit Button */}
<button
onClick={handleFinalize}
disabled={loading}
className="w-full bg-canina-blue text-white py-6 rounded-2xl font-black text-xl mt-10 hover:scale-[1.02] transition-all shadow-xl shadow-canina-blue/20 flex items-center justify-center gap-3 disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full bg-canina-blue hover:bg-blue-600 text-white py-3.5 rounded-xl font-black text-sm transition-all shadow-md shadow-canina-blue/20 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer mt-2"
>
{loading ? (
<>
<div className="w-6 h-6 border-4 border-white/20 border-t-white rounded-full animate-spin" />
<div className="w-4 h-4 border-2 border-white/20 border-t-white rounded-full animate-spin" />
در حال ثبت...
</>
) : (
<>
پرداخت و تکمیل سفارش
<ArrowRight className="w-6 h-6 rotate-180" />
<span>پرداخت و تکمیل سفارش</span>
<ArrowRight className="w-4 h-4 rotate-180" />
</>
)}
</button>
<div className="mt-6 flex items-center justify-center gap-2 text-[10px] font-black text-white/20 tracking-widest uppercase">
<ShieldCheck className="w-4 h-4" />
تضمین امنیت تراکنش بانکی
<div className="flex items-center justify-center gap-1.5 text-[10px] font-bold text-white/30 tracking-wide pt-1">
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
<span>تضمین امنیت پرداخت و اصالت کالا</span>
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@
"3": "app.module.ts",
"4": "reviews.controller.ts",
"5": "tickets.controller.ts",
"6": "AuthService",
"6": "faq.module.ts",
"7": "MediaSelector.tsx",
"8": "PetProfile.tsx",
"9": "Roles",
@ -51,7 +51,7 @@
"49": "SmartAdvisorService",
"50": "TestimonialsService",
"51": "Role & Core Objective",
"52": ".initiateOrderPayment",
"52": ".handleZibalCallback",
"53": "ZibalEBankService",
"54": "compilerOptions",
"55": "Media.tsx",
@ -118,7 +118,7 @@
"116": "compilerOptions",
"117": "compilerOptions",
"118": "backend/README.md",
"119": "devDependencies",
"119": "eslint",
"120": "Repository Map",
"121": "validate_integrity.js",
"122": "admin-panel/package.json",
@ -173,7 +173,7 @@
"171": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
"172": "🚀 SEO & Content Strategy Review (12_seo_content)",
"173": "@types/node",
"174": "MetricsController",
"174": "wiki/page.tsx",
"175": "seed-ui-texts.ts",
"176": "seed-wiki.ts",
"177": "update-blog.dto.ts",
@ -234,7 +234,7 @@
"232": "ProductDto",
"233": "@testing-library/react",
"234": "@types/react",
"235": "typescript",
"235": "bcryptjs",
"236": "vitest",
"237": "axios",
"238": "Body",
@ -242,7 +242,7 @@
"240": "ts-loader",
"241": "VerifyOtpDto",
"242": "@types/bcrypt",
"243": "app.e2e-spec.js",
"243": "helmet",
"244": "blog.entity.ts",
"245": "home.entity.ts",
"246": "wiki.entity.ts",
@ -289,22 +289,37 @@
"287": "Production Docker Compose",
"288": "Staging Docker Compose",
"289": "tailwindcss",
"290": "@types/passport-jwt",
"290": "devDependencies",
"291": "useSettingsStore",
"292": "@types/supertest",
"293": "typescript",
"292": "js-yaml",
"293": "@nestjs/core",
"294": "app-audit-verification.e2e-spec.d.ts",
"295": "app.e2e-spec.d.ts",
"296": "dashboard/page.tsx",
"296": "@nestjs/jwt",
"297": "CreateHealthLogDto",
"298": "eslint-config-prettier",
"299": "CreateReminderDto",
"300": ".sendOtp",
"301": "track/page.tsx",
"302": "@types/react-dom",
"303": "eslint-plugin-prettier",
"303": "@nestjs/swagger",
"304": "@nestjs/throttler",
"305": "RouteErrorBoundary",
"306": "passport-jwt",
"307": "@prisma/client",
"308": "swagger-ui-express",
"309": "globals",
"310": "admin.module.ts",
"312": "tailwindcss"
"311": "@eslint/eslintrc",
"312": "tailwindcss",
"313": "@eslint/js",
"314": "jest",
"315": "@nestjs/schematics",
"316": "@nestjs/testing",
"317": "source-map-support",
"318": "ts-jest",
"319": "tsconfig-paths",
"320": "@types/bcryptjs",
"321": "typescript-eslint",
"322": "eslint-config-next"
}

File diff suppressed because one or more lines are too long

View File

@ -51,7 +51,7 @@
"49": "SmartAdvisorService",
"50": "TestimonialsService",
"51": "Role & Core Objective",
"52": ".handleZibalCallback",
"52": ".initiateOrderPayment",
"53": "ZibalEBankService",
"54": "compilerOptions",
"55": "Media.tsx",
@ -295,13 +295,13 @@
"293": "typescript",
"294": "app-audit-verification.e2e-spec.d.ts",
"295": "app.e2e-spec.d.ts",
"296": "wiki/page.tsx",
"296": "dashboard/page.tsx",
"297": "CreateHealthLogDto",
"298": "eslint-config-prettier",
"299": "CreateReminderDto",
"300": ".sendOtp",
"301": "track/page.tsx",
"302": "eslint-config-next",
"302": "@types/react-dom",
"303": "eslint-plugin-prettier",
"305": "RouteErrorBoundary",
"309": "globals",

View File

@ -1,7 +1,7 @@
# Graph Report - canina (2026-08-22)
## Corpus Check
- 539 files · ~762,735 words
- 539 files · ~762,770 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
@ -10,7 +10,7 @@
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `d62787cd`
- Built from commit: `992c8157`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@ -67,7 +67,7 @@
- SmartAdvisorService
- TestimonialsService
- Role & Core Objective
- .handleZibalCallback
- .initiateOrderPayment
- ZibalEBankService
- compilerOptions
- Media.tsx
@ -294,12 +294,12 @@
- useSettingsStore
- @types/supertest
- typescript
- wiki/page.tsx
- dashboard/page.tsx
- CreateHealthLogDto
- eslint-config-prettier
- CreateReminderDto
- track/page.tsx
- eslint-config-next
- @types/react-dom
- eslint-plugin-prettier
- RouteErrorBoundary
- globals
@ -374,8 +374,8 @@ Cohesion: 0.11
Nodes (18): FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+10 more)
### Community 9 - "Roles"
Cohesion: 0.20
Nodes (15): Roles(), PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body (+7 more)
Cohesion: 0.24
Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more)
### Community 10 - "DoctorQueryDto"
Cohesion: 0.08
@ -467,7 +467,7 @@ Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Inj
### Community 32 - "ZibalService"
Cohesion: 0.09
Nodes (5): Put, PaymentService, Injectable, Injectable, ZibalService
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
### Community 33 - "راهنمای تست سیستم (Software Testing)"
Cohesion: 0.07
@ -545,9 +545,9 @@ Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.09
Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File Scope Boundary, Forbidden Actions, MODE 1: REVIEW (called during Review Phase), MODE 2: CONTENT CREATION (standalone task from backlog), Operating Modes, Operational Rules & Boundaries (+14 more)
### Community 52 - ".handleZibalCallback"
Cohesion: 0.25
Nodes (7): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Res, Headers, Ip
### Community 52 - ".initiateOrderPayment"
Cohesion: 0.20
Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more)
### Community 54 - "compilerOptions"
Cohesion: 0.06
@ -586,7 +586,7 @@ Cohesion: 0.14
Nodes (16): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
### Community 63 - "ApiOperation"
Cohesion: 0.17
Cohesion: 0.19
Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
### Community 64 - "BannersService"
@ -659,7 +659,7 @@ Nodes (16): BlogPost, BlogPreviewSection(), ProductDetailModalProps, SafeImage()
### Community 81 - "devDependencies"
Cohesion: 0.13
Nodes (15): devDependencies, eslint, jsdom, tailwindcss, @tailwindcss/postcss, @types/node, @types/react-dom, @vitejs/plugin-react (+7 more)
Nodes (15): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, tailwindcss, @tailwindcss/postcss, @types/node (+7 more)
### Community 82 - "auth.controller.ts"
Cohesion: 0.18
@ -726,8 +726,8 @@ Cohesion: 0.15
Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more)
### Community 98 - "AdminService"
Cohesion: 0.13
Nodes (5): Delete, Param, Put, AdminService, Injectable
Cohesion: 0.18
Nodes (4): Delete, Param, AdminService, Injectable
### Community 99 - "Comprehensive Change Log"
Cohesion: 0.15
@ -1042,8 +1042,8 @@ Cohesion: 0.29
Nodes (6): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength
### Community 215 - "AdminController"
Cohesion: 0.25
Nodes (5): AdminController, ApiBearerAuth, ApiTags, Controller, UseGuards
Cohesion: 0.14
Nodes (6): AdminController, ApiBearerAuth, ApiTags, Controller, Put, UseGuards
### Community 220 - "getPageMetadata"
Cohesion: 0.13
@ -1107,7 +1107,7 @@ _Questions this graph is uniquely positioned to answer:_
- **Why does `ApiResponse` connect `ApiResponse` to `OrdersService`, `UsersService`, `PetsController`, `ProductsService`, `src/services/api.ts`, `AuthController`?**
_High betweenness centrality (0.064) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `BannersService`, `ZibalService`, `CmsController`, `B2BService`, `reviews.controller.ts`, `tickets.controller.ts`, `SslController`, `IngredientsService`, `FaqService`, `MenuService`, `ContactService`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `SmsService`, `WholesaleService`, `JwtAuthGuard`?**
- **Why does `Roles()` connect `Roles` to `BannersService`, `CmsController`, `B2BService`, `reviews.controller.ts`, `tickets.controller.ts`, `SslController`, `IngredientsService`, `FaqService`, `MenuService`, `ContactService`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `SmsService`, `WholesaleService`, `JwtAuthGuard`?**
_High betweenness centrality (0.060) - this node is a cross-community bridge._
- **Why does `PrismaService` connect `PrismaService` to `OrdersService`, `CmsController`, `app.module.ts`, `reviews.controller.ts`, `tickets.controller.ts`, `DoctorQueryDto`, `MenuService`, `pets/pets.controller.ts`, `auth.service.ts`, `ProductsService`, `CreateVideoDto`, `SmsService`, `WholesaleService`, `ZibalService`, `CategoriesController`, `B2BService`, `UsersService`, `IngredientsService`, `FaqService`, `MediaController`, `MetricsController`, `ContactService`, `zibal.service.ts`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `ZibalEBankService`, `admin.module.ts`, `PetsController`, `PaginationDto`, `BannersService`, `admin.service.ts`, `seo.module.ts`, `AuthController`, `ApiResponse`, `payment.service.ts`, `PetsService`, `WholesaleApplyDto`?**
_High betweenness centrality (0.040) - this node is a cross-community bridge._

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,16 @@
# Graph Report - canina (2026-08-22)
## Corpus Check
- 539 files · ~762,770 words
- 539 files · ~762,408 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 3902 nodes · 6878 edges · 308 communities (207 shown, 101 thin omitted)
- 3902 nodes · 6881 edges · 323 communities (205 shown, 118 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 262 edges (avg confidence: 0.79)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `992c8157`
- Built from commit: `6454c180`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@ -21,7 +21,7 @@
- app.module.ts
- reviews.controller.ts
- tickets.controller.ts
- AuthService
- faq.module.ts
- MediaSelector.tsx
- PetProfile.tsx
- Roles
@ -67,7 +67,7 @@
- SmartAdvisorService
- TestimonialsService
- Role & Core Objective
- .initiateOrderPayment
- .handleZibalCallback
- ZibalEBankService
- compilerOptions
- Media.tsx
@ -134,7 +134,7 @@
- compilerOptions
- compilerOptions
- backend/README.md
- devDependencies
- eslint
- Repository Map
- validate_integrity.js
- admin-panel/package.json
@ -189,7 +189,7 @@
- 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)
- 🚀 SEO & Content Strategy Review (12_seo_content)
- @types/node
- MetricsController
- wiki/page.tsx
- seed-ui-texts.ts
- seed-wiki.ts
- update-blog.dto.ts
@ -250,14 +250,14 @@
- ProductDto
- @testing-library/react
- @types/react
- typescript
- bcryptjs
- vitest
- Body
- WholesaleApplyDto
- ts-loader
- VerifyOtpDto
- @types/bcrypt
- app.e2e-spec.js
- helmet
- blog.entity.ts
- home.entity.ts
- wiki.entity.ts
@ -290,26 +290,41 @@
- Shabnam Font Sample
- Production Docker Compose
- Staging Docker Compose
- @types/passport-jwt
- devDependencies
- useSettingsStore
- @types/supertest
- typescript
- dashboard/page.tsx
- js-yaml
- @nestjs/core
- @nestjs/jwt
- CreateHealthLogDto
- eslint-config-prettier
- CreateReminderDto
- track/page.tsx
- @types/react-dom
- eslint-plugin-prettier
- @nestjs/swagger
- @nestjs/throttler
- RouteErrorBoundary
- passport-jwt
- @prisma/client
- swagger-ui-express
- globals
- admin.module.ts
- @eslint/eslintrc
- tailwindcss
- @eslint/js
- jest
- @nestjs/schematics
- @nestjs/testing
- source-map-support
- ts-jest
- tsconfig-paths
- @types/bcryptjs
- typescript-eslint
- eslint-config-next
## God Nodes (most connected - your core abstractions)
1. `Roles()` - 105 edges
2. `PrismaService` - 87 edges
3. `useSettingsStore` - 51 edges
3. `useSettingsStore` - 53 edges
4. `api` - 43 edges
5. `SmsService` - 42 edges
6. `PaginationDto` - 41 edges
@ -339,23 +354,23 @@
- **Rastikerdar Font Ecosystem** — frontend_application_public_fonts_shabnam_font_v5_0_1_readme, frontend_application_public_fonts_vazirmatn_v33_003_readme, vazir_font [EXTRACTED 0.95]
- **Canina Deployment Stack** — scripts_compose_prod, scripts_compose_stage [EXTRACTED 1.00]
## Communities (308 total, 101 thin omitted)
## Communities (323 total, 118 thin omitted)
### Community 0 - "OrdersService"
Cohesion: 0.06
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
### Community 1 - "productService.ts"
Cohesion: 0.10
Nodes (24): BlogPost, CatalogPageSpread(), CatalogPageSpreadProps, CategoryMeta, getFormBadge(), getSpeciesBadge(), FlipbookCatalog(), FlipbookCatalogProps (+16 more)
Cohesion: 0.09
Nodes (28): BlogPost, CatalogPageSpread(), CatalogPageSpreadProps, CategoryMeta, getFormBadge(), getSpeciesBadge(), FlipbookCatalog(), FlipbookCatalogProps (+20 more)
### Community 2 - "CmsController"
Cohesion: 0.09
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 3 - "app.module.ts"
Cohesion: 0.07
Nodes (36): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+28 more)
Cohesion: 0.06
Nodes (34): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+26 more)
### Community 4 - "reviews.controller.ts"
Cohesion: 0.07
@ -370,16 +385,16 @@ Cohesion: 0.07
Nodes (26): ConfirmModal(), ConfirmModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, BlogPost, Category (+18 more)
### Community 8 - "PetProfile.tsx"
Cohesion: 0.11
Nodes (18): FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+10 more)
Cohesion: 0.10
Nodes (19): FeaturedProducts(), ProductCard(), Header(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton() (+11 more)
### Community 9 - "Roles"
Cohesion: 0.24
Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more)
Cohesion: 0.20
Nodes (15): Roles(), PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body (+7 more)
### Community 10 - "DoctorQueryDto"
Cohesion: 0.08
Nodes (26): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more)
Cohesion: 0.09
Nodes (24): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 11 - "MenuService"
Cohesion: 0.12
@ -390,8 +405,8 @@ Cohesion: 0.09
Nodes (16): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, Ticket (+8 more)
### Community 13 - "PrismaService"
Cohesion: 0.08
Nodes (18): MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto, MenuType (+10 more)
Cohesion: 0.07
Nodes (25): ApiExcludeController, MetricsController, Controller, Get, Res, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions (+17 more)
### Community 14 - "pets/pets.controller.ts"
Cohesion: 0.16
@ -402,8 +417,8 @@ Cohesion: 0.13
Nodes (12): ProductsController, ApiOperation, ApiTags, Body, Controller, Get, Param, Patch (+4 more)
### Community 16 - "UserDashboard.tsx"
Cohesion: 0.09
Nodes (41): VerifyContent(), AddressModal(), AddressModalProps, ArchiveProductCard(), AuthModal(), AuthModalProps, extractOtpFromText(), B2BPortal() (+33 more)
Cohesion: 0.11
Nodes (31): VerifyContent(), AddressModal(), AddressModalProps, ArchiveProductCard(), B2BPortal(), BackButton(), BackButtonProps, CartDrawer() (+23 more)
### Community 17 - "CreateVideoDto"
Cohesion: 0.08
@ -467,7 +482,7 @@ Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Inj
### Community 32 - "ZibalService"
Cohesion: 0.09
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
Nodes (5): Put, PaymentService, Injectable, Injectable, ZibalService
### Community 33 - "راهنمای تست سیستم (Software Testing)"
Cohesion: 0.07
@ -545,9 +560,9 @@ Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.09
Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File Scope Boundary, Forbidden Actions, MODE 1: REVIEW (called during Review Phase), MODE 2: CONTENT CREATION (standalone task from backlog), Operating Modes, Operational Rules & Boundaries (+14 more)
### Community 52 - ".initiateOrderPayment"
Cohesion: 0.20
Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more)
### Community 52 - ".handleZibalCallback"
Cohesion: 0.25
Nodes (7): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Res, Headers, Ip
### Community 54 - "compilerOptions"
Cohesion: 0.06
@ -566,20 +581,20 @@ Cohesion: 0.10
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
### Community 58 - "PaginationDto"
Cohesion: 0.06
Cohesion: 0.05
Nodes (24): BlogsService, Injectable, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder (+16 more)
### Community 59 - "dependencies"
Cohesion: 0.05
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more)
Cohesion: 0.10
Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
### Community 60 - "compilerOptions"
Cohesion: 0.10
Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more)
### Community 61 - "HomeClient.tsx"
Cohesion: 0.11
Nodes (20): HomeClient(), HomeClientProps, CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, FAQItem, FAQSection() (+12 more)
Cohesion: 0.13
Nodes (18): HomeClient(), HomeClientProps, CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, Hero(), StatCounter() (+10 more)
### Community 62 - "BlogsController"
Cohesion: 0.14
@ -659,7 +674,7 @@ Nodes (16): BlogPost, BlogPreviewSection(), ProductDetailModalProps, SafeImage()
### Community 81 - "devDependencies"
Cohesion: 0.13
Nodes (15): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, tailwindcss, @tailwindcss/postcss, @types/node (+7 more)
Nodes (15): devDependencies, eslint, jsdom, tailwindcss, @tailwindcss/postcss, @types/node, typescript, @vitejs/plugin-react (+7 more)
### Community 82 - "auth.controller.ts"
Cohesion: 0.18
@ -801,10 +816,6 @@ Nodes (9): compilerOptions, esModuleInterop, module, moduleResolution, skipLibCh
Cohesion: 0.20
Nodes (9): Compile and run the project, Deployment, Description, License, Project setup, Resources, Run tests, Stay in touch (+1 more)
### Community 119 - "devDependencies"
Cohesion: 0.09
Nodes (23): devDependencies, eslint, @eslint/eslintrc, @eslint/js, jest, @nestjs/schematics, @nestjs/testing, source-map-support (+15 more)
### Community 120 - "Repository Map"
Cohesion: 0.20
Nodes (9): 1. Active Customer Storefront: React 19 + Vite Application, 2. Active Backend Service: NestJS Application, 3. Excluded Placeholder / Non-Auditable Directories, Active Applications & Non-Auditable Scopes, Documentation Governance, Important Configuration & Infrastructure Files, Mandatory Global Audit Exclusions, Repository Map (+1 more)
@ -890,11 +901,11 @@ Cohesion: 0.18
Nodes (9): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiTags, Controller, UploadedFile, UseGuards (+1 more)
### Community 142 - "lib/services/api.ts"
Cohesion: 0.08
Nodes (19): ContactInfoItem, api, ApiErrorPayload, baseURL, ApiErr, AuthResponse, User, ApiErr (+11 more)
Cohesion: 0.06
Nodes (23): ContactInfoItem, FAQItem, FAQSection(), api, ApiErrorPayload, baseURL, ApiErr, AuthResponse (+15 more)
### Community 143 - "auth.service.ts"
Cohesion: 0.14
Cohesion: 0.12
Nodes (8): AdminLoginInput, LoginInput, RegisterInput, RedisModule, Global, Module, RedisService, Injectable
### Community 144 - "Baseline Command Plan & Reconciled Command History"
@ -993,10 +1004,6 @@ Nodes (3): Architectural Overview, Critical Review Findings & Required Enhanceme
Cohesion: 0.50
Nodes (3): Overview & Content Foundation, SEO & Content Requirements, 🚀 SEO & Content Strategy Review (12_seo_content)
### Community 174 - "MetricsController"
Cohesion: 0.20
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
### Community 180 - "graphify reference: add a URL and watch a folder"
Cohesion: 0.50
Nodes (3): For /graphify add, For --watch, graphify reference: add a URL and watch a folder
@ -1066,20 +1073,20 @@ Cohesion: 0.21
Nodes (3): Body, Post, CouponInput
### Community 239 - "WholesaleApplyDto"
Cohesion: 0.29
Nodes (6): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto
Cohesion: 0.17
Nodes (9): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, app_module_1, supertest_1 (+1 more)
### Community 241 - "VerifyOtpDto"
Cohesion: 0.29
Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length
### Community 243 - "app.e2e-spec.js"
Cohesion: 0.50
Nodes (3): app_module_1, supertest_1, testing_1
### Community 290 - "devDependencies"
Cohesion: 0.22
Nodes (9): devDependencies, eslint-plugin-prettier, @types/passport-jwt, @types/supertest, typescript, typescript, eslint-plugin-prettier, @types/passport-jwt (+1 more)
### Community 291 - "useSettingsStore"
Cohesion: 0.13
Nodes (20): ClientLayout(), BrandLogo(), BrandLogoProps, EnamadBadge(), Footer(), MENU_ICONS, MaintenancePage(), NetworkBanner() (+12 more)
Cohesion: 0.12
Nodes (24): ClientLayout(), AuthModal(), AuthModalProps, extractOtpFromText(), BrandLogo(), BrandLogoProps, EnamadBadge(), Footer() (+16 more)
### Community 297 - "CreateHealthLogDto"
Cohesion: 0.29
@ -1100,22 +1107,22 @@ Nodes (16): AdminModule, Module, CategoryQuery, ReportsController, ApiBearerAuth
## Knowledge Gaps
- **1274 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1269 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **101 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **118 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `ApiResponse` connect `ApiResponse` to `OrdersService`, `UsersService`, `PetsController`, `ProductsService`, `src/services/api.ts`, `AuthController`?**
_High betweenness centrality (0.064) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `BannersService`, `CmsController`, `B2BService`, `reviews.controller.ts`, `tickets.controller.ts`, `SslController`, `IngredientsService`, `FaqService`, `MenuService`, `ContactService`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `SmsService`, `WholesaleService`, `JwtAuthGuard`?**
- **Why does `Roles()` connect `Roles` to `BannersService`, `ZibalService`, `CmsController`, `B2BService`, `reviews.controller.ts`, `tickets.controller.ts`, `SslController`, `IngredientsService`, `FaqService`, `MenuService`, `ContactService`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `SmsService`, `WholesaleService`, `JwtAuthGuard`?**
_High betweenness centrality (0.060) - this node is a cross-community bridge._
- **Why does `PrismaService` connect `PrismaService` to `OrdersService`, `CmsController`, `app.module.ts`, `reviews.controller.ts`, `tickets.controller.ts`, `DoctorQueryDto`, `MenuService`, `pets/pets.controller.ts`, `auth.service.ts`, `ProductsService`, `CreateVideoDto`, `SmsService`, `WholesaleService`, `ZibalService`, `CategoriesController`, `B2BService`, `UsersService`, `IngredientsService`, `FaqService`, `MediaController`, `MetricsController`, `ContactService`, `zibal.service.ts`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `ZibalEBankService`, `admin.module.ts`, `PetsController`, `PaginationDto`, `BannersService`, `admin.service.ts`, `seo.module.ts`, `AuthController`, `ApiResponse`, `payment.service.ts`, `PetsService`, `WholesaleApplyDto`?**
- **Why does `PrismaService` connect `PrismaService` to `OrdersService`, `CmsController`, `app.module.ts`, `reviews.controller.ts`, `tickets.controller.ts`, `faq.module.ts`, `DoctorQueryDto`, `MenuService`, `pets/pets.controller.ts`, `auth.service.ts`, `ProductsService`, `CreateVideoDto`, `SmsService`, `WholesaleService`, `ZibalService`, `CategoriesController`, `B2BService`, `UsersService`, `IngredientsService`, `FaqService`, `MediaController`, `ContactService`, `zibal.service.ts`, `PrescriptionsService`, `SmartAdvisorService`, `TestimonialsService`, `ZibalEBankService`, `admin.module.ts`, `PetsController`, `PaginationDto`, `BannersService`, `admin.service.ts`, `seo.module.ts`, `AuthController`, `ApiResponse`, `payment.service.ts`, `PetsService`, `WholesaleApplyDto`?**
_High betweenness centrality (0.040) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
_1274 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `OrdersService` be split into smaller, more focused modules?**
_Cohesion score 0.059932659932659935 - nodes in this community are weakly interconnected._
- **Should `productService.ts` be split into smaller, more focused modules?**
_Cohesion score 0.09619450317124736 - nodes in this community are weakly interconnected._
_Cohesion score 0.08549019607843138 - nodes in this community are weakly interconnected._
- **Should `CmsController` be split into smaller, more focused modules?**
_Cohesion score 0.0899854862119013 - nodes in this community are weakly interconnected._

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff