Skip to content

Commit d605447

Browse files
committed
fix: update best of core x component
1 parent 15d8600 commit d605447

3 files changed

Lines changed: 91 additions & 42 deletions

File tree

src/components/BestOfCorex.jsx

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo, useCallback } from 'react';
22
import { ChevronLeft, ChevronRight } from 'lucide-react';
33
import { useProductCarousel } from '../hooks/useProductCarousel';
44
import ProductSkeleton from './Products/ProductSkeleton';
@@ -7,25 +7,40 @@ import { useBestOfCoreX } from '../hooks/useBestOfCoreX';
77

88
const BestOfCoreX = () => {
99
const {
10-
collections,
10+
collections = [],
1111
activeTab,
1212
setActiveTab,
1313
products,
1414
loading,
15-
error,
16-
allCollectionsData
15+
allCollectionsData,
1716
} = useBestOfCoreX();
1817

1918
// Use the custom hook to manage all carousel state and logic
2019
const { scrollContainerRef, currentPage, productPages, scroll, showArrows } =
2120
useProductCarousel({ products, productsPerPage: 6 });
2221

22+
// memoize tab names for stability
23+
const tabNames = useMemo(
24+
() => (collections || []).map(([name]) => name),
25+
[collections]
26+
);
27+
28+
const handleSetActive = useCallback(
29+
(name) => {
30+
if (typeof setActiveTab === 'function') setActiveTab(name);
31+
},
32+
[setActiveTab]
33+
);
34+
2335
return (
24-
<section className="px-4 sm:px-8 py-12 font-sans">
36+
<section
37+
className="px-4 sm:px-8 py-12 font-sans"
38+
aria-labelledby="best-of-corex-heading"
39+
>
2540
<div className="container mx-auto">
2641
<div className="flex justify-center items-center gap-4">
2742
<h2
28-
id="why-choose"
43+
id="best-of-corex-heading"
2944
className="text-4xl lg:text-heading-xxl font-montserrat text-black leading-none uppercase py-16 section-title"
3045
>
3146
<span className="text-[#000]">BEST </span>
@@ -37,35 +52,50 @@ const BestOfCoreX = () => {
3752
</div>
3853

3954
{/* Tab Navigation & Carousel Arrows */}
40-
<div className="flex justify-center items-center flex-wrap gap-4 mb-8">
55+
<div
56+
className="flex justify-center items-center flex-wrap gap-4 mb-8"
57+
role="tablist"
58+
aria-label="Collections"
59+
>
4160
{/* Tab Buttons */}
42-
{collections.map(([name]) => (
43-
<button
44-
key={name}
45-
onClick={() => setActiveTab(name)}
46-
className={`px-4 py-2 text-sm font-semibold rounded-full transition-colors ${
47-
activeTab === name
48-
? 'bg-black text-white'
49-
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
50-
}`}
51-
>
52-
{name}
53-
</button>
54-
))}
61+
{tabNames.length > 0 ? (
62+
tabNames.map((name) => (
63+
<button
64+
key={name}
65+
role="tab"
66+
aria-selected={activeTab === name}
67+
aria-current={activeTab === name ? 'true' : undefined}
68+
onClick={() => handleSetActive(name)}
69+
className={`px-4 py-2 text-sm font-semibold rounded-full transition-colors ${
70+
activeTab === name
71+
? 'bg-black text-white'
72+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
73+
}`}
74+
>
75+
{name}
76+
</button>
77+
))
78+
) : (
79+
<div className="text-sm text-gray-500">
80+
No collections available
81+
</div>
82+
)}
5583

5684
{/* Carousel Arrows */}
57-
{showArrows && !loading && (
58-
<div className="flex items-center gap-2">
85+
{showArrows && !loading && productPages.length > 0 && (
86+
<div className="flex items-center gap-2" aria-hidden={loading}>
5987
<button
6088
onClick={() => scroll(-1)}
6189
disabled={currentPage === 0}
90+
aria-label="Previous page"
6291
className="p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition disabled:opacity-50 disabled:hover:cursor-not-allowed"
6392
>
6493
<ChevronLeft className="h-5 w-5" />
6594
</button>
6695
<button
6796
onClick={() => scroll(1)}
6897
disabled={currentPage >= productPages.length - 1}
98+
aria-label="Next page"
6999
className="p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition disabled:opacity-50 disabled:hover:cursor-not-allowed"
70100
>
71101
<ChevronRight className="h-5 w-5" />
@@ -83,13 +113,21 @@ const BestOfCoreX = () => {
83113
</div>
84114
)}
85115

86-
87116
{/* Product Carousel */}
88-
{!loading && allCollectionsData && (
117+
{!loading &&
118+
allCollectionsData &&
119+
productPages &&
120+
productPages.length > 0 ? (
89121
<ProductCarousel
90122
productPages={productPages}
91123
scrollContainerRef={scrollContainerRef}
92124
/>
125+
) : (
126+
!loading && (
127+
<div className="text-center py-8 text-gray-500">
128+
No products to display
129+
</div>
130+
)
93131
)}
94132
</div>
95133
</section>

src/components/Products/ProductGrid.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ProductCard from './ProductCard';
22

3-
export default function ProductGrid({ products, lastProductElementRef, }) {
3+
export default function ProductGrid({ products, lastProductElementRef }) {
44
if (!products || products.length === 0) {
55
return null;
66
}

src/pages/Products/Products.jsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState, useCallback, useRef, useMemo, } from 'react';
1+
import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
33
import { fetchProducts } from '../../store/productSlice';
44
import SEO from '../../components/SEO';
@@ -18,11 +18,13 @@ export default function Products() {
1818
const observer = useRef();
1919

2020
const [displayedCount, setDisplayedCount] = useState(12);
21-
21+
2222
// Get max price from products
2323
const maxProductPrice = useMemo(() => {
2424
if (!allProducts || allProducts.length === 0) return 100;
25-
const prices = allProducts.map(p => Number(p.price || 0)).filter(p => !isNaN(p) && p > 0);
25+
const prices = allProducts
26+
.map((p) => Number(p.price || 0))
27+
.filter((p) => !isNaN(p) && p > 0);
2628
return Math.ceil(Math.max(...prices)) || 100;
2729
}, [allProducts]);
2830

@@ -45,9 +47,8 @@ export default function Products() {
4547
return allProducts.filter((p) => {
4648
const price = Number(p.price || 0);
4749
if (Number.isNaN(price)) return false;
48-
49-
if (price < minPrice || price > maxPrice) return false;
5050

51+
if (price < minPrice || price > maxPrice) return false;
5152

5253
if (filters.garageSaleOnly) {
5354
// consider sale fields: sale > 0 or onSale flag
@@ -102,10 +103,12 @@ export default function Products() {
102103
keywords="sports nutrition, supplements, protein powder, pre-workout, fitness products, CoreX Nutrition"
103104
/>
104105

105-
<main className={`min-h-screen bg-[#F7FAFF] ${isFilterOpen ? 'relative' : ''}`}>
106+
<main
107+
className={`min-h-screen bg-[#F7FAFF] ${isFilterOpen ? 'relative' : ''}`}
108+
>
106109
{/* Filter Panel Backdrop - covers entire viewport including header */}
107110
{isFilterOpen && (
108-
<div
111+
<div
109112
className="fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity duration-300"
110113
style={{ zIndex: 9998 }}
111114
onClick={() => setIsFilterOpen(false)}
@@ -130,7 +133,10 @@ export default function Products() {
130133
<section className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
131134
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
132135
{/* Left: All Filters button (full width on mobile) */}
133-
<button onClick={() => setIsFilterOpen(true)} className="w-full sm:w-auto flex items-center justify-center gap-2 bg-transparent text-gray-700 px-4 py-2 sm:px-6 sm:py-3 rounded-lg font-medium border border-gray-300 hover:border-blue-500 hover:text-blue-600 hover:bg-blue-50 transition-all duration-300 cursor-pointer">
136+
<button
137+
onClick={() => setIsFilterOpen(true)}
138+
className="w-full sm:w-auto flex items-center justify-center gap-2 bg-transparent text-gray-700 px-4 py-2 sm:px-6 sm:py-3 rounded-lg font-medium border border-gray-300 hover:border-blue-500 hover:text-blue-600 hover:bg-blue-50 transition-all duration-300 cursor-pointer"
139+
>
134140
<span className="hidden sm:inline">All Filters</span>
135141
<span className="sm:hidden">Filters</span>
136142
<svg
@@ -162,15 +168,22 @@ export default function Products() {
162168
// Supported backend keys: feature (default), a-z, z-a, price_asc, price_desc, rating_asc, rating_desc
163169
let sortKey = 'feature';
164170
if (field === 'title' && order === 'asc') sortKey = 'a-z';
165-
else if (field === 'title' && order === 'desc') sortKey = 'z-a';
166-
else if (field === 'price' && order === 'asc') sortKey = 'price_asc';
167-
else if (field === 'price' && order === 'desc') sortKey = 'price_desc';
168-
else if (field === 'rating' && order === 'asc') sortKey = 'rating_asc';
169-
else if (field === 'rating' && order === 'desc') sortKey = 'rating_desc';
171+
else if (field === 'title' && order === 'desc')
172+
sortKey = 'z-a';
173+
else if (field === 'price' && order === 'asc')
174+
sortKey = 'price_asc';
175+
else if (field === 'price' && order === 'desc')
176+
sortKey = 'price_desc';
177+
else if (field === 'rating' && order === 'asc')
178+
sortKey = 'rating_asc';
179+
else if (field === 'rating' && order === 'desc')
180+
sortKey = 'rating_desc';
170181
else sortKey = 'feature';
171182

172183
// Fetch products from backend with sort
173-
dispatch(fetchProducts({ page: 1, limit: 1000, sort: sortKey }));
184+
dispatch(
185+
fetchProducts({ page: 1, limit: 1000, sort: sortKey })
186+
);
174187
}}
175188
/>
176189
</div>
@@ -223,9 +236,7 @@ export default function Products() {
223236
/>
224237
{hasMoreProducts && (
225238
<div className="flex justify-center mt-8">
226-
<div className="text-gray-500">
227-
Loading more...
228-
</div>
239+
<div className="text-gray-500">Loading more...</div>
229240
</div>
230241
)}
231242
</>

0 commit comments

Comments
 (0)