- {product.stock ? (
+ {hasStock ? (
- موجود در انبار شیراز
+ موجود در انبار ({product.stock} عدد)
) : (
@@ -121,7 +135,7 @@ export default async function SingleProductPage({ params }: PageProps) {
{formattedPrice ? (
<>
{formattedPrice}
- تومان
+ {product.currency_label}
>
) : (
استعلام قیمت
@@ -143,6 +157,7 @@ export default async function SingleProductPage({ params }: PageProps) {
- {/* ========================================= */}
- {/* بخش 3: مشخصات ابعادی (پایین ستون راست) */}
- {/* دسکتاپ: 8 ستون عرض دارد و به طور خودکار زیر بخش معرفی قرار میگیرد */}
- {/* موبایل: با order-3 در انتهای صفحه قرار میگیرد */}
- {/* ========================================= */}
+ {/* بخش 3: مشخصات ابعادی */}
@@ -191,10 +202,10 @@ export default async function SingleProductPage({ params }: PageProps) {
- {"$D=" + (product.l ? product.l.replace('mm', '').trim() : '') + "$"}
+ {"$D=" + getAttribute('قطر خارجی').replace('mm', '').trim() + "$"}
- {"$d=" + (product.d ? product.d.replace('mm', '').trim() : '') + "$"}
+ {"$d=" + getAttribute('قطر داخلی').replace('mm', '').trim() + "$"}
@@ -204,27 +215,27 @@ export default async function SingleProductPage({ params }: PageProps) {
قطر داخلی ($d$)
- {product.d}
+ {getAttribute('قطر داخلی')}
قطر خارجی ($D$)
- {product.l}
+ {getAttribute('قطر خارجی')}
پهنا ($B$)
- {product.width}
+ {getAttribute('پهنا')}
وزن خالص
- {product.weight}
+ {getAttribute('وزن')}
جنس قفسه
- {product.cageMaterial}
+ {getAttribute('جنس قفسه')}
کد بینالمللی
- {product.intCode}
+ {product.technicalCode}
diff --git a/app/products/page.tsx b/app/products/page.tsx
index 7c0ca36..e109338 100644
--- a/app/products/page.tsx
+++ b/app/products/page.tsx
@@ -1,15 +1,29 @@
// app/products/page.tsx
-import ProductCard from '@/components/productcard'; // مسیر را با توجه به ساختار خود اصلاح کنید
-import { products } from '@/lib/data'; // ایمپورت دیتای مرکزی
+import ProductCard from '@/components/productcard';
+import { getProducts } from '@/public/src/services/products/api';
+
+export default async function ProductsPage() {
+ const data = await getProducts(1, 20);
+
+ const products = data.items.map((p: any) => ({
+ id: p.id,
+ title: p.title,
+ brand: p.brand,
+ price: p.calculated_price,
+ stock: p.stock,
+ slug: p.slug,
+ image: p.mainImageUrl || "/placeholder.png",
+ attributes: p.attributes
+ }));
-export default function ProductsPage() {
return (
همه محصولات
-
+
+
- {products.map((product) => (
-
+ {products.map((product: any) => (
+
))}
diff --git a/components/context/cartcontext.tsx b/components/context/cartcontext.tsx
index b069a22..9294bb6 100644
--- a/components/context/cartcontext.tsx
+++ b/components/context/cartcontext.tsx
@@ -1,18 +1,19 @@
'use client';
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 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/productcard.tsx b/components/productcard.tsx
index 29a33b8..4613286 100644
--- a/components/productcard.tsx
+++ b/components/productcard.tsx
@@ -1,31 +1,24 @@
-'use client';
+'use client';
import { Phone, Plus, ShoppingCart, Minus, Trash2 } from "lucide-react";
import Image from "next/image";
import Link from 'next/link';
-import { useCart, Product } from "./context/cartcontext";
+import { useCart } from "./context/cartcontext";
+import { Product } from "@/public/src/types/product";
+
interface ProductCardProps {
product: Product;
}
+
export default function ProductCard({ product }: ProductCardProps) {
const { addToCart, decreaseQuantity, cart } = useCart();
-
- // پیدا کردن محصول در سبد خرید
+
const cartItem = cart.find(item => item.id === product.id);
const quantity = cartItem ? cartItem.quantity : 0;
+ const slug = product.slug;
- const generateSlug = (text: string) => {
- if (!text) return "";
- return text
- .trim()
- .replace(/[\s\u200c]+/g, '-')
- .replace(/[^\w\u0600-\u06FF0-9\-]/g, '')
- .replace(/\-\-+/g, '-');
- };
-
- const slug = generateSlug(product.title);
const handleIncrease = (e: React.MouseEvent) => {
e.preventDefault();
@@ -39,13 +32,14 @@ export default function ProductCard({ product }: ProductCardProps) {
decreaseQuantity(product.id);
};
- // پاکسازی قیمت از ویرگول و تبدیل به عدد برای نمایش صحیح
- const formattedPrice = product.price
- ? Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')
+ const formattedPrice = product.price
+ ? Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')
: null;
-
+
+
+
return (
-
+
{product.brand}
{product.title}
-
-
-
قطر داخلی (L):
-
{product.l}
-
+ {/*
+ ✅ این بخش را تغییر میدهیم تا نام attribute ها را بگیریم
+ اگر attribute اول وجود داشت، نامش را بگیر و نمایش بده
+ اگر attribute اول نبود، نمایش نده
+ */}
+ {product.attributes?.[0] && (
+
+
{product.attributes[0].name}:
{/* 👈 نام attribute اول */}
+
{product.attributes[0].valueText || "-"}
{/* 👈 مقدار attribute اول */}
+
+ )}
-
-
قطر خارجی (D):
-
{product.d}
-
+ {/*
+ ✅ مشابه بالا برای attribute دوم
+ */}
+ {product.attributes?.[1] && (
+
+
{product.attributes[1].name}:
{/* 👈 نام attribute دوم */}
+
{product.attributes[1].valueText || "-"}
{/* 👈 مقدار attribute دوم */}
+
+ )}
- {/* بخش قیمت و دکمه خرید */}
+
{product.price ? (
{formattedPrice} تومان
- {/* بررسی موجودی برای نمایش دکمه سبد خرید */}
{product.stock ? (
quantity > 0 ? (
-
{
e.preventDefault();
e.stopPropagation();
}}
>
-
-
+
{quantity}
-
-
@@ -113,7 +117,7 @@ export default function ProductCard({ product }: ProductCardProps) {
) : (
-
)
) : (
- // جایگزین دکمه خرید وقتی محصول ناموجود است
عدم موجودی
diff --git a/next-env.d.ts b/next-env.d.ts
index 9edff1c..c4b7818 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/public/src/services/auth/api.tsx b/public/src/services/auth/api.tsx
index 756a364..c6a9403 100644
--- a/public/src/services/auth/api.tsx
+++ b/public/src/services/auth/api.tsx
@@ -30,7 +30,6 @@ export async function registerUser(data: RegisterPayload) {
}
-
export async function loginUser(data: LoginPayload) {
const response = await fetch(
diff --git a/public/src/services/products/api.tsx b/public/src/services/products/api.tsx
index 4bb41dd..f74723c 100644
--- a/public/src/services/products/api.tsx
+++ b/public/src/services/products/api.tsx
@@ -1,49 +1,95 @@
-import { ProductsResponse } from "../../types/product";
-const BASE_URL = "https://parsshop-back.mugit.ir/api";
+import { API_BASE_URL } from "../config";
-export const productService = {
- /**
- * دریافت لیست تمام محصولات
- * @param page شماره صفحه (پیشفرض: 1)
- * @param limit تعداد در هر صفحه (پیشفرض: 20)
- * @returns ProductsResponse
- */
- async getProducts(page: number = 1, limit: number = 20): Promise {
- try {
- // استفاده از URLSearchParams برای مدیریت تمیزتر کوئری پارامترها (در صورت نیاز)
- const url = new URL(`${BASE_URL}/products`);
- // اگر API شما از کوئری پشتیبانی میکند، میتوانید به این شکل ارسال کنید:
- // url.searchParams.append('page', page.toString());
- // url.searchParams.append('limit', limit.toString());
+export async function getProductsByCategory(
+ slug: string,
+ page = 1,
+ limit = 20
+) {
+ try {
+ const url = `${API_BASE_URL}/products/categories/${slug}?page=${page}&limit=${limit}`;
- const response = await fetch(url.toString(), {
- method: "GET",
- headers: {
- // هدرهای ضروری بر اساس مستندات شما (معمولاً کلاینت فقط Accept را میفرستد)
- "Accept": "application/json",
- "Content-Type": "application/json; charset=utf-8",
- },
- // در Next.js برای کشینگ میتوانید از گزینههای زیر استفاده کنید:
- // cache: 'no-store', // اگر دادهها مدام تغییر میکنند (مثل موجودی)
- next: { revalidate: 60 } // کش کردن دادهها برای 60 ثانیه (مناسب برای محصولات)
- });
+ console.log("📡 Fetching:", url);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
+ const res = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Accept": "application/json",
+ },
+ cache: "no-store",
+ });
- const data: ProductsResponse = await response.json();
- return data;
- } catch (error) {
- console.error("Error fetching products:", error);
- // مدیریت خطا (بسته به سیاست پروژه شما میتواند throw شود یا مقدار پیشفرض برگردد)
- throw new Error("مشکلی در دریافت اطلاعات محصولات به وجود آمد.");
+ if (!res.ok) {
+ const errorText = await res.text();
+ console.error("❌ API error:", errorText);
+ throw new Error("API Error " + res.status);
}
- },
- /**
- * دریافت محصول بر اساس Slug یا ID (برای استفاده در صفحات سینگل پروداکت در آینده)
- */
- // async getProductBySlug(slug: string) { ... }
-};
+ const json = await res.json();
+
+ return json.data;
+ } catch (err) {
+ console.error("🔥 getProductsByCategory Error:", err);
+ return { items: [], meta: { total: 0, page: 1, limit: 20 } };
+ }
+}
+
+
+export async function getProducts(page = 1, limit = 20) {
+ try {
+ const url = `${API_BASE_URL}/products?page=${page}&limit=${limit}`;
+
+ console.log("📡 Fetching products:", url);
+
+ const res = await fetch(url, {
+ method: "GET",
+ headers: {
+ Accept: "application/json",
+ },
+ cache: "no-store",
+ });
+
+ if (!res.ok) {
+ const errorText = await res.text();
+ console.error("❌ API Error:", errorText);
+ throw new Error(`API Error ${res.status}`);
+ }
+
+ const json = await res.json();
+
+ return json.data;
+ } catch (error) {
+ console.error("🔥 getProducts error:", error);
+
+ return {
+ items: [],
+ meta: {
+ total: 0,
+ page: 1,
+ limit: 20,
+ },
+ };
+ }
+}
+
+
+export async function getProductBySlug(slug: string) {
+ try {
+ const url = `${API_BASE_URL}/product/${slug}`;
+
+ const res = await fetch(url, { cache: "no-store" });
+
+ if (!res.ok) {
+ const errorText = await res.text();
+ console.error("❌ API Error:", errorText);
+ throw new Error(`API Error ${res.status}`);
+ }
+
+ const json = await res.json();
+ return json.data;
+
+ } catch (err) {
+ console.error("🔥 getProductBySlug error:", err);
+ return null;
+ }
+}
diff --git a/public/src/types/product.tsx b/public/src/types/product.tsx
index 52699fd..ccf0562 100644
--- a/public/src/types/product.tsx
+++ b/public/src/types/product.tsx
@@ -1,32 +1,32 @@
-// ساختار هر محصول (Product) بر اساس بادی ریسپانس
-export interface Product {
- id: string;
- sku: string;
- title: string;
- slug: string;
- technicalCode: string;
- brand: string;
- brandEntity: any | null; // اگر ساختار مشخصی دارد، بجای any از تایپ مناسب استفاده کنید
- basePriceUSD: number;
- salePriceUSD: number;
- stock: number;
- featured: boolean;
- type: string;
- status: string;
- mainImageUrl: string | null;
- threeDModelUrl: string | null;
- imageGalleryUrls: string[];
- tags: string[];
- averageRating: number;
- reviewsCount: number;
- primaryCategory: any | null;
- categories: any[];
- meta: any | null;
- createdAt: string;
- updatedAt: string;
- brandInfo: any | null;
- attributes: any[];
- }
+// // ساختار هر محصول (Product) بر اساس بادی ریسپانس
+// export interface Product {
+// id: string;
+// sku: string;
+// title: string;
+// slug: string;
+// technicalCode: string;
+// brand: string;
+// brandEntity: any | null; // اگر ساختار مشخصی دارد، بجای any از تایپ مناسب استفاده کنید
+// basePriceUSD: number;
+// salePriceUSD: number;
+// stock: number;
+// featured: boolean;
+// type: string;
+// status: string;
+// mainImageUrl: string | null;
+// threeDModelUrl: string | null;
+// imageGalleryUrls: string[];
+// tags: string[];
+// averageRating: number;
+// reviewsCount: number;
+// primaryCategory: any | null;
+// categories: any[];
+// meta: any | null;
+// createdAt: string;
+// updatedAt: string;
+// brandInfo: any | null;
+// attributes: any[];
+// }
// ساختار متادیتا (Paginagtion)
export interface PaginationMeta {
@@ -35,15 +35,32 @@ export interface Product {
limit: number;
}
- // ساختار نهایی ریسپانس (Response Body)
- export interface ProductsResponse {
- success: boolean;
- statusCode: number;
- path: string;
- timestamp: string;
- data: {
- items: Product[];
- meta: PaginationMeta;
- };
- }
+
+
+export interface ProductAttribute {
+ id: string;
+ name: string;
+ slug: string;
+ valueText: string | null;
+}
+
+export interface Product {
+ id: string;
+ title: string;
+ brand: string;
+ price?: number;
+ stock: number;
+ image: string;
+ slug: string;
+ attributes?: ProductAttribute[];
+}
+
+export interface ProductsResponse {
+ items: Product[];
+ meta: {
+ total: number;
+ page: number;
+ limit: number;
+ };
+}
\ No newline at end of file