canina/frontend/application/components/UserDashboard.tsx
parsa aghaei 7615aa0e51 fix: resolve seed encoding, search param, and wiki data source issues
- Fix seed-products.ts TS error (implicit any) and BOM handling
- Re-run full seed to restore correct Persian encoding in DB
- Fix productService query→search param mismatch
- Fix IngredientWiki hardcoded port 4000→use settingsStore
- Restore seed-products-data.json from git after accidental corruption
2026-07-11 15:40:49 +03:30

666 lines
37 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { User, ShoppingBag, Wallet, MapPin, LogOut, ChevronRight, Package, Calendar, UserCircle, ShoppingCart, Trash2, Edit2, Phone, Hash, CheckCircle2, ArrowUpCircle, ArrowDownCircle, Info, Clock, CheckCircle, Heart, Sparkles } from "lucide-react";
import { OrderRowSkeleton } from "./Skeleton";
import { useUserStore, Address } from "../lib/store/userStore";
import { useCartStore } from "../lib/store/cartStore";
import { toPersian, cn } from "../lib/utils";
import { toast } from "sonner";
import OrderDetailsModal from "./OrderDetailsModal";
import AddressModal from "./AddressModal";
import DeleteConfirmModal from "./DeleteConfirmModal";
import TopUpModal from "./TopUpModal";
import { useRouter } from 'next/navigation';
export default function UserDashboard() {
const router = useRouter();
const { profile, logout, addAddress, updateAddress, deleteAddress, setDefaultAddress, topUpWallet } = useUserStore();
const { orders } = useCartStore();
const [activeTab, setActiveTab] = React.useState<"profile" | "orders" | "wallet" | "addresses">("profile");
const [isLoadingOrders, setIsLoadingOrders] = useState(false);
// Fetch fresh profile data (which includes orders) whenever orders tab is opened
React.useEffect(() => {
if (activeTab === "orders") {
setIsLoadingOrders(true);
useUserStore.getState().fetchProfile().finally(() => setIsLoadingOrders(false));
}
}, [activeTab]);
const [selectedOrder, setSelectedOrder] = useState<any>(null);
// Address State
const [isAddressModalOpen, setIsAddressModalOpen] = useState(false);
const [editingAddress, setEditingAddress] = useState<Address | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [addressToDelete, setAddressToDelete] = useState<Address | null>(null);
// Wallet State
const [isTopUpModalOpen, setIsTopUpModalOpen] = useState(false);
// Profile State
const [isEditing, setIsEditing] = useState(false);
const [isSavingProfile, setIsSavingProfile] = useState(false);
const [profileForm, setProfileForm] = useState({
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || "",
mobile: profile.mobile || ""
});
// Sync profile data on change
React.useEffect(() => {
setProfileForm({
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || "",
mobile: profile.mobile || ""
});
}, [profile]);
const handleSaveProfile = async (e: React.FormEvent) => {
e.preventDefault();
if (!isEditing) return;
const cleanPhone = profileForm.mobile.trim();
if (!/^09\d{9}$/.test(cleanPhone)) {
toast.error("شماره موبایل وارد شده معتبر نیست (باید ۱۱ رقم باشد و با ۰۹ شروع شود)");
return;
}
setIsSavingProfile(true);
try {
await useUserStore.getState().updateProfile({
firstName: profileForm.firstName,
lastName: profileForm.lastName,
email: profileForm.email,
mobile: cleanPhone
});
setIsEditing(false);
toast.success("اطلاعات کاربری با موفقیت ویرایش شد");
} catch (err: any) {
toast.error(err.message || "خطا در ویرایش اطلاعات");
} finally {
setIsSavingProfile(false);
}
};
const handleLogout = () => {
logout();
router.push('/');
};
const handleSaveAddress = async (addr: Address) => {
try {
if (editingAddress) {
await updateAddress(addr.id, addr);
} else {
await addAddress(addr);
}
} catch (err) {
toast.error("خطا در ثبت آدرس");
}
setEditingAddress(null);
};
const handleDeleteClick = (addr: Address) => {
setAddressToDelete(addr);
setIsDeleteModalOpen(true);
};
const handleConfirmDelete = async () => {
if (addressToDelete) {
try {
await deleteAddress(addressToDelete.id);
toast.success("آدرس با موفقیت حذف شد");
} catch (err) {
toast.error("خطا در حذف آدرس");
}
setAddressToDelete(null);
}
};
const handleEditClick = (addr: Address) => {
setEditingAddress(addr);
setIsAddressModalOpen(true);
};
const handleAddClick = () => {
setEditingAddress(null);
setIsAddressModalOpen(true);
};
const tabs = [
{ id: "profile", label: "اطلاعات فردی", icon: <UserCircle className="w-5 h-5" /> },
{ id: "orders", label: "سفارشات من", icon: <Package className="w-5 h-5" /> },
{ id: "wallet", label: "کیف پول", icon: <Wallet className="w-5 h-5" /> },
{ id: "addresses", label: "آدرس‌ها", icon: <MapPin className="w-5 h-5" /> },
];
return (
<div className="min-h-screen bg-medical-gray-50 pt-12 pb-24 px-4 font-vazir" dir="rtl">
<div className="max-w-6xl mx-auto">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
<ChevronRight className="w-2.5 h-2.5" />
<span className="text-canina-blue">پنل کاربری</span>
</div>
<div className="grid lg:grid-cols-12 gap-8">
{/* Sidebar */}
<div className="lg:col-span-4 space-y-6">
<div className="bg-white rounded-[3rem] border border-medical-gray-200 p-8 shadow-xl">
<div className="flex items-center gap-4 mb-10">
<div className="w-16 h-16 bg-canina-blue rounded-3xl flex items-center justify-center text-white shadow-lg">
<User className="w-8 h-8" />
</div>
<div>
<h2 className="text-xl font-black text-medical-gray-900">{profile.firstName} {profile.lastName}</h2>
<p className="text-sm font-bold text-medical-gray-400">{profile.email}</p>
</div>
</div>
<div className="space-y-2">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`w-full flex items-center gap-3 p-4 rounded-2xl transition-all font-bold ${activeTab === tab.id ? "bg-canina-blue text-white shadow-lg shadow-canina-blue/20" : "text-medical-gray-600 hover:bg-medical-gray-50"}`}
>
{tab.icon}
{tab.label}
</button>
))}
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 p-4 rounded-2xl text-red-500 hover:bg-red-50 transition-all font-bold mt-4"
>
<LogOut className="w-5 h-5" />
خروج از حساب
</button>
</div>
</div>
<div className="bg-medical-gray-900 rounded-[3rem] p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 left-0 p-6 opacity-20">
<Wallet className="w-12 h-12 text-canina-blue" />
</div>
<div className="relative z-10">
<div className="text-[10px] font-black uppercase tracking-widest text-white mb-2">موجودی کیف پول</div>
<div className="flex items-baseline gap-2 flex-row-reverse justify-end">
<div className="text-4xl font-black italic">{toPersian(profile.walletBalance.toLocaleString())}</div>
<span className="text-sm not-italic font-bold text-white/80">تومان</span>
</div>
<button
onClick={() => { setActiveTab("wallet"); setIsTopUpModalOpen(true); }}
disabled={isTopUpModalOpen}
className="mt-8 bg-canina-blue w-full py-4 rounded-2xl font-black hover:bg-white hover:text-canina-blue transition-all shadow-lg shadow-black/20 flex items-center justify-center gap-2"
>
شارژ کیف پول
</button>
</div>
</div>
<div className="bg-pink-500 rounded-[3rem] p-8 text-white relative overflow-hidden shadow-2xl shadow-pink-200">
<div className="absolute -top-4 -right-4 opacity-10">
<Heart className="w-40 h-40 fill-white" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-3 mb-6">
<Heart className="w-5 h-5 fill-white" />
<h4 className="text-sm font-black italic tracking-widest">ردپای مهربانی</h4>
</div>
<div className="space-y-6">
<div>
<p className="text-[10px] font-bold text-white/70 mb-1">مجموع کمکهای اهدایی</p>
<div className="flex items-baseline gap-2 flex-row-reverse justify-end">
<span className="text-3xl font-black italic">{toPersian(profile.charityDonationTotal?.toLocaleString() || "0")}</span>
<span className="text-xs font-bold opacity-60">تومان</span>
</div>
</div>
<div className="p-4 bg-white/10 rounded-2xl border border-white/20 backdrop-blur-sm">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-white/20 rounded-xl flex items-center justify-center">
<Sparkles className="w-4 h-4 text-white" />
</div>
<p className="text-[10px] font-black leading-tight">
همکاری شما باعث تامین هزینه <span className="text-sm text-yellow-300 mx-1">{toPersian(Math.floor((profile.charityDonationTotal || 0) / 15000).toString())} وعده غذا</span> برای حیوانات بیپناه شده است.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Main Content */}
<div className="lg:col-span-8">
<motion.div
key={activeTab}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-[3.5rem] border border-medical-gray-200 p-10 min-h-[600px] shadow-sm overflow-hidden"
>
{activeTab === "profile" && (
<div>
<h3 className="text-2xl font-black text-medical-gray-900 mb-8 italic">اطلاعات فردی</h3>
<form onSubmit={handleSaveProfile} className="space-y-8">
<div className="grid md:grid-cols-2 gap-8">
<div className="space-y-2">
<label htmlFor="firstName" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-4">نام</label>
<input
autoFocus={isEditing}
type="text"
id="firstName"
name="firstName"
autoComplete="given-name"
required
readOnly={!isEditing}
value={isEditing ? profileForm.firstName : profile.firstName}
onChange={e => setProfileForm({ ...profileForm, firstName: e.target.value })}
className={cn(
"w-full border p-4 rounded-2xl font-bold outline-none transition-all",
isEditing
? "bg-white border-canina-blue/30 focus:ring-2 focus:ring-canina-blue/20"
: "bg-medical-gray-50 border-medical-gray-100"
)}
/>
</div>
<div className="space-y-2">
<label htmlFor="lastName" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-4">نام خانوادگی</label>
<input
type="text"
id="lastName"
name="lastName"
autoComplete="family-name"
required
readOnly={!isEditing}
value={isEditing ? profileForm.lastName : profile.lastName}
onChange={e => setProfileForm({ ...profileForm, lastName: e.target.value })}
className={cn(
"w-full border p-4 rounded-2xl font-bold outline-none transition-all",
isEditing
? "bg-white border-canina-blue/30 focus:ring-2 focus:ring-canina-blue/20"
: "bg-medical-gray-50 border-medical-gray-100"
)}
/>
</div>
<div className="space-y-2">
<label htmlFor="email" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-4">ایمیل</label>
<input
type="email"
id="email"
name="email"
autoComplete="email"
required
readOnly={!isEditing}
value={isEditing ? profileForm.email : profile.email}
onChange={e => setProfileForm({ ...profileForm, email: e.target.value })}
className={cn(
"w-full border p-4 rounded-2xl font-bold outline-none transition-all text-left",
isEditing
? "bg-white border-canina-blue/30 focus:ring-2 focus:ring-canina-blue/20"
: "bg-medical-gray-50 border-medical-gray-100"
)}
dir="ltr"
/>
</div>
<div className="space-y-2">
<label htmlFor="mobile" className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-4">شماره موبایل</label>
<input
type="tel"
id="mobile"
name="mobile"
autoComplete="tel"
required
readOnly={!isEditing}
disabled={!isEditing}
value={isEditing ? profileForm.mobile : toPersian(profile.mobile || "")}
onChange={e => setProfileForm({ ...profileForm, mobile: e.target.value.replace(/[^0-9]/g, '') })}
className={cn(
"w-full border p-4 rounded-2xl font-bold outline-none transition-all text-left",
isEditing
? "bg-white border-canina-blue/30 focus:ring-2 focus:ring-canina-blue/20"
: "bg-medical-gray-50 border-medical-gray-100 opacity-75 cursor-not-allowed"
)}
dir="ltr"
/>
</div>
</div>
<div className="flex gap-4 mt-8">
{isEditing ? (
<>
<button
type="submit"
disabled={isSavingProfile}
className="bg-canina-blue text-white px-10 py-4 rounded-2xl font-black hover:bg-indigo-700 transition-all flex items-center gap-2 disabled:opacity-50"
>
{isSavingProfile && <span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />}
ذخیره تغییرات
</button>
<button
type="button"
onClick={() => {
setProfileForm({
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || "",
mobile: profile.mobile || ""
});
setIsEditing(false);
}}
className="bg-medical-gray-50 text-medical-gray-500 border border-medical-gray-200 px-10 py-4 rounded-2xl font-black hover:bg-medical-gray-100 transition-all"
>
انصراف
</button>
</>
) : (
<button
type="button"
onClick={() => setIsEditing(true)}
className="bg-medical-gray-900 text-white px-10 py-4 rounded-2xl font-black hover:bg-canina-blue transition-all"
>
ویرایش اطلاعات
</button>
)}
</div>
</form>
</div>
)}
{activeTab === "orders" && (
<div>
<div className="flex items-center justify-between mb-8">
<h3 className="text-2xl font-black text-medical-gray-900 italic">تاریخچه سفارشات</h3>
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest border border-medical-gray-100 px-4 py-2 rounded-full bg-medical-gray-50">
تعداد کل: {toPersian((orders || []).length.toString())} مورد
</div>
</div>
{(orders || []).length === 0 ? (
<div className="text-center py-20">
<ShoppingBag className="w-16 h-16 text-medical-gray-200 mx-auto mb-4" />
<p className="text-medical-gray-400 font-bold">هنوز سفارشی ثبت نکردهاید.</p>
<button onClick={() => router.push('/shop')} className="mt-6 text-canina-blue font-black underline underline-offset-8">برو به فروشگاه</button>
</div>
) : (
<div className="space-y-4">
{isLoadingOrders ? (
[1, 2, 3].map(i => <OrderRowSkeleton key={i} />)
) : (
(orders || []).map(order => (
<motion.div
key={order.id}
whileHover={{ scale: 1.01, x: -5 }}
onClick={() => setSelectedOrder(order)}
className="p-6 border border-medical-gray-100 rounded-[2.5rem] hover:border-canina-blue transition-all group cursor-pointer bg-white overflow-hidden relative"
>
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-medical-gray-50 rounded-2xl flex items-center justify-center text-canina-blue group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm">
<Package className="w-6 h-6" />
</div>
<div>
<div className="text-sm font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors italic">سفارش {toPersian(order.id.toUpperCase())}</div>
<div className="text-[10px] font-bold text-medical-gray-400 flex items-center gap-1">
<Calendar className="w-3 h-3 opacity-30" />
{toPersian(new Date(order.date).toLocaleDateString("fa-IR"))}
</div>
</div>
</div>
<div className="text-left">
<div className="text-lg font-black text-medical-gray-900 italic">{toPersian(order.total.toLocaleString())} <span className="text-xs">تومان</span></div>
<div className="flex items-center gap-2 justify-end mt-1">
<span className={`text-[9px] font-black px-2 py-0.5 rounded-full uppercase tracking-tighter ${order.status === 'delivered' ? 'bg-green-50 text-green-600' : 'bg-canina-blue/5 text-canina-blue'}`}>
{order.status === 'delivered' ? 'تحویل شده' : 'ارسال شده'}
</span>
<ChevronRight className="w-4 h-4 text-medical-gray-300 -rotate-180" />
</div>
</div>
</div>
</motion.div>
))
)}
</div>
)}
</div>
)}
{activeTab === "addresses" && (
<div>
<div className="flex items-center justify-between mb-8">
<h3 className="text-2xl font-black text-medical-gray-900 italic">آدرسهای من</h3>
<button
onClick={handleAddClick}
className="px-6 py-3 bg-canina-blue text-white rounded-2xl font-black text-xs flex items-center gap-2 hover:bg-medical-gray-900 transition-all shadow-lg shadow-canina-blue/10"
>
<MapPin className="w-4 h-4" />
افزودن آدرس جدید
</button>
</div>
<div className="space-y-6">
{(profile?.addresses || []).length === 0 ? (
<div className="text-center py-20 border-2 border-dashed border-medical-gray-100 rounded-[3rem]">
<MapPin className="w-16 h-16 text-medical-gray-200 mx-auto mb-4" />
<p className="text-medical-gray-400 font-bold">هنوز آدرسی ثبت نکردهاید.</p>
</div>
) : (
(profile?.addresses || []).map((addr) => (
<motion.div
key={addr.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className={cn(
"p-8 bg-white border rounded-[2.5rem] relative group transition-all",
addr.isDefault ? "border-canina-blue shadow-xl shadow-canina-blue/5" : "border-medical-gray-100 hover:border-medical-gray-200"
)}
>
<div className="flex justify-between items-start mb-6">
<div className="flex items-center gap-4">
<div className={cn("w-12 h-12 rounded-2xl flex items-center justify-center transition-all", addr.isDefault ? "bg-canina-blue text-white shadow-lg" : "bg-medical-gray-50 text-canina-blue")}>
<MapPin className="w-6 h-6" />
</div>
<div>
<div className="flex items-center gap-3">
<h4 className="text-xl font-black text-medical-gray-900 italic">{addr.title}</h4>
{addr.isDefault && (
<span className="px-3 py-1 bg-green-50 text-green-600 text-[9px] font-black rounded-full uppercase tracking-widest">پیشفرض</span>
)}
</div>
<p className="text-xs font-bold text-medical-gray-400">تحویل گیرنده: {addr.receptorName}</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handleEditClick(addr)}
className="w-10 h-10 flex items-center justify-center rounded-xl bg-medical-gray-50 text-medical-gray-400 hover:bg-canina-blue hover:text-white transition-all shadow-sm"
>
<Edit2 className="w-5 h-5" />
</button>
<button
onClick={() => handleDeleteClick(addr)}
className="w-10 h-10 flex items-center justify-center rounded-xl bg-medical-gray-50 text-medical-gray-400 hover:bg-red-500 hover:text-white transition-all shadow-sm"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
</div>
<div className="bg-medical-gray-50/50 rounded-2xl p-6 mb-6">
<p className="text-sm font-black text-medical-gray-700 leading-relaxed text-right">{toPersian(addr.province)}، {toPersian(addr.city)}، {toPersian(addr.detail)}</p>
</div>
<div className="flex flex-wrap items-center gap-6">
<div className="flex items-center gap-2">
<Phone className="w-4 h-4 text-canina-blue/40" />
<span className="text-xs font-bold text-medical-gray-500" dir="ltr">{toPersian(addr.phone)}</span>
</div>
<div className="flex items-center gap-2">
<Hash className="w-4 h-4 text-canina-blue/40" />
<span className="text-xs font-bold text-medical-gray-500" dir="ltr">{toPersian(addr.zipCode)}</span>
</div>
{!addr.isDefault && (
<button
onClick={async () => {
try {
await setDefaultAddress(addr.id);
toast.success("آدرس پیش‌فرض با موفقیت تغییر کرد");
} catch (err) {
toast.error("خطا در تغییر آدرس پیش‌فرض");
}
}}
className="mr-auto text-[10px] font-black text-canina-blue hover:text-medical-gray-900 uppercase tracking-widest whitespace-nowrap"
>
انتخاب به عنوان پیشفرض
</button>
)}
{addr.isDefault && (
<div className="mr-auto flex items-center gap-2 text-green-600 text-[10px] font-black italic">
<CheckCircle2 className="w-4 h-4" />
آدرس اصلی ارسال
</div>
)}
</div>
</motion.div>
))
)}
</div>
</div>
)}
{activeTab === "wallet" && (
<div className="space-y-10">
<div className="flex items-center justify-between">
<h3 className="text-2xl font-black text-medical-gray-900 italic">مدیریت کیف پول</h3>
<div className="text-[10px] font-black text-green-600 bg-green-50 px-4 py-2 rounded-full uppercase tracking-widest border border-green-100 flex items-center gap-2">
<CheckCircle className="w-3 h-3" />
حساب تایید شده
</div>
</div>
{/* Main Balance Card */}
<div className="bg-gradient-to-br from-canina-blue to-blue-600 rounded-[3rem] p-12 text-white shadow-2xl shadow-canina-blue/20 relative overflow-hidden group">
<div className="absolute top-0 right-0 p-12 opacity-10 group-hover:scale-110 transition-transform duration-700">
<Wallet className="w-48 h-48" />
</div>
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-10">
<div className="text-center md:text-right">
<div className="text-xs font-black uppercase tracking-[0.2em] text-white/70 mb-4 flex items-center gap-2 justify-center md:justify-start text-right">
<div className="w-2 h-2 bg-white rounded-full animate-pulse" />
موجودی زنده و قابل استفاده
</div>
<div className="flex items-baseline gap-4 flex-row-reverse justify-center md:justify-start">
<div className="text-6xl font-black italic tracking-tight">{toPersian(profile.walletBalance.toLocaleString())}</div>
<span className="text-xl not-italic font-bold opacity-80 decoration-white/30 underline underline-offset-8">تومان</span>
</div>
</div>
<div className="flex gap-4 w-full md:w-auto">
<div className="relative group/btn flex-1 md:flex-none">
<button
disabled
className="w-full md:w-40 h-16 bg-white/10 backdrop-blur-md rounded-2xl font-black text-sm flex items-center justify-center gap-2 border border-white/20 opacity-50 cursor-not-allowed"
>
<ArrowDownCircle className="w-5 h-5" />
برداشت وجه
</button>
<div className="absolute top-full right-0 mt-3 w-48 p-3 bg-medical-gray-900 text-white text-[10px] font-bold rounded-xl opacity-0 group-hover/btn:opacity-100 transition-all pointer-events-none z-20 text-center shadow-xl">
قابلیت برداشت وجه بهزودی فعال خواهد شد.
</div>
</div>
<button
onClick={() => setIsTopUpModalOpen(true)}
className="flex-1 md:w-40 h-16 bg-white text-canina-blue rounded-2xl font-black text-sm flex items-center justify-center gap-2 hover:bg-medical-gray-900 hover:text-white transition-all shadow-xl shadow-black/10 group/topup"
>
<ArrowUpCircle className="w-5 h-5 group-hover:-translate-y-1 transition-transform" />
شارژ آنی
</button>
</div>
</div>
</div>
{/* Transaction History */}
<div className="pt-10 border-t border-medical-gray-100">
<div className="flex items-center gap-4 mb-8">
<div className="w-10 h-10 bg-medical-gray-50 rounded-xl flex items-center justify-center text-medical-gray-400">
<Clock className="w-5 h-5" />
</div>
<h4 className="text-lg font-black text-medical-gray-900 italic">تاریخچه تراکنشها</h4>
</div>
<div className="space-y-4">
{(profile?.transactions || []).length === 0 ? (
<div className="text-center py-20 bg-medical-gray-50 rounded-[2.5rem] border border-dashed border-medical-gray-200">
<Info className="w-12 h-12 text-medical-gray-200 mx-auto mb-4" />
<p className="text-medical-gray-400 font-bold">تراکنشی یافت نشد.</p>
</div>
) : (
(profile?.transactions || []).map((trx) => (
<div key={trx.id} className="p-6 bg-white border border-medical-gray-100 rounded-[2rem] flex flex-wrap items-center justify-between gap-6 hover:shadow-lg hover:border-canina-blue/10 transition-all group">
<div className="flex items-center gap-4">
<div className={cn(
"w-12 h-12 rounded-2xl flex items-center justify-center transition-colors",
trx.type === 'top_up' ? "bg-green-50 text-green-500 group-hover:bg-green-500 group-hover:text-white" : "bg-red-50 text-red-500 group-hover:bg-red-500 group-hover:text-white"
)}>
{trx.type === 'top_up' ? <ArrowUpCircle className="w-6 h-6" /> : <ShoppingCart className="w-6 h-6" />}
</div>
<div>
<div className="text-sm font-black text-medical-gray-900">{trx.type === 'top_up' ? "افزایش موجودی (شارژ)" : "پرداخت سفارش"}</div>
<div className="text-[10px] font-bold text-medical-gray-400 flex items-center gap-2">
<span>کد پیگیری: {trx.id}</span>
<span className="w-1 h-1 bg-medical-gray-200 rounded-full" />
<span>{toPersian(new Date(trx.date).toLocaleDateString("fa-IR"))}</span>
</div>
</div>
</div>
<div className="text-left">
<div className={cn(
"text-lg font-black italic",
trx.type === 'top_up' ? "text-green-600" : "text-red-500"
)}>
{trx.type === 'top_up' ? "+" : ""}{toPersian(trx.amount.toLocaleString())} <span className="text-xs not-italic">تومان</span>
</div>
<div className="text-[9px] font-black uppercase text-green-500 tracking-widest mt-1">
تایید شده
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
)}
</motion.div>
</div>
</div>
</div>
<OrderDetailsModal
isOpen={!!selectedOrder}
onClose={() => setSelectedOrder(null)}
order={selectedOrder}
/>
<AddressModal
isOpen={isAddressModalOpen}
onClose={() => setIsAddressModalOpen(false)}
onSave={handleSaveAddress}
editingAddress={editingAddress}
/>
<DeleteConfirmModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
onConfirm={handleConfirmDelete}
title="حذف آدرس"
message={`آیا از حذف آدرس "${addressToDelete?.title}" اطمینان دارید؟ این عمل غیرقابل بازگشت است.`}
/>
<TopUpModal
isOpen={isTopUpModalOpen}
onClose={() => setIsTopUpModalOpen(false)}
onConfirm={topUpWallet}
/>
</div>
);
}