fix(profile, checkout): redesign profile mobile layout tabs UX and fix wallet deduction API error

This commit is contained in:
parsa aghaei 2026-07-25 11:54:11 +03:30
parent 16ccd99010
commit a6ae1cf30a
2 changed files with 81 additions and 72 deletions

View File

@ -94,10 +94,13 @@ export default function CheckoutPage() {
date: new Date().toISOString(), date: new Date().toISOString(),
status: 'success' as const status: 'success' as const
}; };
await updateProfile({ useUserStore.setState((state) => ({
walletBalance: newBalance, profile: {
transactions: [newTransaction, ...(profile.transactions || [])] ...state.profile,
}); walletBalance: newBalance,
transactions: [newTransaction, ...(state.profile.transactions || [])]
}
}));
} }
// 2. Register the order in the backend // 2. Register the order in the backend
@ -110,9 +113,12 @@ export default function CheckoutPage() {
// 3. Update user charity total // 3. Update user charity total
if (charityDonation > 0) { if (charityDonation > 0) {
await updateProfile({ useUserStore.setState((state) => ({
charityDonationTotal: (profile.charityDonationTotal || 0) + charityDonation profile: {
}); ...state.profile,
charityDonationTotal: (state.profile.charityDonationTotal || 0) + charityDonation
}
}));
} }
// 4. Update pet consumptions for the refill logic // 4. Update pet consumptions for the refill logic

View File

@ -149,26 +149,26 @@ export default function UserDashboard() {
</div> </div>
<div className="grid lg:grid-cols-12 gap-6 sm:gap-8"> <div className="grid lg:grid-cols-12 gap-6 sm:gap-8">
{/* Sidebar */} {/* Top User Info & Navigation Tabs */}
<div className="lg:col-span-4 space-y-4 sm:space-y-6 min-w-0"> <div className="lg:col-span-12">
<div className="bg-white rounded-[1.5rem] sm:rounded-[3rem] border border-medical-gray-200 p-4 sm:p-8 shadow-xl"> <div className="bg-white rounded-[1.5rem] sm:rounded-[3rem] border border-medical-gray-200 p-4 sm:p-6 shadow-xl flex flex-col md:flex-row items-stretch md:items-center justify-between gap-4">
<div className="flex items-center gap-3 sm:gap-4 mb-6 sm:mb-10"> <div className="flex items-center gap-3 sm:gap-4">
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-canina-blue rounded-xl sm:rounded-3xl flex items-center justify-center text-white shadow-lg flex-shrink-0"> <div className="w-12 h-12 sm:w-14 sm:h-14 bg-canina-blue rounded-xl sm:rounded-2xl flex items-center justify-center text-white shadow-lg flex-shrink-0">
<User className="w-6 h-6 sm:w-8 sm:h-8" /> <User className="w-6 h-6 sm:w-7 sm:h-7" />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h2 className="text-base sm:text-xl font-black text-medical-gray-900 truncate">{profile.firstName} {profile.lastName}</h2> <h2 className="text-base sm:text-lg font-black text-medical-gray-900 truncate">{profile.firstName} {profile.lastName}</h2>
<p className="text-[11px] sm:text-sm font-bold text-medical-gray-400 truncate">{profile.email}</p> <p className="text-[11px] sm:text-xs font-bold text-medical-gray-400 truncate">{profile.email}</p>
</div> </div>
</div> </div>
{/* Responsive Tab Bar: Horizontal Scroll on Mobile, Vertical Stack on Desktop */} {/* Tab Navigation */}
<div className="flex lg:flex-col gap-2 overflow-x-auto lg:overflow-x-visible pb-2 lg:pb-0 whitespace-nowrap no-scrollbar -mx-2 px-2 lg:mx-0 lg:px-0"> <div className="grid grid-cols-2 sm:flex sm:flex-wrap items-center gap-2 pt-2 md:pt-0 border-t md:border-t-0 border-medical-gray-100">
{tabs.map(tab => ( {tabs.map(tab => (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id as any)} onClick={() => setActiveTab(tab.id as any)}
className={`flex items-center gap-2 px-3 sm:px-4 py-2.5 sm:py-4 rounded-xl sm:rounded-2xl transition-all font-bold text-xs sm:text-sm flex-shrink-0 ${activeTab === tab.id ? "bg-canina-blue text-white shadow-lg shadow-canina-blue/20" : "text-medical-gray-600 hover:bg-medical-gray-50 bg-medical-gray-50/50 lg:bg-transparent"}`} className={`flex items-center justify-center gap-2 px-3 sm:px-4 py-2.5 sm:py-3 rounded-xl sm:rounded-2xl transition-all font-bold text-xs sm:text-sm ${activeTab === tab.id ? "bg-canina-blue text-white shadow-lg shadow-canina-blue/20" : "text-medical-gray-600 hover:bg-medical-gray-100 bg-medical-gray-50"}`}
> >
<div className="flex-shrink-0">{tab.icon}</div> <div className="flex-shrink-0">{tab.icon}</div>
<span>{tab.label}</span> <span>{tab.label}</span>
@ -176,68 +176,17 @@ export default function UserDashboard() {
))} ))}
<button <button
onClick={handleLogout} onClick={handleLogout}
className="flex items-center gap-2 px-3 sm:px-4 py-2.5 sm:py-4 rounded-xl sm:rounded-2xl text-red-500 hover:bg-red-50 transition-all font-bold text-xs sm:text-sm flex-shrink-0 lg:mt-4 bg-red-50/30 lg:bg-transparent" className="flex items-center justify-center gap-2 px-3 sm:px-4 py-2.5 sm:py-3 rounded-xl sm:rounded-2xl text-red-500 hover:bg-red-50 transition-all font-bold text-xs sm:text-sm bg-red-50/40 col-span-2 sm:col-span-1"
> >
<LogOut className="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" /> <LogOut className="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" />
<span>خروج</span> <span>خروج</span>
</button> </button>
</div> </div>
</div> </div>
<div className="bg-medical-gray-900 rounded-[1.5rem] sm:rounded-[3rem] p-5 sm:p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 left-0 p-6 opacity-20 pointer-events-none">
<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/70 mb-2">موجودی کیف پول</div>
<div className="flex items-baseline gap-2 flex-row-reverse justify-end">
<div className="text-2xl sm:text-4xl font-black italic">{toPersian((profile.walletBalance ?? 0).toLocaleString())}</div>
<span className="text-xs sm:text-sm not-italic font-bold text-white/80">تومان</span>
</div>
<button
onClick={() => { setActiveTab("wallet"); setIsTopUpModalOpen(true); }}
disabled={isTopUpModalOpen}
className="mt-5 sm:mt-8 bg-canina-blue w-full py-3 sm:py-4 rounded-xl sm: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 text-xs sm:text-sm"
>
شارژ کیف پول
</button>
</div>
</div>
<div className="bg-pink-500 rounded-[1.5rem] sm:rounded-[3rem] p-5 sm:p-8 text-white relative overflow-hidden shadow-2xl shadow-pink-200">
<div className="absolute -top-4 -right-4 opacity-10 pointer-events-none">
<Heart className="w-32 sm:w-40 h-32 sm:h-40 fill-white" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 sm:gap-3 mb-4 sm:mb-6">
<Heart className="w-4 h-4 sm:w-5 sm:h-5 fill-white flex-shrink-0" />
<h4 className="text-xs sm:text-sm font-black italic tracking-widest">ردپای مهربانی</h4>
</div>
<div className="space-y-4 sm: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-xl sm: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-3 sm:p-4 bg-white/10 rounded-xl sm:rounded-2xl border border-white/20 backdrop-blur-sm">
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-white/20 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5">
<Sparkles className="w-3 h-3 sm:w-4 sm:h-4 text-white" />
</div>
<p className="text-[10px] sm:text-xs font-black leading-relaxed">
همکاری شما باعث تامین هزینه <span className="text-xs sm:text-sm text-yellow-300 mx-1">{toPersian(Math.floor((profile.charityDonationTotal || 0) / 15000).toString())} وعده غذا</span> برای حیوانات بیپناه شده است.
</p>
</div>
</div>
</div>
</div>
</div>
</div> </div>
{/* Main Content */} {/* Main Content Area */}
<div className="lg:col-span-8 min-w-0"> <div className="lg:col-span-8 min-w-0 order-1 lg:order-1">
<motion.div <motion.div
key={activeTab} key={activeTab}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@ -636,6 +585,60 @@ export default function UserDashboard() {
)} )}
</motion.div> </motion.div>
</div> </div>
{/* Sidebar Widgets (Wallet & Charity) */}
<div className="lg:col-span-4 space-y-4 sm:space-y-6 min-w-0 order-2 lg:order-2">
<div className="bg-medical-gray-900 rounded-[1.5rem] sm:rounded-[3rem] p-5 sm:p-8 text-white relative overflow-hidden shadow-2xl">
<div className="absolute top-0 left-0 p-6 opacity-20 pointer-events-none">
<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/70 mb-2">موجودی کیف پول</div>
<div className="flex items-baseline gap-2 flex-row-reverse justify-end">
<div className="text-2xl sm:text-4xl font-black italic">{toPersian((profile.walletBalance ?? 0).toLocaleString())}</div>
<span className="text-xs sm:text-sm not-italic font-bold text-white/80">تومان</span>
</div>
<button
onClick={() => { setActiveTab("wallet"); setIsTopUpModalOpen(true); }}
disabled={isTopUpModalOpen}
className="mt-5 sm:mt-8 bg-canina-blue w-full py-3 sm:py-4 rounded-xl sm: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 text-xs sm:text-sm"
>
شارژ کیف پول
</button>
</div>
</div>
<div className="bg-pink-500 rounded-[1.5rem] sm:rounded-[3rem] p-5 sm:p-8 text-white relative overflow-hidden shadow-2xl shadow-pink-200">
<div className="absolute -top-4 -right-4 opacity-10 pointer-events-none">
<Heart className="w-32 sm:w-40 h-32 sm:h-40 fill-white" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 sm:gap-3 mb-4 sm:mb-6">
<Heart className="w-4 h-4 sm:w-5 sm:h-5 fill-white flex-shrink-0" />
<h4 className="text-xs sm:text-sm font-black italic tracking-widest">ردپای مهربانی</h4>
</div>
<div className="space-y-4 sm: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-xl sm: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-3 sm:p-4 bg-white/10 rounded-xl sm:rounded-2xl border border-white/20 backdrop-blur-sm">
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="w-6 h-6 sm:w-8 sm:h-8 bg-white/20 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5">
<Sparkles className="w-3 h-3 sm:w-4 sm:h-4 text-white" />
</div>
<p className="text-[10px] sm:text-xs font-black leading-relaxed">
همکاری شما باعث تامین هزینه <span className="text-xs sm:text-sm text-yellow-300 mx-1">{toPersian(Math.floor((profile.charityDonationTotal || 0) / 15000).toString())} وعده غذا</span> برای حیوانات بیپناه شده است.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<OrderDetailsModal <OrderDetailsModal