infiniti scroll product list
This commit is contained in:
Generated
+22
@@ -26,6 +26,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^11.2.10",
|
||||
@@ -5809,6 +5810,18 @@
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-infinite-scroll-component": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
|
||||
"integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"throttle-debounce": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -6509,6 +6522,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/throttle-debounce": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
|
||||
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-case": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^11.2.10",
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
} from '@/components/ui/breadcrumb'
|
||||
import Filters from '../components/Filters'
|
||||
import Sorts from '../components/Sorts'
|
||||
import Pagination from '../components/Pagination'
|
||||
import GridWrapper from '@/components/GridWrapper'
|
||||
import ProductCard from '@/components/ProductCard'
|
||||
import { Setting5 } from 'iconsax-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useCategoryProductsData } from '../hooks/useProductsData'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import { useInfiniteCategoryProductsData } from '../hooks/useInfiniteProductsData'
|
||||
import { IProduct } from '@/types/landing.types'
|
||||
import { Product } from '@/types/products.types'
|
||||
import { useSearchParams, useParams } from 'next/navigation'
|
||||
@@ -55,8 +55,20 @@ const Products: NextPage = () => {
|
||||
setFilterParams(params)
|
||||
}, [searchParams])
|
||||
|
||||
// دریافت دادههای محصولات از API بر اساس categoryUrl
|
||||
const { data, isLoading, error } = useCategoryProductsData(categoryUrl, filterParams)
|
||||
// دریافت دادههای محصولات از API بر اساس categoryUrl با infinite scroll
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage
|
||||
} = useInfiniteCategoryProductsData(categoryUrl, filterParams)
|
||||
|
||||
// جمعآوری تمام محصولات از صفحات مختلف
|
||||
const allProducts = useMemo(() => {
|
||||
if (!data?.pages) return [];
|
||||
return data.pages.flatMap(page => page.results.products);
|
||||
}, [data?.pages]);
|
||||
|
||||
// تبدیل Product به IProduct
|
||||
const convertToIProduct = (product: Product): IProduct => {
|
||||
@@ -91,6 +103,7 @@ const Products: NextPage = () => {
|
||||
status: defaultVariant?.market_status || 'active',
|
||||
imagesUrl: product.imagesUrl,
|
||||
url: product.url,
|
||||
isWishlist: false,
|
||||
default_variant: {
|
||||
_id: defaultVariant?._id || product._id.toString(),
|
||||
market_status: defaultVariant?.market_status || 'active',
|
||||
@@ -117,11 +130,11 @@ const Products: NextPage = () => {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/">فروشگاه آناهیتا</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
{data?.results.breadcrumb?.map((item, index) => (
|
||||
{data?.pages?.[0]?.results.breadcrumb?.map((item, index) => (
|
||||
<div key={item._id} className="flex items-center">
|
||||
<BreadcrumbSeparator className="text-muted-foreground select-none">/</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
{index === data.results.breadcrumb.length - 1 ? (
|
||||
{index === (data?.pages?.[0]?.results.breadcrumb?.length || 0) - 1 ? (
|
||||
<BreadcrumbPage className="text-foreground">
|
||||
{item.title_fa}
|
||||
</BreadcrumbPage>
|
||||
@@ -160,7 +173,7 @@ const Products: NextPage = () => {
|
||||
/>
|
||||
|
||||
<div className='flex-1'>
|
||||
<Sorts data={data} />
|
||||
<Sorts data={data?.pages?.[0]} />
|
||||
|
||||
<div className='mt-6 sm:mt-8 md:mt-10'>
|
||||
{isLoading ? (
|
||||
@@ -171,15 +184,31 @@ const Products: NextPage = () => {
|
||||
<div className="text-center py-8">
|
||||
<div className="text-red-500">خطا در بارگذاری محصولات</div>
|
||||
</div>
|
||||
) : data?.results.products ? (
|
||||
<GridWrapper desktop={4} mobile={2} gapMobile={3} gapDesktop={6}>
|
||||
{data.results.products.map((product) => (
|
||||
<ProductCard
|
||||
key={product._id}
|
||||
item={convertToIProduct(product)}
|
||||
/>
|
||||
))}
|
||||
</GridWrapper>
|
||||
) : allProducts.length > 0 ? (
|
||||
<InfiniteScroll
|
||||
dataLength={allProducts.length}
|
||||
next={fetchNextPage}
|
||||
hasMore={!!hasNextPage}
|
||||
loader={
|
||||
<div className="text-center py-4">
|
||||
<div className="text-gray-500">در حال بارگذاری محصولات بیشتر...</div>
|
||||
</div>
|
||||
}
|
||||
endMessage={
|
||||
<div className="text-center py-4">
|
||||
{/* <div className="text-gray-500">تمام محصولات بارگذاری شد</div> */}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<GridWrapper desktop={4} mobile={2} gapMobile={3} gapDesktop={6}>
|
||||
{allProducts.map((product) => (
|
||||
<ProductCard
|
||||
key={product._id}
|
||||
item={convertToIProduct(product)}
|
||||
/>
|
||||
))}
|
||||
</GridWrapper>
|
||||
</InfiniteScroll>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-gray-500">محصولی یافت نشد</div>
|
||||
@@ -187,8 +216,6 @@ const Products: NextPage = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
<Pagination pager={data?.results?.pager} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/Service";
|
||||
import { ProductsResponse } from "@/types/products.types";
|
||||
|
||||
interface UseInfiniteProductsDataParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
category?: string;
|
||||
brand?: string;
|
||||
minPrice?: number;
|
||||
maxPrice?: number;
|
||||
attributes?: string[];
|
||||
sizes?: number[];
|
||||
meterages?: number[];
|
||||
sort?: string;
|
||||
wholeSale?: string;
|
||||
stock?: string;
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
export const useInfiniteProductsData = (
|
||||
params?: UseInfiniteProductsDataParams
|
||||
) => {
|
||||
return useInfiniteQuery<ProductsResponse>({
|
||||
queryKey: ["infinite-products", params],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
api.getProducts({ ...params, page: pageParam }),
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pager = lastPage.results.pager;
|
||||
return pager.nextPage ? pager.page + 1 : undefined;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
});
|
||||
};
|
||||
|
||||
export const useInfiniteCategoryProductsData = (
|
||||
categoryUrl: string,
|
||||
params?: UseInfiniteProductsDataParams
|
||||
) => {
|
||||
return useInfiniteQuery<ProductsResponse>({
|
||||
queryKey: ["infinite-category-products", categoryUrl, params],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
api.getCategoryProducts(categoryUrl, { ...params, page: pageParam }),
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pager = lastPage.results.pager;
|
||||
return pager.nextPage ? pager.page + 1 : undefined;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
});
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { Heart } from 'iconsax-react'
|
||||
import Image from 'next/image'
|
||||
import { FC, useState } from 'react'
|
||||
import { useAddWishlist, useRemoveWishlist } from '@/app/products/hooks/useProductsData'
|
||||
import { useSharedStore } from '@/share/store/sharedStore'
|
||||
|
||||
type Props = {
|
||||
item?: IProduct
|
||||
@@ -12,6 +13,7 @@ type Props = {
|
||||
|
||||
const ProductCard: FC<Props> = ({ item }) => {
|
||||
|
||||
const { isLogin } = useSharedStore()
|
||||
const [isWished, setIsWished] = useState(item?.isWishlist)
|
||||
|
||||
const { mutate: addWishlist } = useAddWishlist()
|
||||
@@ -19,6 +21,7 @@ const ProductCard: FC<Props> = ({ item }) => {
|
||||
|
||||
|
||||
const handleWish = () => {
|
||||
if (!isLogin) return
|
||||
if (!item?._id || !item?.variants[0]?._id) return
|
||||
|
||||
if (isWished) {
|
||||
|
||||
Reference in New Issue
Block a user