feat: add refill toggle in admin settings and compact checkout UI layout
All checks were successful
Deploy Canina / deploy (push) Successful in 1m31s
All checks were successful
Deploy Canina / deploy (push) Successful in 1m31s
This commit is contained in:
parent
6454c18071
commit
4e768d66a9
@ -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">
|
||||
|
||||
@ -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,31 +274,33 @@ export default function CartDrawer({ isOpen, onClose, onCheckout, onShopNavigate
|
||||
</div>
|
||||
|
||||
{/* Subscription Toggle */}
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isLoggedIn && !isSubscribed) {
|
||||
toast.info("برای فعالسازی سرویس تمدید خودکار و دریافت یادآورها، لطفاً ابتدا وارد حساب کاربری خود شوید.");
|
||||
setIsAuthModalOpen(true);
|
||||
return;
|
||||
}
|
||||
toggleSubscription();
|
||||
}}
|
||||
className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`}
|
||||
>
|
||||
<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'}`}>
|
||||
<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>
|
||||
</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" />}
|
||||
{isRefillEnabled && (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isLoggedIn && !isSubscribed) {
|
||||
toast.info("برای فعالسازی سرویس تمدید خودکار و دریافت یادآورها، لطفاً ابتدا وارد حساب کاربری خود شوید.");
|
||||
setIsAuthModalOpen(true);
|
||||
return;
|
||||
}
|
||||
toggleSubscription();
|
||||
}}
|
||||
className={`p-4 rounded-2xl border-2 transition-all cursor-pointer ${isSubscribed ? 'bg-canina-blue/5 border-canina-blue' : 'bg-white border-medical-gray-100 hover:border-canina-blue/30'}`}
|
||||
>
|
||||
<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'}`}>
|
||||
<ShieldCheck className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<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>
|
||||
</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">
|
||||
|
||||
@ -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">
|
||||
<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"
|
||||
>
|
||||
<span>بازگشت به سبد خرید</span>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</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">
|
||||
{/* 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 hover:text-canina-blue transition-colors cursor-pointer"
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<span>بازگشت</span>
|
||||
</button>
|
||||
<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-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">
|
||||
<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">
|
||||
|
||||
{/* 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 && (
|
||||
<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>
|
||||
|
||||
{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>
|
||||
<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">
|
||||
<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,425 +325,361 @@ 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>
|
||||
<input
|
||||
id="chk-fullname"
|
||||
name="name"
|
||||
type="text"
|
||||
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"
|
||||
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>
|
||||
<input
|
||||
id="chk-tel"
|
||||
name="tel"
|
||||
type="tel"
|
||||
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"
|
||||
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 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"
|
||||
type="text"
|
||||
autoComplete="name"
|
||||
value={manualName}
|
||||
onChange={(e) => setManualName(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-tel" className="text-[11px] font-bold text-medical-gray-700 block mb-1">شماره همراه *</label>
|
||||
<input
|
||||
id="chk-tel"
|
||||
name="tel"
|
||||
type="tel"
|
||||
autoComplete="tel"
|
||||
value={manualPhone}
|
||||
onChange={(e) => setManualPhone(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="۰۹۱۲XXXXXXX"
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
value={manualProvince}
|
||||
placeholder="انتخاب استان..."
|
||||
searchPlaceholder="جستجوی استان..."
|
||||
onChange={(prov) => {
|
||||
const cities = PROVINCE_CITIES[prov] || [];
|
||||
setManualProvince(prov);
|
||||
setManualCity(cities[0] || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<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-province"
|
||||
options={IRAN_PROVINCES}
|
||||
value={manualProvince}
|
||||
placeholder="انتخاب استان..."
|
||||
searchPlaceholder="جستجوی استان..."
|
||||
onChange={(prov) => {
|
||||
const cities = PROVINCE_CITIES[prov] || [];
|
||||
setManualProvince(prov);
|
||||
setManualCity(cities[0] || '');
|
||||
}}
|
||||
id="chk-city"
|
||||
options={PROVINCE_CITIES[manualProvince]}
|
||||
value={manualCity}
|
||||
placeholder="انتخاب شهر..."
|
||||
searchPlaceholder="جستجوی شهر..."
|
||||
onChange={(c) => setManualCity(c)}
|
||||
/>
|
||||
</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
|
||||
id="chk-zipcode"
|
||||
id="chk-city"
|
||||
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"
|
||||
placeholder="۱۲۳۴۵۶۷۸۹۰"
|
||||
value={manualCity}
|
||||
onChange={(e) => setManualCity(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>
|
||||
<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>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Pet Selection */}
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
<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"
|
||||
)}
|
||||
>
|
||||
<span>هیچکدام (خرید عمومی)</span>
|
||||
</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}
|
||||
{/* Pet Selection (if user has pets) */}
|
||||
{pets.length > 0 && (
|
||||
<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 className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPetId(null)}
|
||||
className={cn(
|
||||
"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"
|
||||
)}
|
||||
>
|
||||
خرید عمومی (بدون پت)
|
||||
</button>
|
||||
{pets.map(p => (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedPetId(p.id)}
|
||||
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 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"
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
</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>
|
||||
{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 className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{(() => {
|
||||
const getText = useSettingsStore.getState().getText;
|
||||
const allGateways = [
|
||||
{
|
||||
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" />,
|
||||
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>
|
||||
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
|
||||
<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) => (
|
||||
<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>
|
||||
<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="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 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>
|
||||
|
||||
@ -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
@ -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",
|
||||
|
||||
@ -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
@ -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._
|
||||
2
graphify-out/cache/stat-index.json
vendored
2
graphify-out/cache/stat-index.json
vendored
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
Loading…
Reference in New Issue
Block a user