canina/frontend/application/components/UserDashboard.tsx

1687 lines
96 KiB
TypeScript
Raw Permalink 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, useEffect } 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, MessageSquare, Send, Stethoscope, FileText, Lock, Key, Eye, EyeOff, ShieldCheck, Dog, X, Plus, CreditCard, Activity, RotateCcw } from "lucide-react";
import { OrderRowSkeleton } from "./Skeleton";
import { useUserStore, Address } from "../lib/store/userStore";
import { useCartStore } from "../lib/store/cartStore";
import { useSettingsStore } from "../lib/store/settingsStore";
import { toPersian, toEnglishDigits, 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 { ticketService, Ticket, TicketMessage } from "../lib/services/ticketService";
import { usePetStore } from "../lib/store/usePetStore";
import { useCatalogMode } from "../lib/useCatalogMode";
import PetProfile from "./PetProfile";
import api, { BASE_DOMAIN } from "../lib/services/api";
import { useRouter, useSearchParams } from 'next/navigation';
export default function UserDashboard() {
const router = useRouter();
const searchParams = useSearchParams();
const { profile, isLoggedIn, logout, updateProfile, addAddress, updateAddress, deleteAddress, setDefaultAddress, topUpWallet, fetchProfile } = useUserStore();
const { pets, activePetId, setActivePet, removePet } = usePetStore();
const isWithdrawalEnabled = useSettingsStore((s) => s.getBoolean('wallet_withdrawal_enabled', false));
const isRechargeEnabled = useSettingsStore((s) => s.getBoolean('wallet_recharge_enabled', true));
const { isCatalogOnly, showOrders, showWallet, showCharity } = useCatalogMode();
const isWalletRechargeAllowed = !isCatalogOnly && isRechargeEnabled;
React.useEffect(() => {
if (!isLoggedIn && typeof window !== "undefined") {
router.push("/");
toast.error("برای دسترسی به داشبورد لطفاً ابتدا وارد حساب کاربری خود شوید.");
}
}, [isLoggedIn, router]);
const { orders } = useCartStore();
const validTabs = React.useMemo(() => {
const list = ['profile', 'pets', 'addresses', 'tickets', 'prescriptions'];
if (showOrders) list.push('orders');
if (showWallet) list.push('wallet');
return list;
}, [showOrders, showWallet]);
// Initialize tab directly from URL or default to profile
const initialTab = (searchParams.get('tab') as "profile" | "orders" | "wallet" | "addresses" | "tickets" | "pets" | "prescriptions") || "profile";
const [activeTab, setActiveTab] = React.useState<"profile" | "orders" | "wallet" | "addresses" | "tickets" | "pets" | "prescriptions" | "overview">(
validTabs.includes(initialTab) ? initialTab : "profile"
);
// Handle tab click and update browser URL without full reloads
const handleTabChange = React.useCallback((tabId: "profile" | "orders" | "wallet" | "addresses" | "tickets" | "pets" | "prescriptions") => {
setActiveTab(tabId);
const newUrl = tabId === 'profile' ? '/dashboard' : `/dashboard?tab=${tabId}`;
window.history.replaceState(null, '', newUrl);
}, []);
// Sync tab if user navigates back/forward in browser history or searchParams change
useEffect(() => {
const tabParam = searchParams.get('tab');
if (tabParam && validTabs.includes(tabParam)) {
setActiveTab(tabParam as "profile" | "orders" | "wallet" | "addresses" | "tickets" | "pets" | "prescriptions");
} else if (!tabParam || !validTabs.includes(activeTab)) {
setActiveTab("profile");
}
}, [searchParams, validTabs, activeTab]);
const [isLoadingOrders, setIsLoadingOrders] = useState(false);
const [prescriptions, setPrescriptions] = useState<any[]>([]);
const [isLoadingPrescriptions, setIsLoadingPrescriptions] = useState(false);
const fetchPrescriptions = React.useCallback(async () => {
try {
setIsLoadingPrescriptions(true);
const res = await api.get('/prescriptions');
setPrescriptions(Array.isArray(res.data) ? res.data : (res.data?.data || []));
} catch {
// ignore
} finally {
setIsLoadingPrescriptions(false);
}
}, []);
useEffect(() => {
if (activeTab === 'prescriptions') {
fetchPrescriptions();
}
}, [activeTab, fetchPrescriptions]);
// Tickets State
const [tickets, setTickets] = useState<Ticket[]>([]);
const [isLoadingTickets, setIsLoadingTickets] = useState(false);
const [isNewTicketModalOpen, setIsNewTicketModalOpen] = useState(false);
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
const [newTicketSubject, setNewTicketSubject] = useState("");
const [newTicketMessage, setNewTicketMessage] = useState("");
const [newTicketCategory, setNewTicketCategory] = useState<string>("VET_CONSULTATION");
const [newTicketPriority, setNewTicketPriority] = useState<string>("MEDIUM");
const [newTicketPetId, setNewTicketPetId] = useState<string>("");
const [isSubmittingTicket, setIsSubmittingTicket] = useState(false);
const [replyMessage, setReplyMessage] = useState("");
const [isSendingReply, setIsSendingReply] = useState(false);
const fetchTickets = React.useCallback(async () => {
try {
setIsLoadingTickets(true);
const data = await ticketService.getMyTickets();
setTickets(data);
} catch (err: unknown) {
if (!(err as Record<string, boolean>)?._toastShown) {
toast.error("خطا در دریافت لیست تیکت‌ها");
}
} finally {
setIsLoadingTickets(false);
}
}, []);
React.useEffect(() => {
if (activeTab === "tickets") {
fetchTickets();
}
}, [activeTab, fetchTickets]);
const handleCreateTicket = async (e: React.FormEvent) => {
e.preventDefault();
if (!newTicketSubject.trim() || !newTicketMessage.trim()) {
toast.error("لطفاً موضوع و متن پیام را وارد کنید.");
return;
}
try {
setIsSubmittingTicket(true);
await ticketService.createTicket({
subject: newTicketSubject.trim(),
message: newTicketMessage.trim(),
category: newTicketCategory,
priority: newTicketPriority,
petId: newTicketPetId || undefined,
});
toast.success("تیکت شما با موفقیت ثبت شد!");
setIsNewTicketModalOpen(false);
setNewTicketSubject("");
setNewTicketMessage("");
setNewTicketPetId("");
fetchTickets();
} catch (err: unknown) {
if (!(err as Record<string, boolean>)?._toastShown) {
const gErr = err as { response?: { data?: { message?: string } }; message?: string };
toast.error(gErr.response?.data?.message || gErr.message || "خطا در ثبت تیکت");
}
} finally {
setIsSubmittingTicket(false);
}
};
const handleSendReply = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedTicket || !replyMessage.trim()) return;
try {
setIsSendingReply(true);
const newMsg = await ticketService.replyTicket(selectedTicket.id, replyMessage.trim());
setSelectedTicket((prev) =>
prev ? { ...prev, messages: [...prev.messages, newMsg] } : null,
);
setReplyMessage("");
toast.success("پاسخ شما با موفقیت ارسال شد");
fetchTickets();
} catch (err: unknown) {
if (!(err as Record<string, boolean>)?._toastShown) {
const gErr = err as { response?: { data?: { message?: string } }; message?: string };
toast.error(gErr.response?.data?.message || gErr.message || "خطا در ارسال پاسخ");
}
} finally {
setIsSendingReply(false);
}
};
// Fetch fresh profile data (which includes orders) whenever orders tab is opened
React.useEffect(() => {
if (activeTab === "orders") {
let isSubscribed = true;
Promise.resolve().then(() => {
if (isSubscribed) setIsLoadingOrders(true);
});
useUserStore.getState().fetchProfile().finally(() => {
if (isSubscribed) setIsLoadingOrders(false);
});
return () => {
isSubscribed = false;
};
}
}, [activeTab]);
const [selectedOrder, setSelectedOrder] = useState<Record<string, unknown> | null>(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);
const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false);
const [withdrawAmount, setWithdrawAmount] = useState("");
const [withdrawIban, setWithdrawIban] = useState("");
const [isSubmittingWithdrawal, setIsSubmittingWithdrawal] = useState(false);
// Pet Delete State
const [petToDelete, setPetToDelete] = useState<{ id: string; name: string } | null>(null);
// 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(() => {
let isSubscribed = true;
Promise.resolve().then(() => {
if (isSubscribed) {
setProfileForm({
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || "",
mobile: profile.mobile || ""
});
}
});
return () => {
isSubscribed = false;
};
}, [profile]);
// Password Management State
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showCurrentPass, setShowCurrentPass] = useState(false);
const [showNewPass, setShowNewPass] = useState(false);
const [showConfirmPass, setShowConfirmPass] = useState(false);
const [isSavingPassword, setIsSavingPassword] = useState(false);
if (!isLoggedIn) {
return null;
}
const handleSaveProfile = async (e: React.FormEvent) => {
e.preventDefault();
setIsSavingProfile(true);
try {
const cleanPhone = toEnglishDigits(profileForm.mobile);
await updateProfile({
firstName: profileForm.firstName,
lastName: profileForm.lastName,
email: profileForm.email,
mobile: cleanPhone
});
setIsEditing(false);
toast.success("اطلاعات کاربری با موفقیت ویرایش شد");
} catch {
toast.error("خطا در ویرایش اطلاعات");
} finally {
setIsSavingProfile(false);
}
};
const handleSavePassword = async (e: React.FormEvent) => {
e.preventDefault();
if (!newPassword || newPassword.length < 6) {
toast.error("رمز عبور جدید باید حداقل ۶ کاراکتر باشد");
return;
}
if (newPassword !== confirmPassword) {
toast.error("رمز عبور جدید و تکرار آن یکسان نیستند");
return;
}
if (profile.hasPassword && !currentPassword) {
toast.error("لطفاً رمز عبور فعلی خود را وارد کنید");
return;
}
setIsSavingPassword(true);
try {
await updateProfile({
password: newPassword.trim(),
currentPassword: profile.hasPassword ? currentPassword.trim() : undefined,
});
toast.success(profile.hasPassword ? "رمز عبور با موفقیت تغییر کرد" : "رمز عبور با موفقیت برای حساب شما ثبت شد");
setCurrentPassword("");
setNewPassword("");
setConfirmPassword("");
} catch (err: unknown) {
const errObj = err as { message?: string };
toast.error(errObj.message || "خطا در ثبت رمز عبور");
} finally {
setIsSavingPassword(false);
}
};
const handleLogout = () => {
logout();
router.push('/');
};
const handleSaveAddress = async (addr: Address) => {
try {
if (editingAddress) {
await updateAddress(addr.id, addr);
} else {
await addAddress(addr);
}
} catch {
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 {
toast.error("خطا در حذف آدرس");
}
setAddressToDelete(null);
}
};
const handleEditClick = (addr: Address) => {
setEditingAddress(addr);
setIsAddressModalOpen(true);
};
const handleAddClick = () => {
setEditingAddress(null);
setIsAddressModalOpen(true);
};
const handleWithdrawSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const numAmount = parseInt(toEnglishDigits(withdrawAmount));
if (!numAmount || numAmount < 50000) {
toast.error("حداقل مبلغ قابل برداشت ۵۰,۰۰۰ تومان است.");
return;
}
if (numAmount > (profile.walletBalance || 0)) {
toast.error("مبلغ درخواستی از موجودی کیف پول شما بیشتر است.");
return;
}
if (!withdrawIban.trim() || withdrawIban.trim().length < 10) {
toast.error("لطفاً شماره شبا یا شماره کارت معتبر وارد نمایید.");
return;
}
try {
setIsSubmittingWithdrawal(true);
await ticketService.createTicket({
subject: `درخواست تسویه و برداشت وجه (${toPersian(numAmount.toLocaleString())} تومان)`,
message: `درخواست برداشت وجه از کیف پول کاربری:\nمبلغ: ${numAmount.toLocaleString('fa-IR')} تومان\nشماره شبا/کارت: ${withdrawIban}\nشماره همراه کاربر: ${profile.mobile || ''}`,
category: 'FINANCIAL',
priority: 'HIGH'
});
toast.success(`درخواست برداشت ${toPersian(numAmount.toLocaleString())} تومان با موفقیت ثبت شد و پس از بررسی واحد مالی واریز خواهد شد.`);
setIsWithdrawModalOpen(false);
setWithdrawAmount("");
setWithdrawIban("");
fetchTickets();
} catch {
toast.error("خطا در ثبت درخواست برداشت. لطفاً با پشتیبانی تماس بگیرید.");
} finally {
setIsSubmittingWithdrawal(false);
}
};
const allTabs = [
{ id: "profile", label: "اطلاعات فردی", icon: <UserCircle className="w-5 h-5" /> },
{ id: "pets", label: "پت‌های من", icon: <Dog className="w-5 h-5" /> },
{ id: "orders", label: "سفارشات من", icon: <Package className="w-5 h-5" /> },
{ id: "prescriptions", label: "نسخه‌های من", icon: <Stethoscope 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" /> },
{ id: "tickets", label: "پشتیبانی و مشاوره", icon: <MessageSquare className="w-5 h-5" /> },
];
const tabs = allTabs.filter(tab => {
if (tab.id === 'orders' && !showOrders) return false;
if (tab.id === 'wallet' && !showWallet) return false;
return true;
});
return (
<div className="min-h-screen bg-medical-gray-50 pt-6 sm:pt-12 pb-24 px-3 sm:px-4 font-vazir overflow-x-hidden" dir="rtl">
<div className="max-w-6xl mx-auto w-full">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-6 sm:mb-10 overflow-x-auto whitespace-nowrap pb-2 no-scrollbar">
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
<ChevronRight className="w-2.5 h-2.5 flex-shrink-0" />
<span className="text-canina-blue">پنل کاربری</span>
</div>
<div className="grid lg:grid-cols-12 gap-6 sm:gap-8">
{/* Top User Info & Navigation Tabs */}
<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-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">
<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-7 sm:h-7" />
</div>
<div className="min-w-0 flex-1">
<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-xs font-bold text-medical-gray-400 truncate">{profile.email}</p>
</div>
</div>
{/* Tab Navigation */}
<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 => (
<button
key={tab.id}
onClick={() => handleTabChange(tab.id as any)}
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 cursor-pointer ${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>
<span>{tab.label}</span>
</button>
))}
<button
onClick={handleLogout}
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" />
<span>خروج</span>
</button>
</div>
</div>
</div>
{/* Main Content Area */}
<div className="lg:col-span-8 min-w-0 order-1 lg:order-1">
<motion.div
key={activeTab}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-[1.5rem] sm:rounded-[3.5rem] border border-medical-gray-200 p-4 sm:p-10 min-h-[400px] sm: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>
{/* Password & Security Section */}
<div className="mt-12 pt-8 border-t border-medical-gray-100">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-2xl bg-canina-blue/10 text-canina-blue flex items-center justify-center">
<Lock className="w-5 h-5" />
</div>
<div>
<h4 className="text-lg font-black text-medical-gray-900 italic">
{profile.hasPassword ? "تغییر رمز عبور حساب" : "تعریف رمز عبور برای حساب کاربری"}
</h4>
<p className="text-xs font-medium text-medical-gray-400">
{profile.hasPassword
? "برای افزایش امنیت می‌توانید رمز عبور ورود خود را تغییر دهید."
: "با تعیین رمز عبور می‌توانید در دفعات بعدی علاوه بر پیامک، با رمز عبور نیز وارد شوید."}
</p>
</div>
</div>
<span
className={cn(
"px-3 py-1 rounded-full text-[11px] font-bold self-start sm:self-auto",
profile.hasPassword
? "bg-green-50 text-green-700 border border-green-200"
: "bg-amber-50 text-amber-700 border border-amber-200"
)}
>
{profile.hasPassword ? "رمز عبور فعال است" : "ورود فقط با OTP"}
</span>
</div>
<form onSubmit={handleSavePassword} className="bg-medical-gray-50/50 p-6 sm:p-8 rounded-[2rem] border border-medical-gray-100 space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{profile.hasPassword && (
<div className="space-y-1.5">
<label className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">
رمز عبور فعلی *
</label>
<div className="relative">
<input
type={showCurrentPass ? "text" : "password"}
autoComplete="current-password"
required
placeholder="رمز فعلی..."
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="w-full bg-white border border-medical-gray-200 rounded-xl py-3 pr-3 pl-10 focus:ring-2 focus:ring-canina-blue/20 outline-none text-sm text-left font-mono"
dir="ltr"
/>
<button
type="button"
onClick={() => setShowCurrentPass(!showCurrentPass)}
className="absolute left-2.5 top-1/2 -translate-y-1/2 p-1 text-medical-gray-400 hover:text-medical-gray-700 transition-colors cursor-pointer"
tabIndex={-1}
>
{showCurrentPass ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
)}
<div className="space-y-1.5">
<label className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">
{profile.hasPassword ? "رمز عبور جدید *" : "رمز عبور دلخواه *"}
</label>
<div className="relative">
<input
type={showNewPass ? "text" : "password"}
autoComplete="new-password"
required
placeholder="حداقل ۶ کاراکتر"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="w-full bg-white border border-medical-gray-200 rounded-xl py-3 pr-3 pl-10 focus:ring-2 focus:ring-canina-blue/20 outline-none text-sm text-left font-mono"
dir="ltr"
/>
<button
type="button"
onClick={() => setShowNewPass(!showNewPass)}
className="absolute left-2.5 top-1/2 -translate-y-1/2 p-1 text-medical-gray-400 hover:text-medical-gray-700 transition-colors cursor-pointer"
tabIndex={-1}
>
{showNewPass ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">
تکرار رمز عبور *
</label>
<div className="relative">
<input
type={showConfirmPass ? "text" : "password"}
autoComplete="new-password"
required
placeholder="تکرار رمز عبور"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full bg-white border border-medical-gray-200 rounded-xl py-3 pr-3 pl-10 focus:ring-2 focus:ring-canina-blue/20 outline-none text-sm text-left font-mono"
dir="ltr"
/>
<button
type="button"
onClick={() => setShowConfirmPass(!showConfirmPass)}
className="absolute left-2.5 top-1/2 -translate-y-1/2 p-1 text-medical-gray-400 hover:text-medical-gray-700 transition-colors cursor-pointer"
tabIndex={-1}
>
{showConfirmPass ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
</div>
<div className="flex justify-end pt-3">
<button
type="submit"
disabled={isSavingPassword || newPassword.length < 6 || !confirmPassword}
className="px-8 py-3.5 bg-canina-blue hover:bg-indigo-700 text-white rounded-xl font-black text-xs transition-all shadow-md shadow-canina-blue/15 flex items-center gap-2 cursor-pointer disabled:opacity-50"
>
{isSavingPassword ? (
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<ShieldCheck className="w-4 h-4" />
)}
{profile.hasPassword ? "بروزرسانی رمز عبور" : "ثبت رمز عبور"}
</button>
</div>
</form>
</div>
</div>
)}
{activeTab === "pets" && (
<div className="w-full">
<PetProfile embedded={true} initialView={pets.length > 0 ? "index" : "add"} />
</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 as unknown as Record<string, unknown>)}
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.trackingNumber || order.id?.substring(0, 8))}</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 || (order as unknown as Record<string, string>).createdAt || 0).toLocaleDateString("fa-IR"))} - ساعت {toPersian(new Date(order.date || (order as unknown as Record<string, string>).createdAt || 0).toLocaleTimeString("fa-IR", { hour: '2-digit', minute: '2-digit' }))}
</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 flex-wrap items-center gap-2 justify-end mt-1.5">
{order.isRefill && (
<span className="text-[9px] font-black px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-600 border border-emerald-100">
تمدید خودکار ۵٪-
</span>
)}
{Number(order.charityDonation) > 0 && (
<span className="text-[9px] font-black px-2 py-0.5 rounded-full bg-rose-50 text-rose-600 border border-rose-100 flex items-center gap-1">
<Heart className="w-2.5 h-2.5 fill-rose-500 text-rose-500" />
مهربانی: {toPersian(Number(order.charityDonation).toLocaleString())} ت
</span>
)}
{(() => {
const st = order.status;
let badgeLabel = 'در حال پردازش';
let badgeColor = 'bg-amber-50 text-amber-700 border-amber-200';
if (st === 'delivered') {
badgeLabel = 'تحویل شده';
badgeColor = 'bg-green-50 text-green-700 border-green-200';
} else if (st === 'shipped') {
badgeLabel = 'ارسال شده';
badgeColor = 'bg-blue-50 text-blue-700 border-blue-200';
} else if (st === 'packaged' || st === 'ready_to_ship') {
badgeLabel = 'بسته‌بندی شده (آماده ارسال)';
badgeColor = 'bg-purple-50 text-purple-700 border-purple-200';
} else if (st === 'pending_payment') {
badgeLabel = 'در انتظار پرداخت / ناموفق';
badgeColor = 'bg-rose-50 text-rose-700 border-rose-200';
} else if (st === 'cancelled') {
badgeLabel = 'لغو شده';
badgeColor = 'bg-red-50 text-red-700 border-red-200';
} else if (st === 'refunded') {
badgeLabel = 'مرجوع شده';
badgeColor = 'bg-gray-100 text-gray-700 border-gray-200';
}
return (
<span className={`text-[9px] font-black px-2.5 py-0.5 rounded-full border uppercase tracking-tighter ${badgeColor}`}>
{badgeLabel}
</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 {
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 via-blue-600 to-indigo-700 rounded-[2rem] sm:rounded-[3rem] p-6 sm:p-10 text-white shadow-2xl shadow-canina-blue/20 relative overflow-hidden group">
<div className="absolute top-0 right-0 p-6 sm:p-10 opacity-10 group-hover:scale-110 transition-transform duration-700 pointer-events-none">
<Wallet className="w-32 h-32 sm:w-44 sm:h-44" />
</div>
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-6 sm:gap-8">
<div className="text-center lg:text-right w-full lg:w-auto">
<div className="text-xs font-black uppercase tracking-[0.2em] text-white/80 mb-2 sm:mb-3 flex items-center gap-2 justify-center lg:justify-start">
<div className="w-2 h-2 bg-emerald-400 rounded-full animate-pulse shadow-sm shadow-emerald-400/50" />
موجودی زنده و قابل استفاده
</div>
<div className="flex items-baseline gap-2 flex-row-reverse justify-end">
<div className="text-3xl sm:text-4xl lg:text-5xl font-black italic tracking-tight whitespace-nowrap">
{toPersian((profile.walletBalance || 0).toLocaleString())}
</div>
<span className="text-sm sm:text-base font-bold opacity-90 decoration-white/30 underline underline-offset-8">
تومان
</span>
</div>
</div>
<div className="flex flex-wrap sm:flex-nowrap gap-3 w-full lg:w-auto shrink-0 justify-center">
{isWithdrawalEnabled && (
<button
type="button"
onClick={() => {
if ((profile.walletBalance || 0) < 50000) {
toast.error("حداقل موجودی برای ثبت درخواست برداشت ۵۰,۰۰۰ تومان است.");
return;
}
setWithdrawAmount(String(profile.walletBalance || 50000));
setIsWithdrawModalOpen(true);
}}
className="flex-1 sm:flex-initial sm:w-36 h-12 sm:h-14 bg-white/10 backdrop-blur-md rounded-2xl font-black text-xs sm:text-sm flex items-center justify-center gap-2 border border-white/20 hover:bg-white/20 transition-all cursor-pointer whitespace-nowrap"
>
<ArrowDownCircle className="w-4 h-4 sm:w-5 sm:h-5 shrink-0" />
برداشت وجه
</button>
)}
{isWalletRechargeAllowed ? (
<button
type="button"
onClick={() => setIsTopUpModalOpen(true)}
className="flex-1 sm:flex-initial sm:w-36 h-12 sm:h-14 bg-white text-canina-blue rounded-2xl font-black text-xs sm: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 cursor-pointer whitespace-nowrap"
>
<ArrowUpCircle className="w-4 h-4 sm:w-5 sm:h-5 group-hover:-translate-y-1 transition-transform shrink-0" />
شارژ آنی
</button>
) : (
<span className="text-[11px] font-bold text-white/80 bg-white/10 px-4 py-3 rounded-2xl border border-white/20 whitespace-nowrap">
شارژ در حالت کاتالوگ غیرفعال است
</span>
)}
</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) => {
const desc = trx.description || "";
const isRefund = trx.type === 'refund' || desc.includes('استرداد') || desc.includes('لغو') || desc.includes('عودت');
const isAdmin = desc.includes('مدیر') || desc.includes('دستی');
const isOnlineTopUp = trx.type === 'top_up' && !isRefund && !isAdmin;
const isPurchase = trx.type === 'purchase' || desc.includes('پرداخت سفارش');
let title = "تراکنش کیف پول";
let badgeText = "تراکنش";
let badgeClass = "bg-medical-gray-100 text-medical-gray-600 border-medical-gray-200";
let iconBgClass = "bg-medical-gray-50 text-medical-gray-600 group-hover:bg-medical-gray-700 group-hover:text-white";
let IconComponent = ArrowUpCircle;
let isPositive = trx.type === 'top_up' || trx.type === 'refund';
if (isRefund) {
title = "استرداد وجه سفارش";
badgeText = "استرداد وجه";
badgeClass = "bg-blue-50 text-blue-700 border-blue-200/60";
iconBgClass = "bg-blue-50 text-blue-600 group-hover:bg-blue-600 group-hover:text-white";
IconComponent = RotateCcw;
isPositive = true;
} else if (isAdmin) {
title = isPositive ? "شارژ توسط مدیریت" : "کسر توسط مدیریت";
badgeText = "تغییر توسط پشتیبانی";
badgeClass = "bg-amber-50 text-amber-700 border-amber-200/60";
iconBgClass = "bg-amber-50 text-amber-600 group-hover:bg-amber-600 group-hover:text-white";
IconComponent = ShieldCheck;
} else if (isOnlineTopUp) {
title = "افزایش اعتبار آنلاین (درگاه پرداخت)";
badgeText = "شارژ درگاه";
badgeClass = "bg-emerald-50 text-emerald-700 border-emerald-200/60";
iconBgClass = "bg-emerald-50 text-emerald-600 group-hover:bg-emerald-600 group-hover:text-white";
IconComponent = ArrowUpCircle;
isPositive = true;
} else if (isPurchase) {
title = "پرداخت سفارش از کیف پول";
badgeText = "پرداخت سفارش";
badgeClass = "bg-rose-50 text-rose-700 border-rose-200/60";
iconBgClass = "bg-rose-50 text-rose-600 group-hover:bg-rose-600 group-hover:text-white";
IconComponent = ShoppingCart;
isPositive = false;
}
return (
<div key={trx.id} className="p-4 sm:p-6 bg-white border border-medical-gray-100 rounded-[1.5rem] sm:rounded-[2rem] flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-6 hover:shadow-lg hover:border-canina-blue/20 transition-all group">
<div className="flex items-start sm:items-center gap-3 sm:gap-4 flex-1 min-w-0">
<div className={cn(
"w-10 h-10 sm:w-12 sm:h-12 rounded-xl sm:rounded-2xl flex items-center justify-center transition-colors flex-shrink-0 mt-0.5 sm:mt-0",
iconBgClass
)}>
<IconComponent className="w-5 h-5 sm:w-6 sm:h-6" />
</div>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2 mb-1">
<span className="text-xs sm:text-sm font-black text-medical-gray-900">{title}</span>
<span className={cn("text-[10px] px-2.5 py-0.5 rounded-full border font-black", badgeClass)}>
{badgeText}
</span>
</div>
{desc && (
<p className="text-xs font-bold text-medical-gray-700 line-clamp-2 leading-relaxed mb-1.5">
{desc}
</p>
)}
<div className="text-[10px] font-bold text-medical-gray-400 flex flex-wrap items-center gap-x-2.5 gap-y-1">
<span className="font-mono text-medical-gray-500">کد: {trx.id}</span>
{trx.transactionReference && (
<>
<span className="w-1 h-1 bg-medical-gray-200 rounded-full" />
<span className="font-mono text-canina-blue">مرجع/پیگیری: {trx.transactionReference}</span>
</>
)}
<span className="w-1 h-1 bg-medical-gray-200 rounded-full hidden sm:inline-block" />
<span>{toPersian(new Date(trx.date).toLocaleDateString("fa-IR"))}</span>
</div>
</div>
</div>
<div className="text-right sm:text-left flex sm:flex-col justify-between items-center sm:items-end border-t sm:border-t-0 pt-3 sm:pt-0 border-medical-gray-50 flex-shrink-0">
<div className={cn(
"text-base sm:text-lg font-black italic",
isPositive ? "text-emerald-600" : "text-rose-500"
)}>
{isPositive ? "+" : "-"}{toPersian(trx.amount.toLocaleString())} <span className="text-xs not-italic">تومان</span>
</div>
<div className={cn(
"text-[10px] font-black tracking-wide sm:mt-1 px-2 py-0.5 rounded-full",
trx.status === 'failed' ? "bg-rose-50 text-rose-600" :
trx.status === 'pending' ? "bg-amber-50 text-amber-600" :
"bg-emerald-50 text-emerald-600"
)}>
{trx.status === 'failed' ? "ناموفق" : trx.status === 'pending' ? "در انتظار تایید" : "موفق"}
</div>
</div>
</div>
);
})
)}
</div>
</div>
</div>
)}
{activeTab === "tickets" && (
<div>
<div className="flex items-center justify-between mb-8">
<div>
<h3 className="text-2xl font-black text-medical-gray-900 italic">پشتیبانی و مشاوره آنلاین دامپزشک</h3>
<p className="text-xs text-medical-gray-500 mt-1 font-medium">پاسخگویی تخصصی به سوالات دارویی، مکملها و پیگیری سفارشات توسط تیم پزشکی کنینا</p>
</div>
<button
type="button"
onClick={() => setIsNewTicketModalOpen(true)}
className="px-4 py-2.5 bg-canina-blue text-white rounded-xl text-xs font-black flex items-center gap-1.5 hover:bg-canina-dark transition-all shadow-md shadow-canina-blue/20 cursor-pointer"
>
<Send className="w-3.5 h-3.5" />
ثبت تیکت جدید
</button>
</div>
{isLoadingTickets ? (
<div className="py-20 text-center text-medical-gray-400 font-bold">در حال بارگذاری تیکتها...</div>
) : tickets.length === 0 ? (
<div className="text-center py-20 bg-medical-gray-50 rounded-[2.5rem] border border-dashed border-medical-gray-200">
<MessageSquare className="w-12 h-12 text-medical-gray-300 mx-auto mb-4" />
<h4 className="text-base font-black text-medical-gray-800 mb-1">هنوز تیکتی ثبت نکردهاید</h4>
<p className="text-xs text-medical-gray-400 font-medium max-w-md mx-auto mb-6">
شما میتوانید سوالات پزشکی و تغذیهای حیوان خانگی خود را از دامپزشکان متخصص کنینا بپرسید یا وضعیت سفارشات خود را پیگیری کنید.
</p>
<button
type="button"
onClick={() => setIsNewTicketModalOpen(true)}
className="px-6 py-3 bg-canina-blue text-white rounded-xl text-xs font-black inline-flex items-center gap-2 hover:bg-canina-dark transition-all shadow-md shadow-canina-blue/20 cursor-pointer"
>
<Send className="w-4 h-4" />
ثبت اولین تیکت مشاوره
</button>
</div>
) : (
<div className="space-y-4">
{tickets.map((t) => {
const isVet = t.category === 'VET_CONSULTATION';
return (
<div
key={t.id}
onClick={() => setSelectedTicket(t)}
className="p-5 sm:p-6 bg-medical-gray-50 hover:bg-white border border-medical-gray-200 hover:border-canina-blue/30 rounded-[1.5rem] sm:rounded-[2rem] flex flex-col md:flex-row items-start md:items-center justify-between gap-4 transition-all hover:shadow-lg hover:shadow-canina-blue/5 cursor-pointer group"
>
<div className="flex items-center gap-4">
<div className={cn(
"w-12 h-12 rounded-2xl flex items-center justify-center shrink-0 transition-transform group-hover:scale-105",
isVet ? "bg-emerald-50 text-emerald-600" : "bg-canina-blue/10 text-canina-blue"
)}>
{isVet ? <Stethoscope className="w-6 h-6" /> : <FileText className="w-6 h-6" />}
</div>
<div>
<div className="flex items-center gap-2 flex-wrap mb-1">
<h4 className="font-black text-medical-gray-900 text-sm group-hover:text-canina-blue transition-colors">
{t.subject}
</h4>
{t.pet && (
<span className="px-2 py-0.5 bg-purple-50 text-purple-700 text-[10px] font-bold rounded-lg border border-purple-100">
🐾 {t.pet.name} ({t.pet.breed || t.pet.type})
</span>
)}
</div>
<p className="text-[11px] text-medical-gray-400 font-medium">
شناسه: {t.ticketNumber} {t.messages?.length || 1} پیام {new Date(t.updatedAt).toLocaleDateString('fa-IR')}
</p>
</div>
</div>
<div className="flex items-center gap-3 self-end md:self-center">
<span className={cn(
"px-3 py-1 text-xs font-black rounded-full border",
t.status === 'ANSWERED' && "bg-emerald-50 text-emerald-700 border-emerald-200",
t.status === 'OPEN' && "bg-amber-50 text-amber-700 border-amber-200",
t.status === 'IN_PROGRESS' && "bg-blue-50 text-blue-700 border-blue-200",
t.status === 'CLOSED' && "bg-gray-100 text-gray-600 border-gray-200",
)}>
{t.status === 'ANSWERED' ? 'پاسخ داده شد' :
t.status === 'OPEN' ? 'در انتظار پاسخ' :
t.status === 'IN_PROGRESS' ? 'در حال بررسی' : 'بسته شده'}
</span>
<ChevronRight className="w-4 h-4 text-medical-gray-300 group-hover:translate-x-[-2px] transition-transform" />
</div>
</div>
);
})}
</div>
)}
</div>
)}
{activeTab === "prescriptions" && (
<div>
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-8">
<div>
<h3 className="text-2xl font-black text-medical-gray-900 italic flex items-center gap-2">
<Stethoscope className="w-6 h-6 text-canina-blue" />
نسخههای پزشکی من
</h3>
<p className="text-xs font-bold text-medical-gray-400 mt-1">
پیگیری وضعیت نسخههای بارگذاری شده و توصیههای دارویی دامپزشک
</p>
</div>
<button
onClick={() => router.push('/?rx=1')}
className="px-5 py-3 bg-canina-blue text-white rounded-2xl text-xs font-black hover:bg-canina-dark transition-all flex items-center gap-2 shadow-lg shadow-canina-blue/20 cursor-pointer"
>
<Plus className="w-4 h-4" />
<span>ارسال نسخه جدید</span>
</button>
</div>
{isLoadingPrescriptions ? (
<div className="py-20 text-center text-medical-gray-400 font-bold text-sm">
در حال بارگذاری نسخهها...
</div>
) : prescriptions.length === 0 ? (
<div className="py-20 text-center space-y-4">
<div className="w-16 h-16 bg-medical-gray-50 text-medical-gray-300 rounded-full flex items-center justify-center mx-auto">
<Stethoscope className="w-8 h-8" />
</div>
<p className="text-sm font-bold text-medical-gray-400">هنوز هیچ نسخهای ثبت نکردهاید.</p>
<button
onClick={() => router.push('/?rx=1')}
className="px-6 py-2.5 bg-medical-gray-900 text-white rounded-xl text-xs font-bold hover:bg-canina-blue transition-all cursor-pointer"
>
ثبت اولین نسخه
</button>
</div>
) : (
<div className="space-y-4">
{prescriptions.map((rx: any) => (
<div
key={rx.id}
className="p-5 sm:p-6 bg-medical-gray-50 border border-medical-gray-200 rounded-[1.5rem] sm:rounded-[2rem] space-y-3 transition-all hover:shadow-md"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 pb-3 border-b border-medical-gray-200">
<div className="flex items-center gap-2">
<span className="text-xs font-black text-medical-gray-900">نسخه #{rx.id.slice(0, 8)}</span>
{rx.pet && (
<span className="px-2 py-0.5 bg-purple-50 text-purple-700 text-[10px] font-bold rounded-lg border border-purple-100">
🐾 {rx.pet.name} ({rx.pet.breed || rx.pet.type})
</span>
)}
</div>
<div className="flex items-center gap-2">
<span className={`px-3 py-1 text-xs font-black rounded-full ${
rx.status === 'APPROVED' ? 'bg-green-100 text-green-700' :
rx.status === 'REJECTED' ? 'bg-red-100 text-red-700' :
'bg-amber-100 text-amber-700'
}`}>
{rx.status === 'APPROVED' ? 'تایید شده' : rx.status === 'REJECTED' ? 'عدم تایید' : 'در حال بررسی'}
</span>
<span className="text-[11px] text-medical-gray-400 font-bold">
{toPersian(new Date(rx.createdAt).toLocaleDateString('fa-IR'))}
</span>
</div>
</div>
{rx.notes && (
<p className="text-xs text-medical-gray-600">
<strong className="text-medical-gray-800">توضیحات شما: </strong>
{rx.notes}
</p>
)}
{rx.adminNotes && (
<div className="p-3.5 bg-canina-blue/5 border border-canina-blue/10 rounded-xl text-xs text-canina-blue leading-relaxed font-bold">
<strong className="text-canina-dark block mb-1">پاسخ دامپزشک کنینا:</strong>
{rx.adminNotes.split('__PRESCRIBED_PRODUCTS__:')[0]}
</div>
)}
<div className="pt-2 flex items-center justify-end gap-2">
<a
href={rx.fileUrl.startsWith('http') ? rx.fileUrl : `${BASE_DOMAIN}${rx.fileUrl}`}
target="_blank"
rel="noreferrer"
className="px-4 py-2 bg-white border border-medical-gray-200 text-medical-gray-700 hover:text-canina-blue hover:border-canina-blue rounded-xl text-xs font-bold transition-all"
>
مشاهده تصویر نسخه
</a>
</div>
</div>
))}
</div>
)}
</div>
)}
</motion.div>
</div>
{/* Sidebar Widgets (Wallet & Charity) */}
{(showWallet || showCharity) && (
<div className="lg:col-span-4 space-y-4 sm:space-y-6 min-w-0 order-2 lg:order-2">
{showWallet && (
<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>
{isWalletRechargeAllowed ? (
<button
onClick={() => { handleTabChange("wallet"); setIsTopUpModalOpen(true); }}
disabled={isTopUpModalOpen}
className="mt-5 sm:mt-8 bg-canina-blue text-white w-full py-3.5 sm:py-4 px-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 text-xs sm:text-sm border border-canina-blue cursor-pointer"
>
<ArrowUpCircle className="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" />
<span>شارژ کیف پول</span>
</button>
) : (
<div className="mt-5 sm:mt-8 text-[11px] font-bold text-center text-white/70 bg-white/10 py-3 px-2 rounded-2xl border border-white/20">
شارژ آنلاین موقتاً غیرفعال است
</div>
)}
</div>
</div>
)}
{showCharity && (
<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((Number(profile.charityDonationTotal) || 0).toLocaleString())}
</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((Number(profile.charityDonationTotal) || 0) / 15000).toString())} وعده غذا</span> برای حیوانات بیپناه شده است.
</p>
</div>
</div>
</div>
</div>
</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}
/>
{/* New Ticket Modal */}
{isNewTicketModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
<div className="bg-white w-full max-w-lg rounded-3xl p-6 sm:p-8 shadow-2xl space-y-6 max-h-[90vh] overflow-y-auto" dir="rtl">
<div className="flex items-center justify-between pb-4 border-b border-medical-gray-100">
<div className="flex items-center gap-2">
<div className="w-10 h-10 bg-canina-blue/10 text-canina-blue rounded-xl flex items-center justify-center">
<Stethoscope className="w-5 h-5" />
</div>
<div>
<h3 className="text-lg font-black text-gray-900">ثبت تیکت و مشاوره دامپزشک</h3>
<p className="text-xs text-gray-400 font-medium">پاسخگویی توسط دامپزشکان و کارشناسان کنینا</p>
</div>
</div>
<button
type="button"
onClick={() => setIsNewTicketModalOpen(false)}
className="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 flex items-center justify-center font-bold text-sm cursor-pointer"
>
</button>
</div>
<form onSubmit={handleCreateTicket} className="space-y-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">موضوع تیکت *</label>
<input
required
type="text"
placeholder="مثال: سوال در مورد دوز مصرف مکمل سگ ژرمن"
value={newTicketSubject}
onChange={(e) => setNewTicketSubject(e.target.value)}
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium focus:border-canina-blue outline-none"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">دستهبندی موضوع</label>
<select
value={newTicketCategory}
onChange={(e) => setNewTicketCategory(e.target.value)}
className="w-full px-3 py-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold focus:border-canina-blue outline-none"
>
<option value="VET_CONSULTATION">مشاوره تخصصی دامپزشک</option>
<option value="ORDER_SUPPORT">پیگیری و پشتیبانی سفارش</option>
<option value="PRODUCT_INQUIRY">سوال درباره داروها و مکملها</option>
<option value="GENERAL">عمومی و پیشنهادات</option>
</select>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">حیوان خانگی مرتبط</label>
<select
value={newTicketPetId}
onChange={(e) => setNewTicketPetId(e.target.value)}
className="w-full px-3 py-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold focus:border-canina-blue outline-none"
>
<option value="">بدون انتخاب / عمومی</option>
{pets.map((p) => (
<option key={p.id} value={p.id}>
{p.name} ({p.breed || p.type})
</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">شرح پیام یا سوال پزشکی *</label>
<textarea
required
rows={4}
placeholder="سن، وزن، سابقه بیماری، علائم یا سوال خود را با جزئیات کامل بنویسید..."
value={newTicketMessage}
onChange={(e) => setNewTicketMessage(e.target.value)}
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium focus:border-canina-blue outline-none"
/>
</div>
<div className="pt-2 flex justify-end gap-3">
<button
type="button"
onClick={() => setIsNewTicketModalOpen(false)}
className="px-5 py-2.5 rounded-xl text-xs font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 cursor-pointer"
>
انصراف
</button>
<button
type="submit"
disabled={isSubmittingTicket}
className="px-6 py-2.5 rounded-xl text-xs font-black text-white bg-canina-blue hover:bg-canina-dark disabled:opacity-50 transition-all flex items-center gap-2 cursor-pointer"
>
{isSubmittingTicket ? 'در حال ارسال...' : 'ارسال تیکت'}
</button>
</div>
</form>
</div>
</div>
)}
{/* Ticket Details & Chat Modal */}
{selectedTicket && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
<div className="bg-white w-full max-w-2xl rounded-3xl p-6 sm:p-8 shadow-2xl space-y-6 max-h-[90vh] flex flex-col" dir="rtl">
<div className="flex items-center justify-between pb-4 border-b border-medical-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-canina-blue/10 text-canina-blue rounded-xl flex items-center justify-center">
<MessageSquare className="w-5 h-5" />
</div>
<div>
<h3 className="text-base font-black text-gray-900">{selectedTicket.subject}</h3>
<div className="flex items-center gap-2 text-xs text-gray-400 font-medium mt-0.5">
<span>شناسه: {selectedTicket.ticketNumber}</span>
{selectedTicket.pet && (
<span className="text-purple-600 font-bold"> 🐾 {selectedTicket.pet.name}</span>
)}
</div>
</div>
</div>
<button
type="button"
onClick={() => setSelectedTicket(null)}
className="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 flex items-center justify-center font-bold text-sm cursor-pointer"
>
</button>
</div>
{/* Messages Thread */}
<div className="flex-1 overflow-y-auto space-y-4 p-2 min-h-[220px]">
{(selectedTicket.messages || []).map((msg) => {
const isDoctorOrAdmin = msg.senderRole === 'ADMIN' || msg.senderRole === 'VET_DOCTOR';
return (
<div
key={msg.id}
className={cn(
"p-4 rounded-2xl max-w-[85%] text-xs font-medium leading-relaxed",
isDoctorOrAdmin
? "bg-emerald-50 border border-emerald-200 text-emerald-950 ml-auto"
: "bg-blue-50 border border-blue-200 text-blue-950 mr-auto"
)}
>
<div className="flex items-center justify-between gap-4 mb-1.5 pb-1 border-b border-black/5">
<span className="font-black text-[11px] flex items-center gap-1.5">
{isDoctorOrAdmin ? (
<>
<Stethoscope className="w-3.5 h-3.5 text-emerald-600" />
<span className="text-emerald-700">{msg.senderName || 'پاسخ دامپزشک کنینا'}</span>
</>
) : (
<>
<UserCircle className="w-3.5 h-3.5 text-blue-600" />
<span className="text-blue-700">{msg.senderName || 'شما'}</span>
</>
)}
</span>
<span className="text-[10px] text-gray-400 font-normal">
{new Date(msg.createdAt).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })}
</span>
</div>
<p className="whitespace-pre-wrap">{msg.message}</p>
</div>
);
})}
</div>
{/* Quick Reply Form */}
{selectedTicket.status !== 'CLOSED' ? (
<form onSubmit={handleSendReply} className="pt-3 border-t border-medical-gray-100 flex gap-2">
<input
type="text"
placeholder="پاسخ خود را بنویسید..."
value={replyMessage}
onChange={(e) => setReplyMessage(e.target.value)}
className="flex-1 px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-medium focus:border-canina-blue outline-none"
/>
<button
type="submit"
disabled={isSendingReply || !replyMessage.trim()}
className="px-5 py-3 bg-canina-blue text-white rounded-xl text-xs font-black hover:bg-canina-dark disabled:opacity-50 transition-all flex items-center gap-1.5 shrink-0 cursor-pointer"
>
<Send className="w-3.5 h-3.5" />
<span>ارسال</span>
</button>
</form>
) : (
<div className="p-3 bg-gray-50 rounded-xl text-center text-xs text-gray-500 font-bold">
این تیکت بسته شده است. در صورت نیاز میتوانید تیکت جدیدی ثبت فرمایید.
</div>
)}
</div>
</div>
)}
{/* Wallet Withdrawal Request Modal */}
{isWithdrawModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-fade-in font-vazir">
<div className="bg-white w-full max-w-md rounded-3xl p-6 sm:p-8 shadow-2xl space-y-6" dir="rtl">
<div className="flex items-center justify-between pb-4 border-b border-medical-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-amber-50 text-amber-600 rounded-xl flex items-center justify-center">
<ArrowDownCircle className="w-6 h-6" />
</div>
<div>
<h3 className="text-base font-black text-gray-900">درخواست برداشت و تسویه وجه</h3>
<p className="text-xs text-gray-400 font-bold mt-0.5">
موجودی قابل برداشت: {toPersian((profile.walletBalance || 0).toLocaleString())} تومان
</p>
</div>
</div>
<button
type="button"
onClick={() => setIsWithdrawModalOpen(false)}
className="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 flex items-center justify-center font-bold text-sm cursor-pointer"
>
</button>
</div>
<form onSubmit={handleWithdrawSubmit} className="space-y-4">
<div>
<label className="block text-xs font-black text-gray-700 mb-1">
مبلغ درخواستی (تومان) *
</label>
<input
type="text"
required
placeholder="حداقل ۵۰,۰۰۰ تومان"
value={withdrawAmount}
onChange={(e) => setWithdrawAmount(e.target.value)}
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-bold text-gray-900 focus:border-canina-blue outline-none"
dir="ltr"
/>
</div>
<div>
<label className="block text-xs font-black text-gray-700 mb-1">
شماره شبا یا شماره کارت جهت واریز *
</label>
<input
type="text"
required
placeholder="IR... یا شماره ۱۶ رقمی کارت"
value={withdrawIban}
onChange={(e) => setWithdrawIban(e.target.value)}
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-xs font-mono font-bold text-gray-900 focus:border-canina-blue outline-none"
dir="ltr"
/>
<p className="text-[10px] text-gray-400 font-medium mt-1">
شماره حساب باید به نام صاحب حساب کاربری ({profile.firstName} {profile.lastName}) باشد.
</p>
</div>
<div className="p-3 bg-amber-50 rounded-xl text-[11px] text-amber-800 font-medium leading-relaxed">
واریز مبالغ تسویه پس از تایید واحد مالی کنینا، طی ۲۴ الی ۴۸ ساعت کاری از طریق سامانه پایا انجام خواهد شد.
</div>
<div className="pt-2 flex justify-end gap-3">
<button
type="button"
onClick={() => setIsWithdrawModalOpen(false)}
className="px-5 py-2.5 rounded-xl text-xs font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 cursor-pointer"
>
انصراف
</button>
<button
type="submit"
disabled={isSubmittingWithdrawal}
className="px-6 py-2.5 rounded-xl text-xs font-black text-white bg-canina-blue hover:bg-canina-dark disabled:opacity-50 transition-all flex items-center gap-2 cursor-pointer shadow-md shadow-canina-blue/20"
>
{isSubmittingWithdrawal ? 'در حال ثبت...' : 'ثبت درخواست برداشت'}
</button>
</div>
</form>
</div>
</div>
)}
{/* Order Details & Retry Payment Modal */}
<OrderDetailsModal
isOpen={!!selectedOrder}
onClose={() => setSelectedOrder(null)}
order={selectedOrder}
onOrderUpdated={async () => {
try {
await fetchProfile();
} catch (e) {
console.error(e);
}
}}
/>
</div>
);
}