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, walletWithdrawalEnabled: false,
}); });
const [charityStep, setCharityStep] = useState('10000'); const [charityStep, setCharityStep] = useState('10000');
const [refillEnabled, setRefillEnabled] = useState(false);
const [refillPercent, setRefillPercent] = useState('5');
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [donationInput, setDonationInput] = useState(''); const [donationInput, setDonationInput] = useState('');
@ -41,6 +43,8 @@ export default function FinancialSettingsPage() {
} }
if (resAdmin.data?.success) { if (resAdmin.data?.success) {
setCharityStep(resAdmin.data.data.CHARITY_ROUND_STEP || '10000'); 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) => { .catch((err) => {
@ -63,6 +67,8 @@ export default function FinancialSettingsPage() {
api.put('/admin/settings', { api.put('/admin/settings', {
TAX_PERCENTAGE: String(financial.taxPercentage), TAX_PERCENTAGE: String(financial.taxPercentage),
CHARITY_ROUND_STEP: charityStep, CHARITY_ROUND_STEP: charityStep,
REFILL_SUBSCRIPTION_ENABLED: refillEnabled ? 'true' : 'false',
REFILL_REWARD_PERCENT: refillPercent,
}), }),
]); ]);
toast.success('تنظیمات مالی با موفقیت بروزرسانی شد'); toast.success('تنظیمات مالی با موفقیت بروزرسانی شد');
@ -191,6 +197,52 @@ export default function FinancialSettingsPage() {
</div> </div>
</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 */} {/* Wallet Withdrawal Feature Toggle */}
<div className="md:col-span-2 pt-4 border-t border-gray-100"> <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"> <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 { productService } from "../lib/services/productService";
import SafeImage from "./SafeImage"; import SafeImage from "./SafeImage";
import { useUserStore } from "../lib/store/userStore"; import { useUserStore } from "../lib/store/userStore";
import { useSettingsStore } from "../lib/store/settingsStore";
import AuthModal from "./AuthModal"; import AuthModal from "./AuthModal";
import { toast } from "sonner"; 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 { items, updateQuantity, removeItem, isSubscribed, toggleSubscription, getTotal, getSubtotal, getDiscount, coupon, applyCoupon, addItem } = useCartStore();
const { isLoggedIn } = useUserStore(); 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 [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [couponCode, setCouponCode] = useState(""); const [couponCode, setCouponCode] = useState("");
const [couponStatus, setCouponStatus] = useState<"idle" | "success" | "error">("idle"); const [couponStatus, setCouponStatus] = useState<"idle" | "success" | "error">("idle");
@ -271,31 +274,33 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
</div> </div>
{/* Subscription Toggle */} {/* Subscription Toggle */}
<div {isRefillEnabled && (
onClick={() => { <div
if (!isLoggedIn && !isSubscribed) { onClick={() => {
toast.info("برای فعال‌سازی سرویس تمدید خودکار و دریافت یادآورها، لطفاً ابتدا وارد حساب کاربری خود شوید."); if (!isLoggedIn && !isSubscribed) {
setIsAuthModalOpen(true); toast.info("برای فعال‌سازی سرویس تمدید خودکار و دریافت یادآورها، لطفاً ابتدا وارد حساب کاربری خود شوید.");
return; setIsAuthModalOpen(true);
} return;
toggleSubscription(); }
}} toggleSubscription();
className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`} }}
> className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`}
<div className="flex items-center gap-3"> >
<div className={`w-10 h-10 rounded-xl flex items-center justify-center transition-colors ${isSubscribed ? 'bg-canina-blue text-white' : 'bg-medical-gray-50 text-medical-gray-300'}`}> <div className="flex items-center gap-3">
<ShieldCheck className="w-6 h-6" /> <div className={`w-10 h-10 rounded-xl flex items-center justify-center transition-colors ${isSubscribed ? 'bg-canina-blue text-white' : 'bg-medical-gray-50 text-medical-gray-300'}`}>
</div> <ShieldCheck className="w-6 h-6" />
<div className="flex-1"> </div>
<h5 className="text-xs font-black text-medical-gray-900">سامانه تمدید هوشمند و ۵٪ پاداش Cashback</h5> <div className="flex-1">
<p className="text-[10px] text-canina-blue font-black mt-0.5">۵٪ شارژ هدیه کیف پول روی خرید تمدید بعدی همین محصول + ارسال اولویتدار</p> <h5 className="text-xs font-black text-medical-gray-900">سامانه تمدید هوشمند و {toPersian(refillPercent)}٪ پاداش Cashback</h5>
<p className="text-[9px] text-medical-gray-500 font-bold mt-1">برنامه هوشمند وفاداری یادآوری دقیق زمان اتمام مکمل پت بر اساس دوز دامپزشک + ۵٪ اعتبار هدیه بازگشت وجه</p> <p className="text-[10px] text-canina-blue font-black mt-0.5">{toPersian(refillPercent)}٪ شارژ هدیه کیف پول روی خرید تمدید بعدی همین محصول + ارسال اولویتدار</p>
</div> <p className="text-[9px] text-medical-gray-500 font-bold mt-1">برنامه هوشمند وفاداری یادآوری دقیق زمان اتمام مکمل پت بر اساس دوز دامپزشک + {toPersian(refillPercent)}٪ اعتبار هدیه بازگشت وجه</p>
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${isSubscribed ? 'bg-canina-blue border-canina-blue' : 'border-medical-gray-200'}`}> </div>
{isSubscribed && <X className="w-3 h-3 text-white" />} <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>
</div> </div>
</div> )}
{/* Official Germany Catalog Authenticity Guarantee Banner */} {/* 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"> <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> </div>
); );
} }
if (items.length === 0) { if (items.length === 0) {
return ( return (
<div className="min-h-screen bg-medical-gray-50 flex items-center justify-center p-6" dir="rtl"> <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 ( 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"> <div className="max-w-7xl mx-auto w-full">
{/* Mobile Navigation & Breadcrumbs */} {/* Navigation & Breadcrumbs */}
<div className="lg:hidden mb-6 sm:mb-10 space-y-4"> <div className="flex items-center justify-between gap-2 mb-5">
<button <div className="flex items-center gap-2 text-xs font-bold text-medical-gray-500">
onClick={() => router.back()} <button
className="flex items-center gap-1.5 text-medical-gray-500 font-bold hover:text-canina-blue transition-colors font-vazir" onClick={() => router.back()}
> 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" />
</button> <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> <span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/shop')}>سبد خرید</span>
<ChevronLeft className="w-2.5 h-2.5" /> <span className="text-medical-gray-300">/</span>
<span className="text-canina-blue">نهاییسازی سفارش</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>
</div> </div>
{/* Desktop Breadcrumbs (Hidden on Mobile) */} <div className="grid lg:grid-cols-12 gap-5 w-full items-start">
<div className="hidden lg:flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10"> {/* Main Content (8 cols) */}
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span> <div className="lg:col-span-8 space-y-4 w-full min-w-0">
<ChevronLeft className="w-2.5 h-2.5" />
<span className="text-canina-blue">درگاه پرداخت امن و نهاییسازی</span>
</div>
<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 */} {/* 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"> <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="absolute top-0 right-0 w-2.5 sm:w-3 h-full bg-canina-blue opacity-20" /> <div className="flex items-center justify-between pb-3 mb-4 border-b border-medical-gray-100">
<div className="flex items-center gap-3 sm:gap-4 mb-6 sm:mb-10"> <div className="flex items-center gap-2.5">
<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> <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-xl sm:text-2xl font-black text-medical-gray-900 italic">اطلاعات ارسال</h2> <h2 className="text-base sm:text-lg font-black text-medical-gray-900">اطلاعات تحویل و گیرنده</h2>
</div>
{isLoggedIn && (
<button
type="button"
onClick={() => setShowAddressModal(true)}
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-3.5 h-3.5" />
<span>+ افزودن آدرس</span>
</button>
)}
</div> </div>
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? ( {isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
<div className="space-y-4 w-full"> <div className="space-y-3">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-2"> <div className="grid sm:grid-cols-2 gap-2.5">
<p className="text-xs font-bold text-medical-gray-500">انتخاب آدرس جهت تحویل سفارش:</p>
<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"
>
<PlusCircle className="w-4 h-4" />
<span>+ افزودن آدرس جدید</span>
</button>
</div>
<div className="grid gap-3 w-full">
{profile.addresses.map((addr) => ( {profile.addresses.map((addr) => (
<div <div
key={addr.id} key={addr.id}
onClick={() => setSelectedAddressId(addr.id)} onClick={() => setSelectedAddressId(addr.id)}
className={cn( 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", "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" : "border-medical-gray-100 hover:border-medical-gray-200" selectedAddressId === addr.id ? "border-canina-blue bg-canina-blue/5 shadow-xs" : "border-medical-gray-200 hover:border-medical-gray-300"
)} )}
> >
<input <input
@ -325,425 +325,361 @@ export default function CheckoutPage() {
name="selectedAddress" name="selectedAddress"
checked={selectedAddressId === addr.id} checked={selectedAddressId === addr.id}
onChange={() => setSelectedAddressId(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-1 min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap"> <div className="flex items-center gap-2 mb-0.5">
<span className="font-black text-sm text-medical-gray-900">{addr.title}</span> <span className="font-black text-xs text-medical-gray-900">{addr.title}</span>
{addr.isDefault && ( {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> </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-[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 break-words">گیرنده: {addr.receptorName} | همراه: {toPersian(addr.phone)}</p> <p className="text-[10px] text-medical-gray-400 font-bold mt-1">گیرنده: {addr.receptorName} ({toPersian(addr.phone)})</p>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
{isLoggedIn && ( <div>
<div className="flex justify-end mb-2"> <label htmlFor="chk-fullname" className="text-[11px] font-bold text-medical-gray-700 block mb-1">نام و نام خانوادگی *</label>
<button <input
type="button" id="chk-fullname"
onClick={() => setShowAddressModal(true)} name="name"
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" type="text"
> autoComplete="name"
<PlusCircle className="w-4 h-4" /> value={manualName}
<span>افزودن و ذخیره آدرس جدید</span> onChange={(e) => setManualName(e.target.value)}
</button> 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"
</div> placeholder="مثلاً: پارسا آقایی"
)} />
<div className="grid md:grid-cols-2 gap-4 sm:gap-6"> </div>
<div className="space-y-2"> <div>
<label htmlFor="chk-fullname" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">نام و نام خانوادگی گیرنده *</label> <label htmlFor="chk-tel" className="text-[11px] font-bold text-medical-gray-700 block mb-1">شماره همراه *</label>
<input <input
id="chk-fullname" id="chk-tel"
name="name" name="tel"
type="text" type="tel"
autoComplete="name" autoComplete="tel"
value={manualName} value={manualPhone}
onChange={(e) => setManualName(e.target.value)} 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" 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="مثلاً: پارسا آقایی" placeholder="۰۹۱۲XXXXXXX"
/> />
</div> </div>
<div className="space-y-2"> <div>
<label htmlFor="chk-tel" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شماره تماس گیرنده *</label> <label htmlFor="chk-province" className="text-[11px] font-bold text-medical-gray-700 block mb-1">استان *</label>
<input <SearchableSelect
id="chk-tel" id="chk-province"
name="tel" options={IRAN_PROVINCES}
type="tel" value={manualProvince}
autoComplete="tel" placeholder="انتخاب استان..."
value={manualPhone} searchPlaceholder="جستجوی استان..."
onChange={(e) => setManualPhone(e.target.value.replace(/[^0-9]/g, ''))} onChange={(prov) => {
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" const cities = PROVINCE_CITIES[prov] || [];
placeholder="۰۹۱۲XXXXXXX" setManualProvince(prov);
/> setManualCity(cities[0] || '');
</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>
<div>
<label htmlFor="chk-city" className="text-[11px] font-bold text-medical-gray-700 block mb-1">شهر *</label>
{manualProvince && PROVINCE_CITIES[manualProvince] ? (
<SearchableSelect <SearchableSelect
id="chk-province" id="chk-city"
options={IRAN_PROVINCES} options={PROVINCE_CITIES[manualProvince]}
value={manualProvince} value={manualCity}
placeholder="انتخاب استان..." placeholder="انتخاب شهر..."
searchPlaceholder="جستجوی استان..." searchPlaceholder="جستجوی شهر..."
onChange={(prov) => { onChange={(c) => setManualCity(c)}
const cities = PROVINCE_CITIES[prov] || [];
setManualProvince(prov);
setManualCity(cities[0] || '');
}}
/> />
</div> ) : (
<div className="space-y-2">
<label htmlFor="chk-city" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block">شهر *</label>
{manualProvince && PROVINCE_CITIES[manualProvince] ? (
<SearchableSelect
id="chk-city"
options={PROVINCE_CITIES[manualProvince]}
value={manualCity}
placeholder="انتخاب شهر..."
searchPlaceholder="جستجوی شهر..."
onChange={(c) => setManualCity(c)}
/>
) : (
<input
id="chk-city"
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"
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
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"
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>
<input <input
id="chk-zipcode" id="chk-city"
type="text" type="text"
maxLength={10} value={manualCity}
value={manualZipCode} onChange={(e) => setManualCity(e.target.value)}
onChange={(e) => setManualZipCode(e.target.value.replace(/[^0-9]/g, ''))} 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"
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" placeholder="نام شهر..."
placeholder="۱۲۳۴۵۶۷۸۹۰"
/> />
</div> )}
</div>
<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/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>
<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/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> </div>
)} )}
</section> </section>
{/* Pet Selection */} {/* Pet Selection (if user has pets) */}
{pets.length > 0 && ( {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"> <section className="bg-white rounded-2xl border border-medical-gray-200 p-4 shadow-xs">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center gap-2 mb-2.5">
<div className="flex items-center gap-3"> <span className="text-base">🐾</span>
<div className="w-10 h-10 bg-purple-100 text-purple-700 rounded-2xl flex items-center justify-center font-black">🐾</div> <h3 className="text-xs font-black text-medical-gray-900">اتصال سفارش به پرونده سلامت حیوان خانگی:</h3>
<div> </div>
<h2 className="text-lg font-black text-medical-gray-900 italic">انتخاب همدم (حیوان خانگی)</h2> <div className="flex flex-wrap gap-2">
<p className="text-xs text-medical-gray-500">برای ثبت سابقه خرید در پرونده سلامت پت، همدم موردنظر را انتخاب کنید:</p> <button
</div> type="button"
</div> onClick={() => setSelectedPetId(null)}
</div> className={cn(
"px-3 py-1.5 rounded-xl border text-xs font-bold transition-all cursor-pointer",
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3"> 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"
<button )}
type="button" >
onClick={() => setSelectedPetId(null)} خرید عمومی (بدون پت)
className={cn( </button>
"p-3 rounded-2xl border-2 text-xs font-black transition-all flex items-center justify-center gap-2 cursor-pointer", {pets.map(p => (
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" <button
)} key={p.id}
> type="button"
<span>هیچکدام (خرید عمومی)</span> onClick={() => setSelectedPetId(p.id)}
</button>
{pets.map(p => (
<button
key={p.id}
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"
)}
>
<span>🐾 {p.name} ({p.breed || p.type})</span>
</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>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
{(() => {
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: '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" />,
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: '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" />,
enabled: getText('PAY_GATEWAY_COD_ENABLE', 'false') === 'true'
}
];
const sortedGateways = [...allGateways].sort((a, b) => (b.enabled ? 1 : 0) - (a.enabled ? 1 : 0));
return sortedGateways.map((pay) => (
<div
key={pay.id}
onClick={() => {
if (pay.enabled) {
setPaymentMethod(pay.id);
} else {
toast.info("این روش پرداخت به زودی فعال خواهد شد.");
}
}}
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",
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"
)}
>
<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">
{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>
<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>
{pay.id === 'wallet' && pay.enabled && (
<button
type="button"
onClick={(e) => {
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"
>
<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" />
<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>
<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="کپی شماره کارت"
>
<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>
<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" />
</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>
</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
onClick={handleCharityToggle}
className={cn( 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", "px-3 py-1.5 rounded-xl border text-xs font-bold transition-all cursor-pointer",
useRoundUp ? "bg-pink-500 text-white shadow-pink-200" : "bg-white text-medical-gray-300 border-2 border-medical-gray-100" 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"
)} )}
> >
<PlusCircle className={cn("w-7 h-7 sm:w-10 sm:h-10", useRoundUp ? "rotate-45" : "rotate-0")} /> {p.name} ({p.breed || p.type})
</button> </button>
</div> ))}
</div>
</section>
)}
{/* 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>
{useRoundUp && (
<motion.div <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
initial={{ opacity: 0, y: 10 }} {(() => {
animate={{ opacity: 1, y: 0 }} const getText = useSettingsStore.getState().getText;
className="mt-6 pt-6 border-t border-pink-100 flex items-center justify-between" const allGateways = [
> {
<span className="text-sm font-black text-pink-600 italic">مبلغ اهدایی شما:</span> id: 'online',
<span className="text-lg font-black font-vazir text-pink-500">{toPersian(charityDonation.toLocaleString())} تومان</span> label: getText('PAY_GATEWAY_ONLINE_LABEL', 'درگاه پرداخت اینترنتی (زیبال / شتاب)'),
</motion.div> desc: 'پرداخت آنلاین و آنی با کلیه کارت‌های شتاب',
)} icon: <ShieldCheck className="w-5 h-5" />,
enabled: getText('PAY_GATEWAY_ONLINE_ENABLE', 'true') === 'true'
<p className="mt-6 text-[10px] text-center text-medical-gray-400 font-bold">«با خرید این محصول، شما هم در درمان یک حیوان پناهگاهی سهیم شدید.»</p> },
</section> {
id: 'wallet',
label: getText('PAY_GATEWAY_WALLET_LABEL', 'اعتبار حساب (کیف پول)'),
desc: `موجودی: ${toPersian((profile.walletBalance || 0).toLocaleString())} تومان`,
icon: <Wallet className="w-5 h-5" />,
enabled: getText('PAY_GATEWAY_WALLET_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" />,
enabled: getText('PAY_GATEWAY_COD_ENABLE', 'false') === 'true'
}
];
const sortedGateways = [...allGateways].sort((a, b) => (b.enabled ? 1 : 0) - (a.enabled ? 1 : 0));
return sortedGateways.map((pay) => (
<div
key={pay.id}
onClick={() => {
if (pay.enabled) {
setPaymentMethod(pay.id);
} else {
toast.info("این روش پرداخت به زودی فعال خواهد شد.");
}
}}
className={cn(
"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 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="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">
<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>
{pay.id === 'wallet' && pay.enabled && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setShowTopUpModal(true);
}}
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"
>
+ افزایش
</button>
)}
</div>
));
})()}
</div>
{isCardToCardEnabled && paymentMethod === 'card' && (
<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 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="text-[10px] text-canina-blue hover:underline font-bold"
>
کپی
</button>
</div>
<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>
</div>
</div>
</div>
)}
</section>
{/* 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>
<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>
<button
type="button"
onClick={handleCharityToggle}
className={cn(
"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"
)}
>
{useRoundUp ? `اهدای ${toPersian(charityDonation.toLocaleString())} ت` : 'مشارکت در خیریه'}
</button>
</section>
</div> </div>
{/* Sidebar Summary */} {/* Sticky Order Summary Sidebar (4 cols) */}
<div className="lg:col-span-4 space-y-8"> <div className="lg:col-span-4 w-full">
<div className="bg-medical-gray-900 rounded-[3rem] p-10 text-white shadow-2xl sticky top-32"> <div className="bg-medical-gray-900 rounded-2xl p-5 text-white shadow-xl sticky top-24 space-y-4">
<h3 className="text-2xl font-black italic mb-8 border-b border-white/20 pb-4 text-white">خلاصه سفارش</h3> <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>
<div className="space-y-6 mb-10 overflow-y-auto max-h-60 pr-2"> {/* Items List (Compact) */}
<div className="space-y-2.5 max-h-48 overflow-y-auto pr-1">
{items.map((item) => ( {items.map((item) => (
<div key={item.product.id} className="flex justify-between items-start gap-4"> <div key={item.product.id} className="flex justify-between items-center text-xs gap-2">
<div className="flex-1"> <div className="min-w-0 flex-1">
<p className="text-sm font-black text-white leading-tight">{item.product.name}</p> <p className="font-bold text-white/90 truncate">{item.product.name}</p>
<p className="text-[10px] text-white/60 font-bold mt-1">تعداد: {toPersian(item.quantity)} بسته</p> <p className="text-[10px] text-white/50">{toPersian(item.quantity)} بسته</p>
</div> </div>
<span className="text-xs font-mono font-bold font-vazir text-white">{toPersian((item.product.priceValue * item.quantity).toLocaleString())} تومان</span> <span className="font-bold font-vazir text-white whitespace-nowrap">
{toPersian((item.product.priceValue * item.quantity).toLocaleString())} ت
</span>
</div> </div>
))} ))}
</div> </div>
<div className="space-y-4 pt-8 border-t border-white/20"> {/* Cost Breakdown */}
<div className="flex justify-between text-sm font-bold text-white/80"> <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>مجموع اقلام</span>
<span className="font-vazir text-white">{toPersian(getSubtotal().toLocaleString())} تومان</span> <span className="font-vazir text-white">{toPersian(getSubtotal().toLocaleString())} تومان</span>
</div> </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"> {isRefillEnabled && isSubscribed && (
<span>رزرو هوشمند تمدید (پاداش ۵٪ خرید بعدی):</span> <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 className="font-vazir font-black text-green-400">رزرو شد ({toPersian(Math.round(getSubtotal() * 0.05).toLocaleString())} تومان)</span> <span>رزرو تمدید ({toPersian(refillPercent)}٪ پاداش خرید بعد):</span>
<span className="font-vazir">{toPersian(Math.round(getSubtotal() * (refillPercent / 100)).toLocaleString())} ت</span>
</div> </div>
)} )}
{charityDonation > 0 && ( {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>ردپای مهربانی</span>
<span className="font-vazir text-pink-400">{toPersian(charityDonation.toLocaleString())} تومان+</span> <span className="font-vazir">{toPersian(charityDonation.toLocaleString())} تومان+</span>
</div> </div>
)} )}
<div className="flex justify-between text-sm font-bold text-white/80">
<div className="flex justify-between text-white/80">
<span>هزینه ارسال</span> <span>هزینه ارسال</span>
{shippingFee > 0 ? ( {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>
<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"> <div className="text-left">
<p className="text-3xl font-black text-canina-gold leading-none font-vazir">{toPersian((getTotal() + shippingFee).toLocaleString())}</p> <span className="text-2xl font-black text-canina-gold leading-none font-vazir">
<p className="text-[10px] text-white/50 font-bold mt-1">تومان</p> {toPersian((getTotal() + shippingFee).toLocaleString())}
</span>
<span className="text-[10px] text-white/60 font-bold mr-1">تومان</span>
</div> </div>
</div> </div>
</div> </div>
{/* Discount Code */} {/* Discount Code Input */}
<div className="mt-8 pt-8 border-t border-white/10"> <div className="pt-1">
<form <form
className="flex gap-2" className="flex gap-2"
onSubmit={async (e) => { onSubmit={async (e) => {
@ -751,9 +687,9 @@ export default function CheckoutPage() {
const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value; const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value;
const success = await useCartStore.getState().applyCoupon(code); const success = await useCartStore.getState().applyCoupon(code);
if (success) { if (success) {
toast.success("تبریک! کد تخفیف با موفقیت اعمال شد."); toast.success("کد تخفیف با موفقیت اعمال شد.");
} else { } else {
toast.error("کد تخفیف معتبر نیست."); toast.error("کد تخفیف نامعتبر است.");
} }
}} }}
> >
@ -762,63 +698,35 @@ export default function CheckoutPage() {
name="coupon" name="coupon"
type="text" type="text"
autoComplete="off" autoComplete="off"
placeholder="کد تخفیف (مثلاً: CANINO2024)" placeholder="کد تخفیف..."
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" 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> </form>
</div> </div>
{/* Refill Estimates */} {/* Submit Button */}
<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>
<button <button
onClick={handleFinalize} onClick={handleFinalize}
disabled={loading} 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 ? ( {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" />
در حال ثبت... در حال ثبت...
</> </>
) : ( ) : (
<> <>
پرداخت و تکمیل سفارش <span>پرداخت و تکمیل سفارش</span>
<ArrowRight className="w-6 h-6 rotate-180" /> <ArrowRight className="w-4 h-4 rotate-180" />
</> </>
)} )}
</button> </button>
<div className="mt-6 flex items-center justify-center gap-2 text-[10px] font-black text-white/20 tracking-widest uppercase"> <div className="flex items-center justify-center gap-1.5 text-[10px] font-bold text-white/30 tracking-wide pt-1">
<ShieldCheck className="w-4 h-4" /> <ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
تضمین امنیت تراکنش بانکی <span>تضمین امنیت پرداخت و اصالت کالا</span>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@ -1,7 +1,7 @@
# Graph Report - canina (2026-08-22) # Graph Report - canina (2026-08-22)
## Corpus Check ## Corpus Check
- 539 files · ~762,735 words - 539 files · ~762,770 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## Summary
@ -10,7 +10,7 @@
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
## Graph Freshness ## 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 `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost). - Run `graphify update .` after code changes (no API cost).
@ -67,7 +67,7 @@
- SmartAdvisorService - SmartAdvisorService
- TestimonialsService - TestimonialsService
- Role & Core Objective - Role & Core Objective
- .handleZibalCallback - .initiateOrderPayment
- ZibalEBankService - ZibalEBankService
- compilerOptions - compilerOptions
- Media.tsx - Media.tsx
@ -294,12 +294,12 @@
- useSettingsStore - useSettingsStore
- @types/supertest - @types/supertest
- typescript - typescript
- wiki/page.tsx - dashboard/page.tsx
- CreateHealthLogDto - CreateHealthLogDto
- eslint-config-prettier - eslint-config-prettier
- CreateReminderDto - CreateReminderDto
- track/page.tsx - track/page.tsx
- eslint-config-next - @types/react-dom
- eslint-plugin-prettier - eslint-plugin-prettier
- RouteErrorBoundary - RouteErrorBoundary
- globals - globals
@ -374,8 +374,8 @@ Cohesion: 0.11
Nodes (18): FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+10 more) Nodes (18): FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+10 more)
### Community 9 - "Roles" ### Community 9 - "Roles"
Cohesion: 0.20 Cohesion: 0.24
Nodes (15): Roles(), PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body (+7 more) Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more)
### Community 10 - "DoctorQueryDto" ### Community 10 - "DoctorQueryDto"
Cohesion: 0.08 Cohesion: 0.08
@ -467,7 +467,7 @@ Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Inj
### Community 32 - "ZibalService" ### Community 32 - "ZibalService"
Cohesion: 0.09 Cohesion: 0.09
Nodes (5): Put, PaymentService, Injectable, Injectable, ZibalService Nodes (4): PaymentService, Injectable, Injectable, ZibalService
### Community 33 - "راهنمای تست سیستم (Software Testing)" ### Community 33 - "راهنمای تست سیستم (Software Testing)"
Cohesion: 0.07 Cohesion: 0.07
@ -545,9 +545,9 @@ Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.09 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) 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" ### Community 52 - ".initiateOrderPayment"
Cohesion: 0.25 Cohesion: 0.20
Nodes (7): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Res, Headers, Ip Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more)
### Community 54 - "compilerOptions" ### Community 54 - "compilerOptions"
Cohesion: 0.06 Cohesion: 0.06
@ -586,7 +586,7 @@ Cohesion: 0.14
Nodes (16): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more) Nodes (16): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
### Community 63 - "ApiOperation" ### Community 63 - "ApiOperation"
Cohesion: 0.17 Cohesion: 0.19
Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
### Community 64 - "BannersService" ### Community 64 - "BannersService"
@ -659,7 +659,7 @@ Nodes (16): BlogPost, BlogPreviewSection(), ProductDetailModalProps, SafeImage()
### Community 81 - "devDependencies" ### Community 81 - "devDependencies"
Cohesion: 0.13 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" ### Community 82 - "auth.controller.ts"
Cohesion: 0.18 Cohesion: 0.18
@ -726,8 +726,8 @@ Cohesion: 0.15
Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more) Nodes (13): jest, collectCoverageFrom, coverageDirectory, moduleFileExtensions, rootDir, testEnvironment, testRegex, transform (+5 more)
### Community 98 - "AdminService" ### Community 98 - "AdminService"
Cohesion: 0.13 Cohesion: 0.18
Nodes (5): Delete, Param, Put, AdminService, Injectable Nodes (4): Delete, Param, AdminService, Injectable
### Community 99 - "Comprehensive Change Log" ### Community 99 - "Comprehensive Change Log"
Cohesion: 0.15 Cohesion: 0.15
@ -1042,8 +1042,8 @@ Cohesion: 0.29
Nodes (6): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength Nodes (6): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength
### Community 215 - "AdminController" ### Community 215 - "AdminController"
Cohesion: 0.25 Cohesion: 0.14
Nodes (5): AdminController, ApiBearerAuth, ApiTags, Controller, UseGuards Nodes (6): AdminController, ApiBearerAuth, ApiTags, Controller, Put, UseGuards
### Community 220 - "getPageMetadata" ### Community 220 - "getPageMetadata"
Cohesion: 0.13 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`?** - **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._ _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._ _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`, `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._ _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) # Graph Report - canina (2026-08-22)
## Corpus Check ## Corpus Check
- 539 files · ~762,770 words - 539 files · ~762,408 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
## Summary ## 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) - Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 262 edges (avg confidence: 0.79)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
## Graph Freshness ## 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 `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost). - Run `graphify update .` after code changes (no API cost).
@ -21,7 +21,7 @@
- app.module.ts - app.module.ts
- reviews.controller.ts - reviews.controller.ts
- tickets.controller.ts - tickets.controller.ts
- AuthService - faq.module.ts
- MediaSelector.tsx - MediaSelector.tsx
- PetProfile.tsx - PetProfile.tsx
- Roles - Roles
@ -67,7 +67,7 @@
- SmartAdvisorService - SmartAdvisorService
- TestimonialsService - TestimonialsService
- Role & Core Objective - Role & Core Objective
- .initiateOrderPayment - .handleZibalCallback
- ZibalEBankService - ZibalEBankService
- compilerOptions - compilerOptions
- Media.tsx - Media.tsx
@ -134,7 +134,7 @@
- compilerOptions - compilerOptions
- compilerOptions - compilerOptions
- backend/README.md - backend/README.md
- devDependencies - eslint
- Repository Map - Repository Map
- validate_integrity.js - validate_integrity.js
- admin-panel/package.json - admin-panel/package.json
@ -189,7 +189,7 @@
- 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend) - 🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)
- 🚀 SEO & Content Strategy Review (12_seo_content) - 🚀 SEO & Content Strategy Review (12_seo_content)
- @types/node - @types/node
- MetricsController - wiki/page.tsx
- seed-ui-texts.ts - seed-ui-texts.ts
- seed-wiki.ts - seed-wiki.ts
- update-blog.dto.ts - update-blog.dto.ts
@ -250,14 +250,14 @@
- ProductDto - ProductDto
- @testing-library/react - @testing-library/react
- @types/react - @types/react
- typescript - bcryptjs
- vitest - vitest
- Body - Body
- WholesaleApplyDto - WholesaleApplyDto
- ts-loader - ts-loader
- VerifyOtpDto - VerifyOtpDto
- @types/bcrypt - @types/bcrypt
- app.e2e-spec.js - helmet
- blog.entity.ts - blog.entity.ts
- home.entity.ts - home.entity.ts
- wiki.entity.ts - wiki.entity.ts
@ -290,26 +290,41 @@
- Shabnam Font Sample - Shabnam Font Sample
- Production Docker Compose - Production Docker Compose
- Staging Docker Compose - Staging Docker Compose
- @types/passport-jwt - devDependencies
- useSettingsStore - useSettingsStore
- @types/supertest - js-yaml
- typescript - @nestjs/core
- dashboard/page.tsx - @nestjs/jwt
- CreateHealthLogDto - CreateHealthLogDto
- eslint-config-prettier - eslint-config-prettier
- CreateReminderDto - CreateReminderDto
- track/page.tsx - track/page.tsx
- @types/react-dom - @types/react-dom
- eslint-plugin-prettier - @nestjs/swagger
- @nestjs/throttler
- RouteErrorBoundary - RouteErrorBoundary
- passport-jwt
- @prisma/client
- swagger-ui-express
- globals - globals
- admin.module.ts - admin.module.ts
- @eslint/eslintrc
- tailwindcss - 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) ## God Nodes (most connected - your core abstractions)
1. `Roles()` - 105 edges 1. `Roles()` - 105 edges
2. `PrismaService` - 87 edges 2. `PrismaService` - 87 edges
3. `useSettingsStore` - 51 edges 3. `useSettingsStore` - 53 edges
4. `api` - 43 edges 4. `api` - 43 edges
5. `SmsService` - 42 edges 5. `SmsService` - 42 edges
6. `PaginationDto` - 41 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] - **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] - **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" ### Community 0 - "OrdersService"
Cohesion: 0.06 Cohesion: 0.06
Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more) Nodes (35): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+27 more)
### Community 1 - "productService.ts" ### Community 1 - "productService.ts"
Cohesion: 0.10 Cohesion: 0.09
Nodes (24): BlogPost, CatalogPageSpread(), CatalogPageSpreadProps, CategoryMeta, getFormBadge(), getSpeciesBadge(), FlipbookCatalog(), FlipbookCatalogProps (+16 more) Nodes (28): BlogPost, CatalogPageSpread(), CatalogPageSpreadProps, CategoryMeta, getFormBadge(), getSpeciesBadge(), FlipbookCatalog(), FlipbookCatalogProps (+20 more)
### Community 2 - "CmsController" ### Community 2 - "CmsController"
Cohesion: 0.09 Cohesion: 0.09
Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more) Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 3 - "app.module.ts" ### Community 3 - "app.module.ts"
Cohesion: 0.07 Cohesion: 0.06
Nodes (36): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+28 more) Nodes (34): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+26 more)
### Community 4 - "reviews.controller.ts" ### Community 4 - "reviews.controller.ts"
Cohesion: 0.07 Cohesion: 0.07
@ -370,16 +385,16 @@ Cohesion: 0.07
Nodes (26): ConfirmModal(), ConfirmModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, BlogPost, Category (+18 more) Nodes (26): ConfirmModal(), ConfirmModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, BlogPost, Category (+18 more)
### Community 8 - "PetProfile.tsx" ### Community 8 - "PetProfile.tsx"
Cohesion: 0.11 Cohesion: 0.10
Nodes (18): FeaturedProducts(), ProductCard(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+10 more) Nodes (19): FeaturedProducts(), ProductCard(), Header(), OrderSuccess(), PrescriptionUploadModal(), PrescriptionUploadModalProps, SearchResultsPage(), OrderRowSkeleton() (+11 more)
### Community 9 - "Roles" ### Community 9 - "Roles"
Cohesion: 0.24 Cohesion: 0.20
Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more) Nodes (15): Roles(), PaymentController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body (+7 more)
### Community 10 - "DoctorQueryDto" ### Community 10 - "DoctorQueryDto"
Cohesion: 0.08 Cohesion: 0.09
Nodes (26): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+18 more) Nodes (24): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 11 - "MenuService" ### Community 11 - "MenuService"
Cohesion: 0.12 Cohesion: 0.12
@ -390,8 +405,8 @@ Cohesion: 0.09
Nodes (16): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, Ticket (+8 more) Nodes (16): App(), HeroBanner, VetTestimonial, ContactInfoItem, ContactSubmission, CategoryDist, DashboardData, Ticket (+8 more)
### Community 13 - "PrismaService" ### Community 13 - "PrismaService"
Cohesion: 0.08 Cohesion: 0.07
Nodes (18): MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto, MenuType (+10 more) Nodes (25): ApiExcludeController, MetricsController, Controller, Get, Res, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions (+17 more)
### Community 14 - "pets/pets.controller.ts" ### Community 14 - "pets/pets.controller.ts"
Cohesion: 0.16 Cohesion: 0.16
@ -402,8 +417,8 @@ Cohesion: 0.13
Nodes (12): ProductsController, ApiOperation, ApiTags, Body, Controller, Get, Param, Patch (+4 more) Nodes (12): ProductsController, ApiOperation, ApiTags, Body, Controller, Get, Param, Patch (+4 more)
### Community 16 - "UserDashboard.tsx" ### Community 16 - "UserDashboard.tsx"
Cohesion: 0.09 Cohesion: 0.11
Nodes (41): VerifyContent(), AddressModal(), AddressModalProps, ArchiveProductCard(), AuthModal(), AuthModalProps, extractOtpFromText(), B2BPortal() (+33 more) Nodes (31): VerifyContent(), AddressModal(), AddressModalProps, ArchiveProductCard(), B2BPortal(), BackButton(), BackButtonProps, CartDrawer() (+23 more)
### Community 17 - "CreateVideoDto" ### Community 17 - "CreateVideoDto"
Cohesion: 0.08 Cohesion: 0.08
@ -467,7 +482,7 @@ Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Inj
### Community 32 - "ZibalService" ### Community 32 - "ZibalService"
Cohesion: 0.09 Cohesion: 0.09
Nodes (4): PaymentService, Injectable, Injectable, ZibalService Nodes (5): Put, PaymentService, Injectable, Injectable, ZibalService
### Community 33 - "راهنمای تست سیستم (Software Testing)" ### Community 33 - "راهنمای تست سیستم (Software Testing)"
Cohesion: 0.07 Cohesion: 0.07
@ -545,9 +560,9 @@ Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body,
Cohesion: 0.09 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) 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" ### Community 52 - ".handleZibalCallback"
Cohesion: 0.20 Cohesion: 0.25
Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more) Nodes (7): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, Res, Headers, Ip
### Community 54 - "compilerOptions" ### Community 54 - "compilerOptions"
Cohesion: 0.06 Cohesion: 0.06
@ -566,20 +581,20 @@ Cohesion: 0.10
Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more) Nodes (14): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+6 more)
### Community 58 - "PaginationDto" ### Community 58 - "PaginationDto"
Cohesion: 0.06 Cohesion: 0.05
Nodes (24): BlogsService, Injectable, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder (+16 more) Nodes (24): BlogsService, Injectable, BlogsModule, Module, BlogsService, Injectable, PaginationDto, SortOrder (+16 more)
### Community 59 - "dependencies" ### Community 59 - "dependencies"
Cohesion: 0.05 Cohesion: 0.10
Nodes (41): dependencies, bcrypt, bcryptjs, class-transformer, class-validator, helmet, ioredis, js-yaml (+33 more) Nodes (21): dependencies, bcrypt, class-transformer, class-validator, ioredis, @nestjs/common, @nestjs/passport, @nestjs/platform-express (+13 more)
### Community 60 - "compilerOptions" ### Community 60 - "compilerOptions"
Cohesion: 0.10 Cohesion: 0.10
Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more) Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib, module, moduleDetection, moduleResolution, noEmit (+12 more)
### Community 61 - "HomeClient.tsx" ### Community 61 - "HomeClient.tsx"
Cohesion: 0.11 Cohesion: 0.13
Nodes (20): HomeClient(), HomeClientProps, CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, FAQItem, FAQSection() (+12 more) Nodes (18): HomeClient(), HomeClientProps, CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, Hero(), StatCounter() (+10 more)
### Community 62 - "BlogsController" ### Community 62 - "BlogsController"
Cohesion: 0.14 Cohesion: 0.14
@ -659,7 +674,7 @@ Nodes (16): BlogPost, BlogPreviewSection(), ProductDetailModalProps, SafeImage()
### Community 81 - "devDependencies" ### Community 81 - "devDependencies"
Cohesion: 0.13 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" ### Community 82 - "auth.controller.ts"
Cohesion: 0.18 Cohesion: 0.18
@ -801,10 +816,6 @@ Nodes (9): compilerOptions, esModuleInterop, module, moduleResolution, skipLibCh
Cohesion: 0.20 Cohesion: 0.20
Nodes (9): Compile and run the project, Deployment, Description, License, Project setup, Resources, Run tests, Stay in touch (+1 more) 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" ### Community 120 - "Repository Map"
Cohesion: 0.20 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) 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) Nodes (9): PetsController, ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiTags, Controller, UploadedFile, UseGuards (+1 more)
### Community 142 - "lib/services/api.ts" ### Community 142 - "lib/services/api.ts"
Cohesion: 0.08 Cohesion: 0.06
Nodes (19): ContactInfoItem, api, ApiErrorPayload, baseURL, ApiErr, AuthResponse, User, ApiErr (+11 more) Nodes (23): ContactInfoItem, FAQItem, FAQSection(), api, ApiErrorPayload, baseURL, ApiErr, AuthResponse (+15 more)
### Community 143 - "auth.service.ts" ### Community 143 - "auth.service.ts"
Cohesion: 0.14 Cohesion: 0.12
Nodes (8): AdminLoginInput, LoginInput, RegisterInput, RedisModule, Global, Module, RedisService, Injectable Nodes (8): AdminLoginInput, LoginInput, RegisterInput, RedisModule, Global, Module, RedisService, Injectable
### Community 144 - "Baseline Command Plan & Reconciled Command History" ### Community 144 - "Baseline Command Plan & Reconciled Command History"
@ -993,10 +1004,6 @@ Nodes (3): Architectural Overview, Critical Review Findings & Required Enhanceme
Cohesion: 0.50 Cohesion: 0.50
Nodes (3): Overview & Content Foundation, SEO & Content Requirements, 🚀 SEO & Content Strategy Review (12_seo_content) 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" ### Community 180 - "graphify reference: add a URL and watch a folder"
Cohesion: 0.50 Cohesion: 0.50
Nodes (3): For /graphify add, For --watch, graphify reference: add a URL and watch a folder 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 Nodes (3): Body, Post, CouponInput
### Community 239 - "WholesaleApplyDto" ### Community 239 - "WholesaleApplyDto"
Cohesion: 0.29 Cohesion: 0.17
Nodes (6): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto Nodes (9): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, app_module_1, supertest_1 (+1 more)
### Community 241 - "VerifyOtpDto" ### Community 241 - "VerifyOtpDto"
Cohesion: 0.29 Cohesion: 0.29
Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length Nodes (6): ApiProperty, IsNotEmpty, IsString, Matches, VerifyOtpDto, Length
### Community 243 - "app.e2e-spec.js" ### Community 290 - "devDependencies"
Cohesion: 0.50 Cohesion: 0.22
Nodes (3): app_module_1, supertest_1, testing_1 Nodes (9): devDependencies, eslint-plugin-prettier, @types/passport-jwt, @types/supertest, typescript, typescript, eslint-plugin-prettier, @types/passport-jwt (+1 more)
### Community 291 - "useSettingsStore" ### Community 291 - "useSettingsStore"
Cohesion: 0.13 Cohesion: 0.12
Nodes (20): ClientLayout(), BrandLogo(), BrandLogoProps, EnamadBadge(), Footer(), MENU_ICONS, MaintenancePage(), NetworkBanner() (+12 more) Nodes (24): ClientLayout(), AuthModal(), AuthModalProps, extractOtpFromText(), BrandLogo(), BrandLogoProps, EnamadBadge(), Footer() (+16 more)
### Community 297 - "CreateHealthLogDto" ### Community 297 - "CreateHealthLogDto"
Cohesion: 0.29 Cohesion: 0.29
@ -1100,22 +1107,22 @@ Nodes (16): AdminModule, Module, CategoryQuery, ReportsController, ApiBearerAuth
## Knowledge Gaps ## Knowledge Gaps
- **1274 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1269 more) - **1274 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1269 more)
These have ≤1 connection - possible missing edges or undocumented components. 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 ## Suggested Questions
_Questions this graph is uniquely positioned to answer:_ _Questions this graph is uniquely positioned to answer:_
- **Why does `ApiResponse` connect `ApiResponse` to `OrdersService`, `UsersService`, `PetsController`, `ProductsService`, `src/services/api.ts`, `AuthController`?** - **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._ _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._ _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._ _High betweenness centrality (0.040) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?** - **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
_1274 weakly-connected nodes found - possible documentation gaps or missing edges._ _1274 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `OrdersService` be split into smaller, more focused modules?** - **Should `OrdersService` be split into smaller, more focused modules?**
_Cohesion score 0.059932659932659935 - nodes in this community are weakly interconnected._ _Cohesion score 0.059932659932659935 - nodes in this community are weakly interconnected._
- **Should `productService.ts` be split into smaller, more focused modules?** - **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?** - **Should `CmsController` be split into smaller, more focused modules?**
_Cohesion score 0.0899854862119013 - nodes in this community are weakly interconnected._ _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