diff --git a/app/cart/page.tsx b/app/cart/page.tsx index ef5a306..f5559ad 100644 --- a/app/cart/page.tsx +++ b/app/cart/page.tsx @@ -4,7 +4,7 @@ import { useCart } from "@/components/context/cartcontext"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useState } from "react"; // اضافه شدن useState +import { useState, useEffect } from "react"; // اضافه شدن useEffect import { Trash2, ShoppingBag, @@ -16,40 +16,140 @@ import { CreditCard } from "lucide-react"; -// مسیر ایمپورت کامپوننت Notlogin را بر اساس ساختار پوشه‌بندی خود تنظیم کنید import NotLogin from "@/components/Notlogin"; +import { getCartApi } from "@/public/src/services/cart/api"; // ایمپورت API +import { clearServerCartApi } from "@/public/src/services/cart/api"; export default function CartPage() { - const { cart, clearCart, addToCart, decreaseQuantity } = useCart(); + const { + cart, + clearCart, + addToCart, + decreaseQuantity, + } = useCart(); + + // const handleClearAll = async () => { + // // ۱. پاک کردن استیت لوکال + // clearCart(); + + // const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null; + // if (token) { + // try { + // // ۲. پاک کردن از سرور + // await clearServerCartApi(); + + // // ۳. آپدیت کردن صفحه برای دریافت دیتای جدید سرور (سبد خالی) + // router.refresh(); + + // } catch (error) { + // console.error(error); + // } + // } + // }; + + const router = useRouter(); - // استیت برای مدیریت نمایش مودال/کامپوننت لاگین نشده‌ها const [showNotLogin, setShowNotLogin] = useState(false); + // --- استیت‌های مربوط به دریافت اطلاعات سرور --- + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [serverCartItems, setServerCartItems] = useState([]); + const [serverSummary, setServerSummary] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + // --- منطق اصلاح شده دکمه حذف همه --- + const handleClearAll = async () => { + // ۱. پاک کردن استیت‌های لوکال و استیت‌های همین صفحه + clearCart(); + setServerCartItems([]); + setServerSummary(null); + + // ۲. ارسال دستور مستقیم به هدر برای خالی شدن درجا (بدون درنگ) + window.dispatchEvent(new Event('cartCleared')); + + // ۳. فراخوانی API برای پاک کردن دیتابیس در پس‌زمینه + const token = typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null; + if (token) { + try { + await clearServerCartApi(); // متد DELETE + // اینجا دیگر نیازی به cartUpdated نیست چون هدر را به صورت دستی خالی کردیم + } catch (error) { + console.error("خطا در پاک کردن سبد خرید سرور:", error); + } + } + }; + + + // --- useEffect برای بررسی لاگین و دریافت دیتای سرور --- + useEffect(() => { + const fetchServerCart = 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); + } + } else { + setIsLoggedIn(false); + } + setIsLoading(false); + }; + + fetchServerCart(); + }, []); + + // بررسی لاگین کاربر const handleCheckoutNavigation = () => { const token = localStorage.getItem('accessToken'); if (token) { - // اگر لاگین بود برود به چک اوت router.push('/checkout'); } else { - // اگر لاگین نبود، کامپوننت Notlogin نمایش داده شود setShowNotLogin(true); } }; // تبدیل رشته قیمت به عدد - const parsePrice = (priceStr?: number | null) => { + const parsePrice = (priceStr?: number | string | null) => { if (!priceStr) return 0; return Number(priceStr.toString().replace(/,/g, '')); }; - // محاسبه قیمت کل و تعداد کل - const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0); - const totalItems = cart.reduce((total, item) => total + item.quantity, 0); + // --- منطق یکپارچه‌سازی: اگر لاگین بود دیتای سرور، در غیر این‌صورت دیتای لوکال --- + 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; - // دیزاین حالت سبد خرید خالی - if (cart.length === 0) { + // محاسبه قیمت کل و تعداد کل + const totalPrice = isLoggedIn && serverSummary + ? serverSummary.totalPrice || serverSummary.total || 0 + : cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0); + + const totalItems = isLoggedIn && serverSummary + ? serverSummary.totalQuantity || serverSummary.itemsCount || 0 + : cart.reduce((total, item) => total + item.quantity, 0); + + // --- جلوگیری از پرش تصویر قبل از لود شدن دیتا --- + if (isLoading) { + return
در حال بارگذاری...
; + } + + // دیزاین حالت سبد خرید خالی (تغییر cart.length به displayCart.length) + if (displayCart.length === 0) { return (
@@ -61,7 +161,7 @@ export default function CartPage() {

هنوز هیچ محصولی به سبد خرید خود اضافه نکرده‌اید. برای مشاهده محصولات به صفحه اصلی برگردید.

- + بازگشت به فروشگاه @@ -72,11 +172,9 @@ export default function CartPage() { return (
- {/* رندر کردن کامپوننت Notlogin به صورت شرطی */} - {/* اگر نیاز است که کاربر بتواند آن را ببندد، پراپ onClose را به آن پاس بدهید */} {showNotLogin && ( - setShowNotLogin(false)} /> )} @@ -106,7 +204,6 @@ export default function CartPage() { سبد خرید - {/* کلیک روی آیکون اطلاعات ارسال */}
@@ -130,14 +227,15 @@ export default function CartPage() {

محصولات انتخاب شده

-
- {cart.map((item) => { + {/* حلقه مپ روی displayCart اعمال شده است */} + {displayCart.map((item) => { const itemTotal = parsePrice(item.price) * item.quantity; return (
@@ -153,7 +251,7 @@ export default function CartPage() { {item.price ? `${itemTotal.toLocaleString('fa-IR')} ت` : 'استعلام'}
- {item.quantity} @@ -191,10 +289,9 @@ export default function CartPage() {
- {/* دکمه تایید و ادامه */}
- {/* 4. نمایش وضعیت لودینگ یا لیست محصولات */} {loading ? (

در حال بارگذاری محصولات...

) : (
- {products.slice(-4).map((product: any) => ( + {products.slice(0,4).map((product: any) => ( ))}
diff --git a/app/product/[slug]/page.tsx b/app/product/[slug]/page.tsx index 0717f7b..14eb7ff 100644 --- a/app/product/[slug]/page.tsx +++ b/app/product/[slug]/page.tsx @@ -3,6 +3,7 @@ import { notFound } from 'next/navigation'; import Image from "next/image"; import ProductCartAction from '@/components/cartaction'; import { getProductBySlug } from "@/public/src/services/products/api"; +import Link from 'next/link'; interface PageProps { params: Promise<{ slug: string }>; @@ -10,7 +11,7 @@ interface PageProps { export default async function SingleProductPage({ params }: PageProps) { - const resolvedParams = await params; + const resolvedParams = await params; const slug = resolvedParams.slug; console.log("👉 Extracted slug from URL:", slug); @@ -47,18 +48,18 @@ export default async function SingleProductPage({ params }: PageProps) { return (
-
+
{/* مسیر راهنما */}
- خانه > محصولات > {product.title} + خانه > محصولات > {product.title}
- + {/* بخش 1: معرفی محصول */} -
+
{/* باکس تصویر */}
@@ -80,7 +81,7 @@ export default async function SingleProductPage({ params }: PageProps) {
{product.brand}
-

+

{product.title}

@@ -89,20 +90,20 @@ export default async function SingleProductPage({ params }: PageProps) {

- {/* رندر داینامیک ۳ ویژگی اول از API */} - {product.attributes?.slice(0, 3).map((attr: any) => ( -
- {attr.name} - {attr.valueText || '-'} -
- ))} + {/* رندر داینامیک ۳ ویژگی اول از API */} + {product.attributes?.slice(0, 2).map((attr: any) => ( +
+ {attr.name} + {attr.valueText || '-'} +
+ ))} - {/* آیتم چهارم: دسته‌بندی (برای حفظ ظاهر گرید ۴ تایی) */} -
- دسته‌بندی - {product.primaryCategory?.name || '-'} -
-
+ {/* آیتم چهارم: دسته‌بندی (برای حفظ ظاهر گرید ۴ تایی) */} + {/*
+ دسته‌بندی + {product.primaryCategory?.name || '-'} +
*/} +
@@ -110,7 +111,7 @@ export default async function SingleProductPage({ params }: PageProps) { {/* بخش 2: سایدبار و دکمه خرید */}
-
+
{hasStock ? ( @@ -180,7 +181,7 @@ export default async function SingleProductPage({ params }: PageProps) {
{/* بخش 3: مشخصات ابعادی */} -
+

مشخصات ابعادی دقیق

@@ -206,29 +207,16 @@ export default async function SingleProductPage({ params }: PageProps) {
- - - - - - - - - - - - - - - - - - - - + {product.attributes?.map((attr: any) => ( + + + + + ))} + - - + +
قطر داخلی ($d$){getAttribute('قطر داخلی')}
قطر خارجی ($D$){getAttribute('قطر خارجی')}
پهنا ($B$){getAttribute('پهنا')}
وزن خالص{getAttribute('وزن')}
جنس قفسه{getAttribute('جنس قفسه')}
{attr.name}:{attr.valueText || '-'}
کد بین‌المللی{product.technicalCode}کد بین‌المللی:{product.technicalCode}
diff --git a/app/products/page.tsx b/app/products/page.tsx index 94ac09e..ef58634 100644 --- a/app/products/page.tsx +++ b/app/products/page.tsx @@ -26,3 +26,4 @@ export default async function ProductsPage() {
); } + diff --git a/components/clientProduct.tsx b/components/clientProduct.tsx index 2d2986f..f1e1fd5 100644 --- a/components/clientProduct.tsx +++ b/components/clientProduct.tsx @@ -57,7 +57,7 @@ export default function ProductGrid({ products }: { products: any[] }) { ); diff --git a/components/context/cartcontext.tsx b/components/context/cartcontext.tsx index 9294bb6..0213e57 100644 --- a/components/context/cartcontext.tsx +++ b/components/context/cartcontext.tsx @@ -3,17 +3,6 @@ import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import { Product } from '@/public/src/types/product'; -// export interface Product { -// id: string; -// title: string; -// image: string; -// l: string; -// d: string; -// brand: string; -// price?: string | null; -// badge?: string; -// stock: boolean; -// } export interface CartItem extends Product { quantity: number; diff --git a/components/header.tsx b/components/header.tsx index 5cc875c..54f1c6a 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -11,6 +11,7 @@ import { registerUser } from '@/public/src/services/auth/api'; 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'; const topBarLinks = [ { label: "بخش صنعتی", href: "/" }, @@ -129,7 +130,48 @@ export function Header() { const [showLoginPassword, setShowLoginPassword] = useState(false); const [loginMobile, setLoginMobile] = useState(""); const [showRegisterSuccessDialog, setShowRegisterSuccessDialog] = useState(false); + const [isOptimistic, setIsOptimistic] = useState(false); + // ۱. افکت اول: وقتی کانتکست لوکال (cart) با کلیک کاربر آپدیت می‌شود، + // منو را به حالت Optimistic می‌بریم تا تغییرات را درجا نشان دهد. + useEffect(() => { + setIsOptimistic(true); + // بعد از ۱.۵ ثانیه (زمانی که قاعدتاً API سرور کارش تمام شده) به حالت عادی برمی‌گردد + const timer = setTimeout(() => setIsOptimistic(false), 1500); + return () => clearTimeout(timer); + }, [cart]); + // ۲. افکت دوم: دریافت اطلاعات سرور در پس‌زمینه بدون رفرش صفحه + 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); + } + } + }; + + // گوش دادن به سیگنالی که از ProductCard ارسال می‌شود + window.addEventListener('cartUpdated', fetchServerData); + + // فراخوانی در لود اولیه + fetchServerData(); + + return () => window.removeEventListener('cartUpdated', fetchServerData); + }, []); + + // محاسبه قیمت کل لوکال (برای زمانی که کاربر لاگین نیست یا در حالت Optimistic هستیم) + const localTotalPrice = cart.reduce((total, item) => { + const price = item.price ? Number(item.price.toString().replace(/,/g, '')) : 0; + return total + (price * item.quantity); + }, 0); const [loginForm, setLoginForm] = useState({ @@ -212,9 +254,9 @@ export function Header() { localStorage.setItem("username", username); localStorage.setItem("fullName", displayName); localStorage.setItem("role", role.toLowerCase()); - console.log(res); + console.log(res); + - setUser({ username, displayName }); setIsOpen(false); setShowRegisterSuccessDialog(true); @@ -267,7 +309,7 @@ export function Header() { localStorage.setItem("refreshToken", res.data.refreshToken); localStorage.setItem("username", username); localStorage.setItem("fullName", displayName); - console.log(res); + console.log(res); setUser({ username, displayName }); setIsOpen(false); router.push("/dashboard?success=login"); @@ -373,6 +415,133 @@ export function Header() { } }; + + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [serverCartItems, setServerCartItems] = useState([]); + const [serverSummary, setServerSummary] = useState(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); // کلین‌آپ لیسنر جدید + // }; + // }, []); + + + + + // دریافت اطلاعات سبد خرید از سرور + useEffect(() => { + const fetchServerCart = 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); + } + } else { + setIsLoggedIn(false); + } + }; + // --- این تابع جدید را اضافه کنید --- + const handleCartClear = () => { + setServerCartItems([]); // درجا لیست منو را خالی می‌کند + setServerSummary(null); + }; + // گوش دادن به رویدادها + window.addEventListener('cartUpdated', fetchServerCart); + window.addEventListener('cartCleared', handleCartClear); // لیسنر جدید + + fetchServerCart(); + return () => { + window.removeEventListener('cartUpdated', fetchServerCart); + window.removeEventListener('cartCleared', handleCartClear); // کلین‌آپ لیسنر جدید + }; + // در صورت نیاز به آپدیت شدن دراپ‌داون با هر تغییر، می‌توانید این تابع را به یک 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, + 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; + + const displayTotalQuantity = (isLoggedIn && !isOptimistic) && serverSummary + ? serverSummary.totalQuantity || serverSummary.itemsCount || 0 + : cart.reduce((total, item) => total + item.quantity, 0); + + const displayTotalPrice = (isLoggedIn && !isOptimistic) && serverSummary + ? serverSummary.totalPrice || serverSummary.total || 0 + : localTotalPrice; + + + return (
@@ -573,9 +742,9 @@ export function Header() { - {cart.length > 0 && ( + {displayCart.length > 0 && ( - {cart.reduce((total, item) => total + item.quantity, 0)} + {displayTotalQuantity} )} @@ -583,7 +752,7 @@ export function Header() {
- {cart.length === 0 ? ( + {displayCart.length === 0 ? (
سبد خرید شما خالی است @@ -591,11 +760,11 @@ export function Header() { ) : ( <>
- {cart.length} کالا + {displayCart.length} کالا مشاهده سبد خرید
- {cart.slice(0, 3).map((item) => { + {displayCart.slice(0, 3).map((item) => { const itemTotal = item.price ? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR') : null; return (
@@ -619,12 +788,12 @@ export function Header() {
) })} - {cart.length > 3 && (

و {cart.length - 3} کالای دیگر...

)} + {displayCart.length > 3 && (

و {displayCart.length - 3} کالای دیگر...

)}
مبلغ قابل پرداخت: - {totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'} + {displayTotalPrice > 0 ? `${displayTotalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}
ثبت سفارش
@@ -633,6 +802,7 @@ export function Header() {
+ {!user ? ( - - ))} - -
- - {/* بخش برندها */} -
-

برندها

-
    - {brands.map((brand) => ( -
  • - -
  • - ))} -
-
-
- ); -} diff --git a/components/productcard.tsx b/components/productcard.tsx index 28773e0..d2fc43b 100644 --- a/components/productcard.tsx +++ b/components/productcard.tsx @@ -5,13 +5,12 @@ import Image from "next/image"; import Link from 'next/link'; import { useCart } from "./context/cartcontext"; import { Product } from "@/public/src/types/product"; - +import { addToCartApi } from "@/public/src/services/cart/api"; interface ProductCardProps { product: Product; } - export default function ProductCard({ product }: ProductCardProps) { const { addToCart, decreaseQuantity, cart } = useCart(); @@ -19,17 +18,45 @@ export default function ProductCard({ product }: ProductCardProps) { const quantity = cartItem ? cartItem.quantity : 0; const slug = product.slug; - - const handleIncrease = (e: React.MouseEvent) => { + const handleIncrease = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); + + // ۱. آپدیت آنی سبد خرید محلی (این کار باعث آپدیت درجا در UI می‌شود) addToCart(product); + + // ۲. ارسال درخواست به سرور در پس‌زمینه + const token = localStorage.getItem('accessToken'); + if (token) { + try { + await addToCartApi(product.id, 1); + // ارسال سیگنال به هدر (منو) برای دریافت اطلاعات جدید سرور بدون رفرش + window.dispatchEvent(new Event('cartUpdated')); + } catch (error) { + console.error("خطا در افزودن محصول به سبد خرید سرور:", error); + } + } }; - const handleDecrease = (e: React.MouseEvent) => { + // --- هندلر کاهش / حذف از سبد خرید --- + const handleDecrease = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); + decreaseQuantity(product.id); + + const token = localStorage.getItem('accessToken'); + if (token) { + try { + // فرض بر این است که API مربوط به کاهش را در اینجا فراخوانی می‌کنید + // await decreaseFromCartApi(product.id); + + // پس از موفقیت‌آمیز بودن حذف/کاهش از سرور، دوباره سیگنال می‌دهیم + window.dispatchEvent(new Event('cartUpdated')); + } catch (error) { + console.error("خطا در کاهش محصول از سبد خرید سرور:", error); + } + } }; const formattedPrice = product.price @@ -37,7 +64,6 @@ export default function ProductCard({ product }: ProductCardProps) { : null; - return ( @@ -59,30 +85,21 @@ export default function ProductCard({ product }: ProductCardProps) {

{product.brand}

{product.title}

- {/* - ✅ این بخش را تغییر می‌دهیم تا نام attribute ها را بگیریم - اگر attribute اول وجود داشت، نامش را بگیر و نمایش بده - اگر attribute اول نبود، نمایش نده - */} {product.attributes?.[0] && (
-

{product.attributes[0].name}:

{/* 👈 نام attribute اول */} -

{product.attributes[0].valueText || "-"}

{/* 👈 مقدار attribute اول */} +

{product.attributes[0].name}:

+

{product.attributes[0].valueText || "-"}

)} - {/* - ✅ مشابه بالا برای attribute دوم - */} {product.attributes?.[1] && (
-

{product.attributes[1].name}:

{/* 👈 نام attribute دوم */} -

{product.attributes[1].valueText || "-"}

{/* 👈 مقدار attribute دوم */} +

{product.attributes[1].name}:

+

{product.attributes[1].valueText || "-"}

)}
- {product.price ? (
diff --git a/next.config.ts b/next.config.ts index 66e1566..f2552e7 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ reactCompiler: true, + images: { + domains: ['s3.ir-thr-at1.arvanstorage.ir'], // add this line + }, }; export default nextConfig; diff --git a/public/src/hook/UseProductFilters.tsx b/public/src/hook/UseProductFilters.tsx new file mode 100644 index 0000000..bf3e6ec --- /dev/null +++ b/public/src/hook/UseProductFilters.tsx @@ -0,0 +1,24 @@ +// hooks/useProductFilters.ts +import { useSearchParams, useRouter, usePathname } from 'next/navigation'; + +export function useProductFilters() { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + + const setFilter = (key: string, value: string | null) => { + const params = new URLSearchParams(searchParams.toString()); + if (value) params.set(key, value); + else params.delete(key); + + // بازگشت به صفحه اول هنگام تغییر فیلتر + params.set('page', '1'); + router.push(`${pathname}?${params.toString()}`); + }; + + return { + category: searchParams.get('category'), + brand: searchParams.get('brand'), + setFilter + }; +} diff --git a/public/src/services/cart/api.tsx b/public/src/services/cart/api.tsx new file mode 100644 index 0000000..7160763 --- /dev/null +++ b/public/src/services/cart/api.tsx @@ -0,0 +1,103 @@ +import { API_BASE_URL } from "../config"; + +// تابع دریافت توکن از لوکال استوریج +const getAuthHeaders = () => { + const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null; + return { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + }; +}; + + +// ۱. API دریافت سبد خرید (GET) +export async function getCartApi() { + try { + const response = await fetch(`${API_BASE_URL}/users/me/cart`, { + method: 'GET', + headers: getAuthHeaders(), + }); + + if (!response.ok) { + // دریافت متن دقیق خطای سرور + const errorText = await response.text(); + console.error("جزئیات خطای سرور (دریافت سبد):", response.status, errorText); + throw new Error(`خطا در دریافت سبد خرید: کد ${response.status}`); + } + + const data = await response.json(); + return data.data; // برگرداندن بخش data که شامل items و summary است + } catch (error) { + console.error(error); + return null; + } +} + + +// ۲. API افزودن محصول به سبد خرید (POST) +export async function addToCartApi(productId: string, quantity: number = 1) { + try { + const response = await fetch(`${API_BASE_URL}/users/me/cart/items`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ productId, quantity }), + }); + + if (!response.ok) { + // دریافت متن دقیق خطای سرور + const errorText = await response.text(); + console.error("جزئیات خطای سرور (دریافت سبد):", response.status, errorText); + throw new Error(`خطا در دریافت سبد خرید: کد ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + console.error(error); + return null; + } +} + + +// cartApi.js +export const updateCartItemApi = async (itemId:any, quantity:any, token:any) => { + try { + const response = await fetch(`${API_BASE_URL}/users/me/cart/items/${itemId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ quantity }) + }); + + if (!response.ok) { + throw new Error('خطا در به‌روزرسانی سبد خرید'); + } + + const data = await response.json(); + return data; // حاوی items و summary به‌روزشده + } catch (error) { + console.error("Error updating cart item:", error); + throw error; + } + }; + + export const clearServerCartApi = async () => { + const token = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null; + const headers = { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + }; + + const response = await fetch(`${API_BASE_URL}/users/me/cart`, { + method: 'DELETE', + headers: headers, + }); + + if (!response.ok) { + throw new Error('خطا در پاک کردن سبد خرید سرور'); + } + + return await response.json(); +}; diff --git a/public/src/services/products/api.tsx b/public/src/services/products/api.tsx index 7593dc1..d88ab0d 100644 --- a/public/src/services/products/api.tsx +++ b/public/src/services/products/api.tsx @@ -92,3 +92,26 @@ export async function getProductBySlug(slug: string) { return null; } } + +export async function getCategoryFilters(slug: string) { + try { + const url = `${API_BASE_URL}/products/categories/${slug}/filters?page=1&limit=20`; + const res = await fetch(url, { method: "GET", headers: { Accept: "application/json" } }); + if (!res.ok) throw new Error("Failed to fetch category filters"); + return (await res.json()).data; + } catch (error) { + return null; + } +} + +// دریافت فیلترهای مرتبط با یک برند +export async function getBrandFilters(slug: string) { + try { + const url = `${API_BASE_URL}/products/brands/${slug}/filters?page=1&limit=20`; + const res = await fetch(url, { method: "GET", headers: { Accept: "application/json" } }); + if (!res.ok) throw new Error("Failed to fetch brand filters"); + return (await res.json()).data; + } catch (error) { + return null; + } +} \ No newline at end of file