diff --git a/src/app/api/channelsApi.js b/src/app/api/channelsApi.js new file mode 100644 index 0000000..56bc8aa --- /dev/null +++ b/src/app/api/channelsApi.js @@ -0,0 +1,34 @@ +import { baseApi } from "./baseApi"; + +export const channelsApi = baseApi.injectEndpoints({ + endpoints: (builder) => ({ + getChannelProducts: builder.query({ + query: (params) => { + // params: { channelId, page, limit, min_price, max_price, sorting } + const { + channelId, + page = 1, + limit = 24, + min_price, + max_price, + sorting, + } = params; + const urlParams = new URLSearchParams(); + urlParams.append("page", page); + urlParams.append("limit", limit); + if (min_price) urlParams.append("min_price", min_price); + if (max_price) urlParams.append("max_price", max_price); + if (sorting) urlParams.append("sorting", sorting); + return `/channels/${channelId}/products?${urlParams.toString()}`; + }, + transformResponse: (response) => ({ + data: response.data || response, + pagination: response.pagination || {}, + }), + }), + }), + overrideExisting: true, +}); + +export const { useGetChannelProductsQuery, useLazyGetChannelProductsQuery } = + channelsApi; diff --git a/src/app/api/filtersApi.js b/src/app/api/filtersApi.js index ba7a110..9cf1fac 100644 --- a/src/app/api/filtersApi.js +++ b/src/app/api/filtersApi.js @@ -15,6 +15,9 @@ export const filtersApi = baseApi.injectEndpoints({ if (params?.brand_id) { queryParams.append("brand_id", String(params.brand_id)) } + if (params?.channel_id) { + queryParams.append("channel_id", String(params.channel_id)) + } return `/filters?${queryParams.toString()}` }, @@ -22,6 +25,7 @@ export const filtersApi = baseApi.injectEndpoints({ return { categories: response.data?.categories || [], brands: response.data?.brands || [], + channels: response.data?.channels || [], } }, keepUnusedDataFor: 300, @@ -40,7 +44,9 @@ export const filtersApi = baseApi.injectEndpoints({ if (queryArgs.brand_id) { parts.push(`brd:${queryArgs.brand_id}`); } - + if (queryArgs.channel_id) { + parts.push(`chn:${queryArgs.channel_id}`); + } return parts.length > 0 ? parts.join('|') : 'no-params'; }, @@ -66,7 +72,9 @@ export const filtersApi = baseApi.injectEndpoints({ if (arg.brand_id) { tags.push({ type: "Filters", id: `brd-${arg.brand_id}` }); } - + if (arg.channel_id) { + tags.push({ type: "Filters", id: `chn-${arg.channel_id}` }); + } return tags; }, }), diff --git a/src/components/CategoryDropdown/DropdownMenu.module.scss b/src/components/CategoryDropdown/DropdownMenu.module.scss index 2532815..f10cfe6 100644 --- a/src/components/CategoryDropdown/DropdownMenu.module.scss +++ b/src/components/CategoryDropdown/DropdownMenu.module.scss @@ -87,24 +87,31 @@ overflow: hidden; width: 1336px; max-height: 520px; - // max-width: calc(100vw - 32px); + max-width: calc(100vw - 32px); } // ---- LEFT LIST ---- .categoriesList { width: 270px; flex-shrink: 0; - border-right: 1px solid rgba(255, 255, 255, 0.12); + border-right: 1px solid #e5e7eb; padding: 10px 0; max-height: 520px; overflow-y: auto; &::-webkit-scrollbar { - width: 4px; + width: 6px; + } + &::-webkit-scrollbar-track { + background: #f9fafb; } &::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; + background: #d1d5db; + border-radius: 10px; + + &:hover { + background: #9ca3af; + } } } @@ -119,13 +126,13 @@ transition: background-color 0.12s, color 0.12s; &:hover { - background-color: rgba(255, 255, 255, 0.08); - color: #000; + background-color: #f3f4f6; + color: #e63946; } &.active { - background-color: rgba(255, 255, 255, 0.15); - color: #000; + background-color: #f3f4f6; + color: #e63946; .title { font-weight: 600; @@ -138,7 +145,7 @@ } .chevron { - color: rgba(255, 255, 255, 0.4); + color: #9ca3af; flex-shrink: 0; } @@ -151,11 +158,18 @@ background: #ffffff; &::-webkit-scrollbar { - width: 4px; + width: 6px; + } + &::-webkit-scrollbar-track { + background: #f9fafb; } &::-webkit-scrollbar-thumb { background: #d1d5db; - border-radius: 2px; + border-radius: 10px; + + &:hover { + background: #9ca3af; + } } } diff --git a/src/pages/Category/hooks/useCategoryData.js b/src/pages/Category/hooks/useCategoryData.js index aec25b4..58a759d 100644 --- a/src/pages/Category/hooks/useCategoryData.js +++ b/src/pages/Category/hooks/useCategoryData.js @@ -10,6 +10,7 @@ const useCategoryData = ({ categoryId, collectionId, brandId, + channelId, // EKLE selectedFilterCategory, searchQuery, }) => { @@ -23,8 +24,9 @@ const useCategoryData = ({ if (categoryId) return { category_id: categoryId }; if (collectionId) return { collection_id: collectionId }; if (brandId) return { brand_id: brandId }; + if (channelId) return { channel_id: channelId }; return null; - }, [categoryId, collectionId, brandId, selectedFilterCategory, searchQuery]); + }, [categoryId, collectionId, brandId, channelId, selectedFilterCategory, searchQuery]); const { data: filtersData, diff --git a/src/pages/Category/hooks/useCategoryProducts.js b/src/pages/Category/hooks/useCategoryProducts.js index 91810d7..8f5020c 100644 --- a/src/pages/Category/hooks/useCategoryProducts.js +++ b/src/pages/Category/hooks/useCategoryProducts.js @@ -1,15 +1,17 @@ -import { useState, useEffect, useRef, useCallback } from "react"; +import { useState, useEffect, useRef } from "react"; import { useLazyGetAllCategoryProductsPaginatedQuery, useGetCategoryProductsQuery, } from "../../../app/api/categories"; import { useLazyGetBrandProductsQuery } from "../../../app/api/brandsApi"; import { useLazyGetCollectionProductsPaginatedQuery } from "../../../app/api/collectionsApi"; +import { useLazyGetChannelProductsQuery } from "../../../app/api/channelsApi"; // EKLE const useCategoryProducts = ({ categoryId, collectionId, brandId, + channelId, selectedCategory, isSubCategory, currentPage, @@ -27,8 +29,6 @@ const useCategoryProducts = ({ const [isFetching, setIsFetching] = useState(false); const activeRequestId = useRef(0); - // Tüm parametreleri ref'te tut — stale closure'ı tamamen engelle - const paramsRef = useRef({}); const shouldUseBaseQuery = categoryId && @@ -36,7 +36,8 @@ const useCategoryProducts = ({ !searchQuery && !selectedFilterCategory && !brandId && - !collectionId; + !collectionId && + !channelId; const { data: baseQueryData, isFetching: baseQueryFetching } = useGetCategoryProductsQuery( @@ -54,8 +55,17 @@ const useCategoryProducts = ({ const [fetchCategoryPaginated] = useLazyGetAllCategoryProductsPaginatedQuery(); const [fetchBrandPaginated] = useLazyGetBrandProductsQuery(); const [fetchCollectionPaginated] = useLazyGetCollectionProductsPaginatedQuery(); + const [fetchChannelPaginated] = useLazyGetChannelProductsQuery(); + + // ✅ Ref'e al — dependency array'den çıkar, stale closure yok + const fetchersRef = useRef({}); + fetchersRef.current = { + fetchCategoryPaginated, + fetchBrandPaginated, + fetchCollectionPaginated, + fetchChannelPaginated, + }; - // Base query handler useEffect(() => { if (!shouldUseBaseQuery || !baseQueryData) return; const data = baseQueryData.data || []; @@ -69,11 +79,23 @@ const useCategoryProducts = ({ setHasMore(hasNextPage); }, [baseQueryData, currentPage, shouldUseBaseQuery]); - // Her fetch çağrısını doğrudan effect içinde yap — useCallback kaldırıldı + + useEffect(() => { if (shouldUseBaseQuery || searchQuery) return; - // Parametreleri snapshot al + + console.log("🔥 LAZY EFFECT TRIGGERED", { + shouldUseBaseQuery, + categoryId, + collectionId, + brandId, + channelId, + isSubCategory, + selectedFilterCategory, + selectedCategory, + }); + const snapshot = { currentPage, selectedFilterCategory, @@ -81,6 +103,7 @@ const useCategoryProducts = ({ isSubCategory, brandId, collectionId, + channelId, selectedFilterBrand, minPrice, maxPrice, @@ -92,6 +115,13 @@ const useCategoryProducts = ({ const run = async () => { try { + const { + fetchCategoryPaginated, + fetchBrandPaginated, + fetchCollectionPaginated, + fetchChannelPaginated, + } = fetchersRef.current; // ✅ ref'ten oku + const params = { page: snapshot.currentPage, limit: 24, @@ -123,6 +153,11 @@ const useCategoryProducts = ({ collectionId: snapshot.collectionId, ...params, }).unwrap(); + } else if (snapshot.channelId) { + result = await fetchChannelPaginated({ + channelId: snapshot.channelId, + ...params, + }).unwrap(); } if (requestId !== activeRequestId.current) return; @@ -159,7 +194,6 @@ const useCategoryProducts = ({ run(); }, [ - // useCallback YOK — her dependency değişince effect direkt çalışır shouldUseBaseQuery, searchQuery, currentPage, @@ -168,13 +202,12 @@ const useCategoryProducts = ({ isSubCategory, brandId, collectionId, + channelId, selectedFilterBrand, minPrice, maxPrice, sorting, - fetchCategoryPaginated, - fetchBrandPaginated, - fetchCollectionPaginated, + // ✅ fetcher fonksiyonlar dependency'den tamamen çıktı ]); const isLoading = shouldUseBaseQuery ? baseQueryFetching : isFetching; @@ -188,4 +221,5 @@ const useCategoryProducts = ({ }; }; -export default useCategoryProducts; \ No newline at end of file +export default useCategoryProducts; + diff --git a/src/pages/Category/index.jsx b/src/pages/Category/index.jsx index b11c0be..ad1d3ce 100644 --- a/src/pages/Category/index.jsx +++ b/src/pages/Category/index.jsx @@ -19,16 +19,19 @@ import MobilePhoneCard from "./components/Mobilephonecard"; const CategoryPage = () => { const { t } = useTranslation(); - const { categoryId, collectionId, brandId } = useParams(); + const { categoryId, collectionId, brandId, channelId } = useParams(); const location = useLocation(); const navigate = useNavigate(); const routeKey = useMemo( - () => `${categoryId || "x"}-${collectionId || "x"}-${brandId || "x"}`, - [categoryId, collectionId, brandId], + () => `${categoryId || "x"}-${collectionId || "x"}-${brandId || "x"}-${channelId || "x"}`, + [categoryId, collectionId, brandId, channelId], ); const getSavedState = (key, defaultVal) => { + if (location.state?.clearFilters) { + return defaultVal; + } try { const saved = sessionStorage.getItem(`category_${key}_${routeKey}`); if (saved) return JSON.parse(saved); @@ -92,6 +95,7 @@ const CategoryPage = () => { categoryId, collectionId, brandId, + channelId, selectedFilterCategory: filterState.selectedFilterCategory, searchQuery, }); @@ -105,6 +109,7 @@ const CategoryPage = () => { } = useCategoryProducts({ categoryId, collectionId, + channelId, brandId, selectedCategory, isSubCategory, @@ -137,10 +142,12 @@ const CategoryPage = () => { prevRouteRef.current = routeKey; - const savedPageState = getSavedStateByKey(routeKey, "pageState"); - const savedFilterState = getSavedStateByKey(routeKey, "filterState"); - const savedProducts = getSavedStateByKey(routeKey, "products"); - const savedHasMore = getSavedStateByKey(routeKey, "hasMore"); + const shouldClear = location.state?.clearFilters; + + const savedPageState = shouldClear ? null : getSavedStateByKey(routeKey, "pageState"); + const savedFilterState = shouldClear ? null : getSavedStateByKey(routeKey, "filterState"); + const savedProducts = shouldClear ? null : getSavedStateByKey(routeKey, "products"); + const savedHasMore = shouldClear ? null : getSavedStateByKey(routeKey, "hasMore"); if (savedPageState && savedFilterState && savedProducts) { setPageState(savedPageState); diff --git a/src/pages/ProductDetail/index.jsx b/src/pages/ProductDetail/index.jsx index 49a110d..1ff62cc 100644 --- a/src/pages/ProductDetail/index.jsx +++ b/src/pages/ProductDetail/index.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { useParams, useNavigate } from "react-router-dom"; +import { useParams, useNavigate, Link } from "react-router-dom"; import styles from "./ProductPage.module.scss"; import { IoMdHeartEmpty, IoMdHeart } from "react-icons/io"; import { FaShoppingCart } from "react-icons/fa"; @@ -366,12 +366,14 @@ const ProductPage = ({ )} {product.channel?.[0]?.name && ( -
+ + {t("order.channel")} {product.channel[0].name} -
+ + )} {product.properties?.length > 0 && ( diff --git a/src/routes.jsx b/src/routes.jsx index 2915abb..3fa0307 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -40,6 +40,7 @@ export default function Router() { { path: "/category/:categoryId", element: }, { path: "/search", element: }, { path: "/collections/:collectionId", element: }, + { path: "/channel/:channelId", element: }, { path: "/product/:productId", element: }, { path: "/profile", element: }, { path: "/orders", element: },