Auth Modal / All Cart API's Done and Local storage Logic handle / Add cart Control button

This commit is contained in:
haniyeroozmand
2026-04-08 22:43:40 +03:30
parent 8be715b34b
commit 411109ee7e
8 changed files with 959 additions and 291 deletions

View File

@@ -12,6 +12,7 @@ import { loginUser } from '@/public/src/services/auth/api';
import { logoutUser } from '@/public/src/services/auth/api';
import { useCategories } from './context/categoryprovider';
import { getCartApi } from '@/public/src/services/cart/api';
import AuthModal from './Auth';
const topBarLinks = [
{ label: "بخش صنعتی", href: "/" },
@@ -109,6 +110,7 @@ const dashboardMenuItems = [
export function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const pathname = usePathname();
const { cart, removeFromCart } = useCart();
const [searchTerm, setSearchTerm] = useState('');
@@ -131,8 +133,8 @@ export function Header() {
const [loginMobile, setLoginMobile] = useState("");
const [showRegisterSuccessDialog, setShowRegisterSuccessDialog] = useState(false);
const [isOptimistic, setIsOptimistic] = useState(false);
// ۱. افکت اول: وقتی کانتکست لوکال (cart) با کلیک کاربر آپدیت می‌شود،
// منو را به حالت Optimistic می‌بریم تا تغییرات را درجا نشان دهد.
useEffect(() => {
setIsOptimistic(true);
// بعد از ۱.۵ ثانیه (زمانی که قاعدتاً API سرور کارش تمام شده) به حالت عادی برمی‌گردد
@@ -366,13 +368,10 @@ export function Header() {
} finally {
clearAuthState();
setUserMenuOpen(false);
router.push("/");
window.location.href = "/";
}
};
const parsePrice = (priceStr?: number | null) => {
if (!priceStr) return 0;
return Number(priceStr.toString().replace(/,/g, ''));
@@ -420,41 +419,6 @@ export function Header() {
const [serverCartItems, setServerCartItems] = useState<any[]>([]);
const [serverSummary, setServerSummary] = useState<any>(null);
// // درون کامپوننت هدر شما
// useEffect(() => {
// const fetchServerData = async () => {
// const token = localStorage.getItem('accessToken');
// if (token) {
// setIsLoggedIn(true);
// try {
// const data = await getCartApi();
// if (data) {
// setServerCartItems(data.items || []);
// setServerSummary(data.summary || null);
// }
// } catch (error) {
// console.error("خطا در همگام‌سازی سبد خرید سرور:", error);
// }
// }
// };
// // --- این تابع جدید را اضافه کنید ---
// const handleCartClear = () => {
// setServerCartItems([]); // درجا لیست منو را خالی می‌کند
// setServerSummary(null);
// };
// // گوش دادن به رویدادها
// window.addEventListener('cartUpdated', fetchServerData);
// window.addEventListener('cartCleared', handleCartClear); // لیسنر جدید
// fetchServerData();
// return () => {
// window.removeEventListener('cartUpdated', fetchServerData);
// window.removeEventListener('cartCleared', handleCartClear); // کلین‌آپ لیسنر جدید
// };
// }, []);
@@ -478,15 +442,15 @@ export function Header() {
setIsLoggedIn(false);
}
};
// --- این تابع جدید را اضافه کنید ---
const handleCartClear = () => {
setServerCartItems([]); // درجا لیست منو را خالی می‌کند
setServerSummary(null);
};
// گوش دادن به رویدادها
window.addEventListener('cartUpdated', fetchServerCart);
window.addEventListener('cartCleared', handleCartClear); // لیسنر جدید
// --- این تابع جدید را اضافه کنید ---
const handleCartClear = () => {
setServerCartItems([]); // درجا لیست منو را خالی می‌کند
setServerSummary(null);
};
// گوش دادن به رویدادها
window.addEventListener('cartUpdated', fetchServerCart);
window.addEventListener('cartCleared', handleCartClear); // لیسنر جدید
fetchServerCart();
return () => {
window.removeEventListener('cartUpdated', fetchServerCart);
@@ -495,32 +459,6 @@ export function Header() {
// در صورت نیاز به آپدیت شدن دراپ‌داون با هر تغییر، می‌توانید این تابع را به یک Event یا Context متصل کنید
}, []);
// --- متغیرهای هوشمند برای جایگزینی در UI ---
// const displayCart = isLoggedIn
// ? serverCartItems.map(item => ({
// id: item.product?.id || item.productId,
// title: item.product?.title || "بدون نام",
// brand: item.product?.brand || "متفرقه",
// price: item.unitPrice || item.product?.price || 0,
// quantity: item.quantity || 1,
// image: item.product?.mainImageUrl || item.product?.image || "/placeholder.png"
// }))
// : cart; // cart از useCart() می‌آید
// const displayTotalQuantity = isLoggedIn && serverSummary
// ? serverSummary.totalQuantity || serverSummary.itemsCount || 0
// : cart.reduce((total, item) => total + item.quantity, 0);
// const displayTotalPrice = isLoggedIn && serverSummary
// ? serverSummary.totalPrice || serverSummary.total || 0
// : totalPrice;
// --- متغیرهای هوشمند اصلاح شده ---
// اگر لاگین باشیم و در لحظه‌ی کلیک (Optimistic) نباشیم، دیتای سرور را نشان می‌دهد
// در غیر این صورت (برای نمایش آنی) دیتای لوکال را نشان می‌دهد.
const displayCart = (isLoggedIn && !isOptimistic)
? serverCartItems.map(item => ({
id: item.product?.id || item.productId,
@@ -805,7 +743,8 @@ export function Header() {
{!user ? (
<button
onClick={() => setIsOpen(true)}
// onClick={() => setIsOpen(true)}
onClick={() => setIsAuthModalOpen(true)}
className="flex cursor-pointer items-center gap-2 px-3 py-2.5 bg-white border border-gray-300/60 rounded-xl text-xs text-gray-700 hover:bg-gray-50 transition"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-4 h-4 text-gray-500">
@@ -867,6 +806,17 @@ export function Header() {
)}
{/* فراخوانی مودال احراز هویت */}
<AuthModal
isOpen={isAuthModalOpen}
onClose={() => setIsAuthModalOpen(false)}
onLoginSuccess={(userData) => setUser(userData)}
onRegisterSuccess={(userData) => {
setUser(userData);
setShowRegisterSuccessDialog(true);
}}
/>
{/*
{isOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-[#1A2332]/40 backdrop-blur-sm transition-opacity" dir="rtl">
@@ -877,7 +827,6 @@ export function Header() {
<div className="relative w-full max-w-sm bg-white rounded-3xl shadow-2xl p-8">
{/* دکمه بستن */}
<button
onClick={() => setIsOpen(false)}
className="absolute cursor-pointer top-4 left-5 text-gray-400 hover:text-gray-700 transition"
@@ -887,7 +836,6 @@ export function Header() {
<div className="flex flex-col items-center mt-2 w-full">
{/* Tabs */}
<div className="flex w-full bg-gray-100 rounded-xl p-1 mb-6">
<button
onClick={() => setActiveTab("login")}
@@ -911,7 +859,6 @@ export function Header() {
</div>
{/* ---------------- LOGIN ---------------- */}
{activeTab === "login" && (
<div className="w-full space-y-5">
@@ -1046,7 +993,6 @@ export function Header() {
{false && activeTab === "login" && (
<div className="w-full">
{/* فرم موبایل */}
{formType === "mobile" && (
<div className="w-full space-y-4">
@@ -1070,7 +1016,6 @@ export function Header() {
</div>
)}
{/* فرم یوزر پس */}
{formType === "password" && (
<div className="w-full space-y-4">
@@ -1108,14 +1053,12 @@ export function Header() {
className="absolute cursor-pointer left-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700 p-1" // پدینگ اضافه شد تا کلیک راحت‌تر باشد
>
{showLoginPassword ? (
// آیکون چشم خط خورده (eye-off)
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.8">
<path strokeLinecap="round" strokeLinejoin="round"
d="M3 3l18 18M10.477 10.477A3 3 0 0113.5 13.5m-7.09-2.664A9.956 9.956 0 003 12s2.91-6 9-6a9.953 9.953 0 016.328 2.318M15.54 15.54A9.953 9.953 0 0112 18c-6.09 0-9-6-9-6a9.956 9.956 0 012.41-3.868" />
</svg>
) : (
// آیکون چشم باز (eye)
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.8">
<path strokeLinecap="round" strokeLinejoin="round"
@@ -1149,7 +1092,6 @@ export function Header() {
</div>
)}
{/* ---------------- REGISTER ---------------- */}
{activeTab === "register" && (
<div className="w-full space-y-4">
<div className="rounded-2xl border border-gray-200 bg-gray-50 px-4 py-4 text-right">
@@ -1298,14 +1240,12 @@ export function Header() {
className="absolute cursor-pointer left-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
>
{showPassword ? (
// eye-off icon
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.8">
<path strokeLinecap="round" strokeLinejoin="round"
d="M3 3l18 18M10.477 10.477A3 3 0 0113.5 13.5m-7.09-2.664A9.956 9.956 0 003 12s2.91-6 9-6a9.953 9.953 0 016.328 2.318M15.54 15.54A9.953 9.953 0 0112 18c-6.09 0-9-6-9-6a9.956 9.956 0 012.41-3.868" />
</svg>
) : (
// eye icon
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none"
viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.8">
<path strokeLinecap="round" strokeLinejoin="round"
@@ -1317,16 +1257,6 @@ export function Header() {
</button>
</div>
{/* <input
name="confirmPassword"
value={registerForm.confirmPassword}
onChange={handleChange}
type="password"
placeholder="تکرار رمز عبور"
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
/> */}
<button
onClick={handleRegister}
className="w-full cursor-pointer py-3.5 bg-[#ffb900] hover:bg-[#e5a600] text-[#1A2332] font-semibold rounded-2xl text-sm"
@@ -1347,7 +1277,7 @@ export function Header() {
</div>
</div>
)}
)} */}
{showRegisterSuccessDialog && (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-[#1A2332]/45 p-4 backdrop-blur-sm" dir="rtl">