added filter to category page
This commit is contained in:
59
package-lock.json
generated
59
package-lock.json
generated
@@ -173,6 +173,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.0",
|
||||
@@ -1084,7 +1085,6 @@
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
@@ -1574,17 +1574,6 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
||||
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
@@ -1596,6 +1585,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -1639,6 +1629,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -1935,6 +1926,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001688",
|
||||
"electron-to-chromium": "^1.5.73",
|
||||
@@ -1959,8 +1951,7 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
@@ -2088,8 +2079,7 @@
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "3.1.0",
|
||||
@@ -2207,7 +2197,8 @@
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
@@ -2527,6 +2518,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
|
||||
"integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3101,6 +3093,7 @@
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
},
|
||||
@@ -7320,6 +7313,7 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -7331,6 +7325,7 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -7399,6 +7394,7 @@
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -7467,7 +7463,8 @@
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -8213,7 +8210,6 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -8233,7 +8229,6 @@
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
@@ -8417,26 +8412,6 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/throttle-debounce": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
||||
@@ -8569,8 +8544,7 @@
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
@@ -8630,6 +8604,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
||||
"integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.24.2",
|
||||
"postcss": "^8.4.49",
|
||||
|
||||
@@ -11,7 +11,6 @@ export const brandsApi = baseApi.injectEndpoints({
|
||||
if (params.page) {
|
||||
queryParams.append("page", params.page);
|
||||
}
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append("limit", params.limit);
|
||||
}
|
||||
@@ -29,12 +28,10 @@ export const brandsApi = baseApi.injectEndpoints({
|
||||
|
||||
getBrandProducts: builder.query({
|
||||
query: (params) => {
|
||||
// Handle both string ID and object with pagination params
|
||||
if (typeof params === 'string' || typeof params === 'number') {
|
||||
return `/brands/${params}/products`;
|
||||
}
|
||||
|
||||
// Handle object with pagination
|
||||
const { id, page = 1, limit } = params;
|
||||
let url = `/brands/${id}/products?page=${page}`;
|
||||
|
||||
@@ -44,9 +41,10 @@ export const brandsApi = baseApi.injectEndpoints({
|
||||
|
||||
return url;
|
||||
},
|
||||
transformResponse: (response) => {
|
||||
return response.data || response;
|
||||
},
|
||||
transformResponse: (response) => ({
|
||||
data: response.data || response,
|
||||
pagination: response.pagination || {},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -5,17 +5,24 @@ export const categoriesApi = baseApi.injectEndpoints({
|
||||
getCategories: builder.query({
|
||||
query: (type = "tree") => `/categories?type=${type}`,
|
||||
}),
|
||||
|
||||
getCategoryProducts: builder.query({
|
||||
query: ({ categoryId, page = 1, limit }) => {
|
||||
let url = `categories/${categoryId}/products?page=${page}`;
|
||||
if (limit) url += `&limit=${limit}`;
|
||||
return url;
|
||||
query: ({ categoryId, page = 1, limit, brands, min_price, max_price }) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', page);
|
||||
if (limit) params.append('limit', limit);
|
||||
if (brands) params.append('brands', brands);
|
||||
if (min_price) params.append('min_price', min_price);
|
||||
if (max_price) params.append('max_price', max_price);
|
||||
|
||||
return `categories/${categoryId}/products?${params.toString()}`;
|
||||
},
|
||||
transformResponse: (response) => ({
|
||||
data: response.data || [],
|
||||
pagination: response.pagination || {},
|
||||
}),
|
||||
}),
|
||||
|
||||
getAllCategoryProducts: builder.query({
|
||||
async queryFn(category, queryApi, extraOptions, baseQuery) {
|
||||
const fetchProducts = async (categoryId) => {
|
||||
@@ -36,7 +43,7 @@ export const categoriesApi = baseApi.injectEndpoints({
|
||||
|
||||
getAllCategoryProductsPaginated: builder.query({
|
||||
async queryFn(
|
||||
{ category, page = 1, limit = 6 },
|
||||
{ category, page = 1, limit = 6, brands, min_price, max_price },
|
||||
queryApi,
|
||||
extraOptions,
|
||||
baseQuery
|
||||
@@ -51,14 +58,20 @@ export const categoriesApi = baseApi.injectEndpoints({
|
||||
const perCategoryLimit = Math.ceil(limit / categoryIds.length);
|
||||
|
||||
for (const categoryId of categoryIds) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', currentPage);
|
||||
params.append('limit', perCategoryLimit);
|
||||
if (brands) params.append('brands', brands);
|
||||
if (min_price) params.append('min_price', min_price);
|
||||
if (max_price) params.append('max_price', max_price);
|
||||
|
||||
const result = await baseQuery(
|
||||
`categories/${categoryId}/products?page=${currentPage}&limit=${perCategoryLimit}`
|
||||
`categories/${categoryId}/products?${params.toString()}`
|
||||
);
|
||||
|
||||
if (result.data && result.data.data) {
|
||||
allPageProducts = [...allPageProducts, ...result.data.data];
|
||||
|
||||
hasMoreByCategory[categoryId] =
|
||||
!!result.data.pagination.next_page_url;
|
||||
hasMoreByCategory[categoryId] = !!result.data.pagination.next_page_url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +103,11 @@ export const categoriesApi = baseApi.injectEndpoints({
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
getProductById: builder.query({
|
||||
query: (productId) => `/products/${productId}`,
|
||||
}),
|
||||
|
||||
getRelatedProducts: builder.query({
|
||||
query: (productId) => `/products/${productId}/related`,
|
||||
}),
|
||||
@@ -107,4 +122,4 @@ export const {
|
||||
useLazyGetAllCategoryProductsPaginatedQuery,
|
||||
useGetProductByIdQuery,
|
||||
useGetRelatedProductsQuery,
|
||||
} = categoriesApi;
|
||||
} = categoriesApi;
|
||||
@@ -5,12 +5,13 @@ export const collectionsApi = baseApi.injectEndpoints({
|
||||
getCollections: builder.query({
|
||||
query: () => `/collections`,
|
||||
}),
|
||||
|
||||
getCollectionById: builder.query({
|
||||
query: (collectionId) => `/collections/${collectionId}`,
|
||||
}),
|
||||
|
||||
getCollectionProducts: builder.query({
|
||||
query: (collectionId) => `/collections/${collectionId}/products`,
|
||||
// Ürünleri dönüştürerek boş kontrol edilebilir
|
||||
transformResponse: (response) => {
|
||||
return {
|
||||
data: response.data || [],
|
||||
@@ -18,7 +19,7 @@ export const collectionsApi = baseApi.injectEndpoints({
|
||||
};
|
||||
},
|
||||
}),
|
||||
// Yeni endpoint: Koleksiyonun ürün içerip içermediğini kontrol eder
|
||||
|
||||
checkCollectionHasProducts: builder.query({
|
||||
query: (collectionId) => `/collections/${collectionId}/products?limit=1`,
|
||||
transformResponse: (response) => {
|
||||
@@ -27,10 +28,18 @@ export const collectionsApi = baseApi.injectEndpoints({
|
||||
};
|
||||
},
|
||||
}),
|
||||
// Sayfalı koleksiyon ürünleri için endpoint
|
||||
|
||||
getCollectionProductsPaginated: builder.query({
|
||||
query: ({ collectionId, page = 1, limit = 6 }) =>
|
||||
`/collections/${collectionId}/products?page=${page}${limit ? `&limit=${limit}` : ''}`,
|
||||
query: ({ collectionId, page = 1, limit = 6, brands, min_price, max_price }) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', page);
|
||||
if (limit) params.append('limit', limit);
|
||||
if (brands) params.append('brands', brands);
|
||||
if (min_price) params.append('min_price', min_price);
|
||||
if (max_price) params.append('max_price', max_price);
|
||||
|
||||
return `/collections/${collectionId}/products?${params.toString()}`;
|
||||
},
|
||||
transformResponse: (response) => ({
|
||||
data: response.data || [],
|
||||
pagination: response.pagination || {},
|
||||
|
||||
77
src/app/api/filtersApi.js
Normal file
77
src/app/api/filtersApi.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import { baseApi } from "./baseApi"
|
||||
|
||||
export const filtersApi = baseApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getFilters: builder.query({
|
||||
query: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params?.category_id) {
|
||||
queryParams.append("category_id", String(params.category_id))
|
||||
}
|
||||
if (params?.collection_id) {
|
||||
queryParams.append("collection_id", String(params.collection_id))
|
||||
}
|
||||
if (params?.brand_id) {
|
||||
queryParams.append("brand_id", String(params.brand_id))
|
||||
}
|
||||
|
||||
return `/filters?${queryParams.toString()}`
|
||||
},
|
||||
transformResponse: (response) => {
|
||||
return {
|
||||
categories: response.data?.categories || [],
|
||||
brands: response.data?.brands || [],
|
||||
}
|
||||
},
|
||||
keepUnusedDataFor: 300,
|
||||
|
||||
serializeQueryArgs: ({ queryArgs }) => {
|
||||
if (!queryArgs) return 'no-params';
|
||||
|
||||
const parts = [];
|
||||
|
||||
if (queryArgs.category_id) {
|
||||
parts.push(`cat:${queryArgs.category_id}`);
|
||||
}
|
||||
if (queryArgs.collection_id) {
|
||||
parts.push(`col:${queryArgs.collection_id}`);
|
||||
}
|
||||
if (queryArgs.brand_id) {
|
||||
parts.push(`brd:${queryArgs.brand_id}`);
|
||||
}
|
||||
|
||||
return parts.length > 0 ? parts.join('|') : 'no-params';
|
||||
},
|
||||
|
||||
merge: (currentCache, newItems) => {
|
||||
return newItems;
|
||||
},
|
||||
|
||||
refetchOnMountOrArgChange: 30, // Refetch if older than 30 seconds
|
||||
refetchOnFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
||||
providesTags: (result, error, arg) => {
|
||||
if (!arg) return [{ type: "Filters", id: "no-params" }];
|
||||
|
||||
const tags = [{ type: "Filters", id: "LIST" }];
|
||||
|
||||
if (arg.category_id) {
|
||||
tags.push({ type: "Filters", id: `cat-${arg.category_id}` });
|
||||
}
|
||||
if (arg.collection_id) {
|
||||
tags.push({ type: "Filters", id: `col-${arg.collection_id}` });
|
||||
}
|
||||
if (arg.brand_id) {
|
||||
tags.push({ type: "Filters", id: `brd-${arg.brand_id}` });
|
||||
}
|
||||
|
||||
return tags;
|
||||
},
|
||||
}),
|
||||
}),
|
||||
overrideExisting: true,
|
||||
})
|
||||
|
||||
export const { useGetFiltersQuery, useLazyGetFiltersQuery } = filtersApi
|
||||
@@ -13,6 +13,7 @@ import { mediaApi } from "./api/bannersApi";
|
||||
import { reviewsApi } from "./api/reviewApi";
|
||||
import { profileApi } from "./api/myProfileApi";
|
||||
import { contactApi } from "./api/contactUs";
|
||||
import { filtersApi } from "./api/filtersApi";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
@@ -30,6 +31,7 @@ const store = configureStore({
|
||||
[reviewsApi.reducerPath]: reviewsApi.reducer,
|
||||
[profileApi.reducerPath]: profileApi.reducer,
|
||||
[contactApi.reducerPath]: contactApi.reducer,
|
||||
[filtersApi.reducerPath]: filtersApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(
|
||||
@@ -46,6 +48,7 @@ const store = configureStore({
|
||||
mediaApi.middleware,
|
||||
profileApi.middleware,
|
||||
contactApi.middleware,
|
||||
filtersApi.middleware
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,38 +1,18 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { Drawer, Input, Checkbox } from "antd";
|
||||
import styles from "./BrandsSidebar.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import brand from "../../assets/icons/brand.svg";
|
||||
|
||||
const Sidebar = () => {
|
||||
const BrandSidebar = ({
|
||||
brands = [],
|
||||
selectedBrand = null,
|
||||
onBrandSelect,
|
||||
onBrandDeselect
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const { t, i18n } = useTranslation();
|
||||
const brands = [
|
||||
"Abat",
|
||||
"Altın",
|
||||
"Arçalyk",
|
||||
"Aýaz baba",
|
||||
"Balşeker",
|
||||
"Bars",
|
||||
"Belet Film",
|
||||
"Beýlekiler / Другие",
|
||||
"Bingo",
|
||||
"Bold",
|
||||
"Carte Noire",
|
||||
"Çaykur",
|
||||
"Dabara",
|
||||
"Datmeni",
|
||||
"Elin",
|
||||
"Emin Et",
|
||||
"Enemeli",
|
||||
"Ermak",
|
||||
"Eyfel",
|
||||
"Familia",
|
||||
"Farmasi",
|
||||
"Ferrero Rocher",
|
||||
"Granum",
|
||||
];
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsOpen(!isOpen);
|
||||
@@ -42,15 +22,28 @@ const Sidebar = () => {
|
||||
setSearchTerm(e.target.value.toLowerCase());
|
||||
};
|
||||
|
||||
const filteredBrands = brands.filter((brand) =>
|
||||
brand.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
const filteredBrands = useMemo(() => {
|
||||
if (!brands || brands.length === 0) return [];
|
||||
|
||||
return brands.filter((brand) =>
|
||||
brand.name.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}, [brands, searchTerm]);
|
||||
|
||||
const handleBrandChange = (brandId, checked) => {
|
||||
if (checked) {
|
||||
onBrandSelect?.(brandId);
|
||||
} else {
|
||||
onBrandDeselect?.();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.sidebarContainer}>
|
||||
<button onClick={handleToggle} className={styles.mobileNavButton}>
|
||||
<img src={brand} alt="" />
|
||||
{t("navbar.brands")}
|
||||
{selectedBrand && <span className={styles.badge}>1</span>}
|
||||
</button>
|
||||
|
||||
<Drawer
|
||||
@@ -67,16 +60,30 @@ const Sidebar = () => {
|
||||
onChange={handleSearch}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
<div className={styles.brandsList}>
|
||||
{filteredBrands.map((brand, index) => (
|
||||
<div key={index} className={styles.brandItem}>
|
||||
<Checkbox>{brand}</Checkbox>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredBrands.length > 0 ? (
|
||||
<div className={styles.brandsList}>
|
||||
{filteredBrands.map((brand) => (
|
||||
<div key={brand.id} className={styles.brandItem}>
|
||||
<Checkbox
|
||||
checked={selectedBrand === brand.id}
|
||||
onChange={(e) => handleBrandChange(brand.id, e.target.checked)}
|
||||
>
|
||||
{brand.name}
|
||||
</Checkbox>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.emptyState}>
|
||||
{brands.length === 0
|
||||
? t("common.noData")
|
||||
: t("common.noResults")}
|
||||
</div>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
export default BrandSidebar;
|
||||
@@ -5,6 +5,7 @@
|
||||
margin: auto;
|
||||
padding: 20px 1.375rem;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
|
||||
59
src/pages/Category/components/CategoryBreadcrumbs.jsx
Normal file
59
src/pages/Category/components/CategoryBreadcrumbs.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Breadcrumb } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "../CategoryPage.module.scss";
|
||||
|
||||
const CategoryBreadcrumbs = ({ categoriesData, categoryId, onCategoryClick }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const breadcrumbs = useMemo(() => {
|
||||
if (!categoriesData?.data || !categoryId) return [];
|
||||
|
||||
const buildPath = (categories, targetId, path = []) => {
|
||||
for (const category of categories) {
|
||||
if (category.id === parseInt(targetId)) {
|
||||
return [...path, category];
|
||||
}
|
||||
if (category.children) {
|
||||
const found = buildPath(category.children, targetId, [...path, category]);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return buildPath(categoriesData.data, categoryId) || [];
|
||||
}, [categoriesData, categoryId]);
|
||||
|
||||
if (breadcrumbs.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Breadcrumb className={styles.breadcrumb}>
|
||||
|
||||
|
||||
{breadcrumbs.map((crumb, index) => {
|
||||
const isLast = index === breadcrumbs.length - 1;
|
||||
|
||||
return (
|
||||
<Breadcrumb.Item key={crumb.id}>
|
||||
{isLast ? (
|
||||
<span>{crumb.name}</span>
|
||||
) : (
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onCategoryClick?.(crumb.id);
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{crumb.name}
|
||||
</a>
|
||||
)}
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
})}
|
||||
</Breadcrumb>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryBreadcrumbs;
|
||||
98
src/pages/Category/components/CategoryFilters.jsx
Normal file
98
src/pages/Category/components/CategoryFilters.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TiTick } from "react-icons/ti";
|
||||
import styles from "../CategoryPage.module.scss";
|
||||
|
||||
const CategoryFilters = ({
|
||||
filtersData,
|
||||
selectedFilterCategory,
|
||||
selectedFilterBrand,
|
||||
brandSearchQuery,
|
||||
searchQuery,
|
||||
onCategorySelect,
|
||||
onCategoryDeselect,
|
||||
onBrandSelect,
|
||||
onBrandDeselect,
|
||||
onBrandSearchChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (searchQuery) return null;
|
||||
|
||||
return (
|
||||
<aside className={styles.sidebar}>
|
||||
{filtersData?.categories?.length > 0 && (
|
||||
<div className={styles.filterSection}>
|
||||
<h3>{t("category.subCategories")}</h3>
|
||||
<ul>
|
||||
{filtersData.categories.map((category) => (
|
||||
<li
|
||||
key={category.id}
|
||||
onClick={() => {
|
||||
if (selectedFilterCategory === category.id) {
|
||||
onCategoryDeselect();
|
||||
} else {
|
||||
onCategorySelect(category.id);
|
||||
}
|
||||
}}
|
||||
className={
|
||||
selectedFilterCategory === category.id
|
||||
? styles.activeSubcategory
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{category.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filtersData?.brands?.length > 0 && (
|
||||
<div className={styles.filterSection}>
|
||||
<h3>{t("navbar.brands")}</h3>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Gözleg"
|
||||
value={brandSearchQuery}
|
||||
onChange={(e) => onBrandSearchChange(e.target.value)}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
|
||||
<ul>
|
||||
{filtersData.brands
|
||||
.filter((brand) =>
|
||||
brand.name
|
||||
.toLowerCase()
|
||||
.includes(brandSearchQuery.toLowerCase())
|
||||
)
|
||||
.map((brand) => (
|
||||
<li key={brand.id}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedFilterBrand === brand.id}
|
||||
onChange={() => {
|
||||
if (selectedFilterBrand === brand.id) {
|
||||
onBrandDeselect();
|
||||
} else {
|
||||
onBrandSelect(brand.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className={styles.customCheckbox}>
|
||||
<TiTick className={styles.checkIcon} />
|
||||
</span>
|
||||
{brand.name}
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryFilters;
|
||||
110
src/pages/Category/hooks/useCategoryData.js
Normal file
110
src/pages/Category/hooks/useCategoryData.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useGetCategoriesQuery } from "../../../app/api/categories";
|
||||
import { useGetCollectionByIdQuery } from "../../../app/api/collectionsApi";
|
||||
import {
|
||||
useGetFiltersQuery,
|
||||
useLazyGetFiltersQuery,
|
||||
} from "../../../app/api/filtersApi";
|
||||
|
||||
const useCategoryData = ({
|
||||
categoryId,
|
||||
collectionId,
|
||||
brandId,
|
||||
selectedFilterCategory,
|
||||
searchQuery,
|
||||
}) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
|
||||
const { data: categoriesData } = useGetCategoriesQuery("tree");
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
if (searchQuery) return null;
|
||||
if (selectedFilterCategory) return { category_id: selectedFilterCategory };
|
||||
if (categoryId) return { category_id: categoryId };
|
||||
if (collectionId) return { collection_id: collectionId };
|
||||
if (brandId) return { brand_id: brandId };
|
||||
return null;
|
||||
}, [categoryId, collectionId, brandId, selectedFilterCategory, searchQuery]);
|
||||
|
||||
const {
|
||||
data: filtersData,
|
||||
isLoading: filtersLoading,
|
||||
error: filtersError,
|
||||
} = useGetFiltersQuery(filterParams, {
|
||||
skip: !filterParams,
|
||||
});
|
||||
|
||||
const [fetchFilters, { data: lazyFiltersData }] = useLazyGetFiltersQuery();
|
||||
|
||||
const {
|
||||
data: collectionData,
|
||||
isLoading: collectionLoading,
|
||||
error: collectionError,
|
||||
} = useGetCollectionByIdQuery(collectionId, {
|
||||
skip: !collectionId,
|
||||
});
|
||||
|
||||
const isSubCategory = useMemo(() => {
|
||||
if (!categoriesData?.data || !categoryId) return false;
|
||||
|
||||
const checkIsSubCategory = (categories, targetId) => {
|
||||
for (const category of categories) {
|
||||
if (category.children) {
|
||||
for (const subCategory of category.children) {
|
||||
if (subCategory.id === parseInt(targetId)) return true;
|
||||
if (subCategory.children) {
|
||||
const found = checkIsSubCategory([subCategory], targetId);
|
||||
if (found) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return checkIsSubCategory(categoriesData.data, parseInt(categoryId));
|
||||
}, [categoriesData, categoryId]);
|
||||
|
||||
const activeFilters = useMemo(() => {
|
||||
return selectedFilterCategory && lazyFiltersData
|
||||
? lazyFiltersData
|
||||
: filtersData;
|
||||
}, [selectedFilterCategory, lazyFiltersData, filtersData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!categoryId || !categoriesData?.data) {
|
||||
setSelectedCategory(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const findCategory = (categories, targetId) => {
|
||||
for (const cat of categories) {
|
||||
if (cat.id === parseInt(targetId)) return cat;
|
||||
if (cat.children) {
|
||||
const found = findCategory(cat.children, targetId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const category = findCategory(categoriesData.data, parseInt(categoryId));
|
||||
setSelectedCategory(category);
|
||||
}, [categoryId, categoriesData]);
|
||||
|
||||
const isLoading = filtersLoading || collectionLoading;
|
||||
const hasError = filtersError || collectionError;
|
||||
|
||||
return {
|
||||
categoriesData,
|
||||
selectedCategory,
|
||||
isSubCategory,
|
||||
filtersData: activeFilters,
|
||||
collectionData,
|
||||
isLoading,
|
||||
hasError,
|
||||
fetchFilters,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCategoryData;
|
||||
316
src/pages/Category/hooks/useCategoryProducts.js
Normal file
316
src/pages/Category/hooks/useCategoryProducts.js
Normal file
@@ -0,0 +1,316 @@
|
||||
import { useState, useEffect, useMemo, useRef } from "react";
|
||||
import {
|
||||
useGetCategoryProductsQuery,
|
||||
useLazyGetAllCategoryProductsPaginatedQuery,
|
||||
} from "../../../app/api/categories";
|
||||
import { useLazyGetBrandProductsQuery } from "../../../app/api/brandsApi";
|
||||
import { useLazyGetCollectionProductsPaginatedQuery } from "../../../app/api/collectionsApi";
|
||||
|
||||
const useCategoryProducts = ({
|
||||
categoryId,
|
||||
collectionId,
|
||||
brandId,
|
||||
selectedCategory,
|
||||
isSubCategory,
|
||||
currentPage,
|
||||
selectedFilterCategory,
|
||||
selectedFilterBrand,
|
||||
minPrice,
|
||||
maxPrice,
|
||||
searchQuery,
|
||||
}) => {
|
||||
const [products, setProducts] = useState([]);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
|
||||
const isFetchingRef = useRef(false);
|
||||
const lastFetchKeyRef = useRef(null);
|
||||
const abortControllerRef = useRef(null);
|
||||
|
||||
const contextId = useMemo(() => {
|
||||
const parts = [
|
||||
selectedFilterCategory && `fcat-${selectedFilterCategory}`,
|
||||
categoryId && `cat-${categoryId}`,
|
||||
brandId && `brand-${brandId}`,
|
||||
collectionId && `col-${collectionId}`,
|
||||
selectedFilterBrand && `fbrand-${selectedFilterBrand}`,
|
||||
].filter(Boolean);
|
||||
return parts.join("|") || "none";
|
||||
}, [
|
||||
selectedFilterCategory,
|
||||
categoryId,
|
||||
brandId,
|
||||
collectionId,
|
||||
selectedFilterBrand,
|
||||
]);
|
||||
|
||||
const fetchParams = useMemo(
|
||||
() => ({
|
||||
page: currentPage,
|
||||
limit: 6,
|
||||
brands: selectedFilterBrand || undefined,
|
||||
min_price: minPrice || undefined,
|
||||
max_price: maxPrice || undefined,
|
||||
}),
|
||||
[currentPage, selectedFilterBrand, minPrice, maxPrice]
|
||||
);
|
||||
|
||||
const fetchKey = `${contextId}-p${currentPage}`;
|
||||
|
||||
const shouldUseBaseQuery =
|
||||
categoryId &&
|
||||
!isSubCategory &&
|
||||
!searchQuery &&
|
||||
!selectedFilterCategory &&
|
||||
!selectedFilterBrand &&
|
||||
!brandId &&
|
||||
!collectionId;
|
||||
|
||||
const {
|
||||
data: paginatedCategoryProducts,
|
||||
isLoading: categoryLoading,
|
||||
isFetching: categoryFetching,
|
||||
} = useGetCategoryProductsQuery(
|
||||
{
|
||||
categoryId: categoryId,
|
||||
page: currentPage,
|
||||
min_price: minPrice || undefined,
|
||||
max_price: maxPrice || undefined,
|
||||
},
|
||||
{
|
||||
skip: !shouldUseBaseQuery,
|
||||
}
|
||||
);
|
||||
|
||||
const [
|
||||
fetchCategoryPaginated,
|
||||
{
|
||||
data: lazyCategoryProducts,
|
||||
isLoading: lazyCategoryLoading,
|
||||
isFetching: lazyCategoryFetching,
|
||||
reset: resetCategoryPaginated,
|
||||
},
|
||||
] = useLazyGetAllCategoryProductsPaginatedQuery();
|
||||
|
||||
const [
|
||||
fetchBrandPaginated,
|
||||
{
|
||||
data: paginatedBrandProducts,
|
||||
isLoading: brandPaginatedLoading,
|
||||
isFetching: brandFetching,
|
||||
reset: resetBrandPaginated,
|
||||
},
|
||||
] = useLazyGetBrandProductsQuery();
|
||||
|
||||
const [
|
||||
fetchCollectionPaginated,
|
||||
{
|
||||
data: paginatedCollectionProducts,
|
||||
isLoading: collectionPaginatedLoading,
|
||||
isFetching: collectionFetching,
|
||||
reset: resetCollectionPaginated,
|
||||
},
|
||||
] = useLazyGetCollectionProductsPaginatedQuery();
|
||||
|
||||
useEffect(() => {
|
||||
setProducts([]);
|
||||
setHasMore(true);
|
||||
|
||||
resetCategoryPaginated?.();
|
||||
resetBrandPaginated?.();
|
||||
resetCollectionPaginated?.();
|
||||
|
||||
lastFetchKeyRef.current = null;
|
||||
isFetchingRef.current = false;
|
||||
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
}, [
|
||||
contextId,
|
||||
resetCategoryPaginated,
|
||||
resetBrandPaginated,
|
||||
resetCollectionPaginated,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery) return;
|
||||
|
||||
if (lastFetchKeyRef.current === fetchKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFetchingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
|
||||
abortControllerRef.current = new AbortController();
|
||||
isFetchingRef.current = true;
|
||||
lastFetchKeyRef.current = fetchKey;
|
||||
|
||||
const executeFetch = async () => {
|
||||
try {
|
||||
if (selectedFilterBrand) {
|
||||
await fetchBrandPaginated({
|
||||
id: selectedFilterBrand,
|
||||
...fetchParams,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFilterCategory) {
|
||||
await fetchCategoryPaginated({
|
||||
category: {
|
||||
id: selectedFilterCategory,
|
||||
children: [],
|
||||
},
|
||||
...fetchParams,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (categoryId && isSubCategory) {
|
||||
await fetchCategoryPaginated({
|
||||
category: {
|
||||
id: parseInt(categoryId),
|
||||
children: [],
|
||||
},
|
||||
...fetchParams,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (brandId) {
|
||||
await fetchBrandPaginated({
|
||||
id: brandId,
|
||||
...fetchParams,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectionId) {
|
||||
await fetchCollectionPaginated({
|
||||
collectionId,
|
||||
...fetchParams,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name !== "AbortError") {
|
||||
console.error("Fetch error:", error);
|
||||
}
|
||||
} finally {
|
||||
isFetchingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
executeFetch();
|
||||
|
||||
return () => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
};
|
||||
}, [
|
||||
fetchKey,
|
||||
searchQuery,
|
||||
selectedFilterBrand,
|
||||
selectedFilterCategory,
|
||||
categoryId,
|
||||
isSubCategory,
|
||||
brandId,
|
||||
collectionId,
|
||||
fetchParams,
|
||||
fetchCategoryPaginated,
|
||||
fetchBrandPaginated,
|
||||
fetchCollectionPaginated,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateProducts = (newData, hasNextPage) => {
|
||||
if (!newData || newData.length === 0) {
|
||||
if (currentPage === 1) {
|
||||
setProducts([]);
|
||||
setHasMore(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setProducts((prev) => {
|
||||
if (currentPage === 1) {
|
||||
return newData;
|
||||
}
|
||||
|
||||
const existingIds = new Set(prev.map((p) => p.id));
|
||||
const newProducts = newData.filter((p) => !existingIds.has(p.id));
|
||||
|
||||
return newProducts.length > 0 ? [...prev, ...newProducts] : prev;
|
||||
});
|
||||
|
||||
setHasMore(hasNextPage);
|
||||
};
|
||||
|
||||
if (paginatedCategoryProducts && shouldUseBaseQuery) {
|
||||
updateProducts(
|
||||
paginatedCategoryProducts.data || [],
|
||||
!!paginatedCategoryProducts.pagination?.next_page_url
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lazyCategoryProducts) {
|
||||
updateProducts(
|
||||
lazyCategoryProducts.data || [],
|
||||
lazyCategoryProducts.pagination?.hasMorePages || false
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Brand products
|
||||
if (paginatedBrandProducts) {
|
||||
updateProducts(
|
||||
paginatedBrandProducts.data || [],
|
||||
!!paginatedBrandProducts.pagination?.next_page_url
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (paginatedCollectionProducts) {
|
||||
updateProducts(
|
||||
paginatedCollectionProducts.data || [],
|
||||
!!paginatedCollectionProducts.pagination?.next_page_url
|
||||
);
|
||||
}
|
||||
}, [
|
||||
paginatedCategoryProducts,
|
||||
lazyCategoryProducts,
|
||||
paginatedBrandProducts,
|
||||
paginatedCollectionProducts,
|
||||
currentPage,
|
||||
shouldUseBaseQuery,
|
||||
]);
|
||||
|
||||
const isLoading =
|
||||
categoryLoading ||
|
||||
lazyCategoryLoading ||
|
||||
brandPaginatedLoading ||
|
||||
collectionPaginatedLoading ||
|
||||
categoryFetching ||
|
||||
lazyCategoryFetching ||
|
||||
brandFetching ||
|
||||
collectionFetching;
|
||||
|
||||
return {
|
||||
products,
|
||||
hasMore,
|
||||
isLoading,
|
||||
setProducts,
|
||||
setHasMore,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCategoryProducts;
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user