1- import React from 'react'
2- import GarageSaleBanner from '../../components/GarageSaleBanner'
3-
4- const GarageSale = ( ) => {
5- return (
6- < div className = 'w-full h-screen overflow-hidden' >
7- < GarageSaleBanner />
8- </ div >
9- )
10- }
1+ import { useEffect , useState , useCallback , useRef , useMemo } from 'react' ;
2+ import { getProducts } from '../../api/productService' ;
3+ import GarageSaleBanner from '../../components/GarageSaleBanner' ;
4+ import ProductCard from '../../components/Products/ProductCard' ;
5+ import SEO from '../../components/SEO' ;
6+ import ProductSkeleton from '../../components/Products/ProductSkeleton' ;
7+ import RecentlyViewed from '../../components/RecentlyViewed' ;
8+ import {
9+ toggleWishlist ,
10+ isInWishlist ,
11+ } from '../../utils/wishlist' ;
12+ import { toggleCart , isInCart } from '../../utils/cart' ;
13+
14+ export default function GarageSale ( ) {
15+ const [ products , setProducts ] = useState ( [ ] ) ;
16+ const [ loading , setLoading ] = useState ( true ) ;
17+ const [ error , setError ] = useState ( null ) ;
18+ const [ displayedCount , setDisplayedCount ] = useState ( 12 ) ;
19+ const observer = useRef ( ) ;
20+
21+ // Filter only sale products
22+ const saleProducts = useMemo ( ( ) => {
23+ return products . filter ( ( p ) => p . onSale ) ;
24+ } , [ products ] ) ;
25+
26+ const displayedProducts = saleProducts . slice ( 0 , displayedCount ) ;
27+ const hasMoreProducts = displayedCount < saleProducts . length ;
28+
29+ // Infinite scroll callback
30+ const lastProductElementRef = useCallback (
31+ ( node ) => {
32+ if ( loading ) return ;
33+ if ( observer . current ) observer . current . disconnect ( ) ;
34+ observer . current = new IntersectionObserver ( ( entries ) => {
35+ if ( entries [ 0 ] . isIntersecting && hasMoreProducts ) {
36+ setDisplayedCount ( ( prevCount ) =>
37+ Math . min ( prevCount + 5 , saleProducts . length )
38+ ) ;
39+ }
40+ } ) ;
41+ if ( node ) observer . current . observe ( node ) ;
42+ } ,
43+ [ loading , hasMoreProducts , saleProducts . length ]
44+ ) ;
45+
46+ useEffect ( ( ) => {
47+ const fetchData = async ( ) => {
48+ try {
49+ setLoading ( true ) ;
50+ const response = await getProducts ( { limit : 1000 } ) ;
51+ if ( response . success ) {
52+ setProducts ( response . data . products ) ;
53+ } else {
54+ setError ( 'Failed to load products' ) ;
55+ }
56+ } catch ( err ) {
57+ setError ( 'Failed to load products ' + err . message ) ;
58+ } finally {
59+ setLoading ( false ) ;
60+ }
61+ } ;
62+ fetchData ( ) ;
63+ } , [ ] ) ;
64+
65+ return (
66+ < >
67+ < SEO
68+ title = "Garage Sale | CoreX Nutrition"
69+ description = "Discover amazing deals on premium sports nutrition supplements. Find discounted protein powders, pre-workouts, and fitness products."
70+ keywords = "garage sale, deals, supplements, CoreX, discount, sale, protein powder, pre-workout, fitness products"
71+ />
72+
73+ < main className = "min-h-screen bg-[#F7FAFF]" >
74+ < GarageSaleBanner />
1175
12- export default GarageSale
76+ { /* Toolbar Section */ }
77+ < section className = "max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8" >
78+ < div className = "flex justify-between items-center" >
79+ { /* Left: All Filters button */ }
80+ < button className = "flex items-center gap-2 bg-transparent text-gray-700 px-6 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" >
81+ < svg
82+ width = "19"
83+ height = "15"
84+ viewBox = "0 0 19 15"
85+ fill = "none"
86+ xmlns = "http://www.w3.org/2000/svg"
87+ >
88+ < path
89+ d = "M0.863281 10.7949H3.13601M3.13601 10.7949C3.13601 12.3012 4.35705 13.5222 5.86328 13.5222C7.36951 13.5222 8.59055 12.3012 8.59055 10.7949C8.59055 9.28865 7.36951 8.06765 5.86328 8.06765C4.35705 8.06765 3.13601 9.28865 3.13601 10.7949ZM15.8633 3.52219H18.136M15.8633 3.52219C15.8633 5.02842 14.6423 6.24947 13.136 6.24947C11.6297 6.24947 10.4087 5.02842 10.4087 3.52219C10.4087 2.01597 11.6297 0.794922 13.136 0.794922C14.6423 0.794922 15.8633 2.01597 15.8633 3.52219ZM11.3178 10.7949H18.136M0.863281 3.52219H7.68146"
90+ stroke = "#0D1B2A"
91+ strokeLinecap = "round"
92+ />
93+ </ svg >
94+ All Filters
95+ </ button >
96+
97+ < div className = "flex items-center gap-4" >
98+ < span className = "font-semibold text-lg text-gray-800" >
99+ Sort by:
100+ </ span >
101+ < button className = "bg-transparent text-gray-700 px-6 py-3 rounded-lg font-semibold border-2 border-gray-300 hover:border-blue-500 hover:text-blue-600 hover:bg-blue-50 transition-all duration-300" >
102+ Best Selling
103+ </ button >
104+ </ div >
105+ </ div >
106+ </ section >
107+
108+ { /* Product Grid Section */ }
109+ < section className = "max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 pb-12" >
110+ < div className = "flex flex-col gap-8" >
111+ < div className = "w-full" >
112+ { error && (
113+ < div className = "bg-red-50 border border-red-200 rounded-2xl p-8 text-center" >
114+ < div className = "text-red-600 mb-4" >
115+ < svg
116+ className = "w-16 h-16 mx-auto mb-4"
117+ fill = "none"
118+ stroke = "currentColor"
119+ viewBox = "0 0 24 24"
120+ >
121+ < path
122+ strokeLinecap = "round"
123+ strokeLinejoin = "round"
124+ strokeWidth = { 2 }
125+ d = "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.728-.833-2.498 0L4.316 15.5c-.77.833.192 2.5 1.732 2.5z"
126+ />
127+ </ svg >
128+ </ div >
129+ < h3 className = "text-xl font-bold text-red-800 mb-3" >
130+ Unable to Load Sale Products
131+ </ h3 >
132+ < p className = "text-red-600 mb-6" > { error } </ p >
133+ < button
134+ onClick = { ( ) => window . location . reload ( ) }
135+ className = "bg-red-600 text-white px-8 py-3 rounded-full hover:bg-red-700 transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:scale-105"
136+ >
137+ Try Again
138+ </ button >
139+ </ div >
140+ ) }
141+
142+ { loading && < ProductSkeleton count = { 12 } /> }
143+
144+ { ! loading && ! error && displayedProducts . length > 0 && (
145+ < >
146+ < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12" >
147+ { displayedProducts . map ( ( product , index ) => {
148+ const isLast = index === displayedProducts . length - 1 ;
149+ return (
150+ < ProductCard
151+ key = { product . id || product . _id }
152+ product = { product }
153+ ref = { isLast ? lastProductElementRef : null }
154+ onAddToWishlist = { ( prod ) => toggleWishlist ( prod ) }
155+ onAddToCart = { ( prod , flavor ) =>
156+ toggleCart ( prod , flavor )
157+ }
158+ isWishlisted = { isInWishlist ( product . id || product . _id ) }
159+ isInCart = { ( flavor ) => isInCart ( product , flavor ) }
160+ />
161+ ) ;
162+ } ) }
163+ </ div >
164+
165+ { hasMoreProducts && (
166+ < div className = "flex justify-center mt-8" >
167+ < div className = "text-gray-500" > Loading more...</ div >
168+ </ div >
169+ ) }
170+ </ >
171+ ) }
172+
173+ { ! loading && ! error && saleProducts . length === 0 && (
174+ < div className = "bg-white rounded-2xl shadow-sm p-16 text-center border border-gray-100" >
175+ < div className = "text-gray-400 mb-6" >
176+ < svg
177+ className = "w-20 h-20 mx-auto"
178+ fill = "none"
179+ stroke = "currentColor"
180+ viewBox = "0 0 24 24"
181+ >
182+ < path
183+ strokeLinecap = "round"
184+ strokeLinejoin = "round"
185+ strokeWidth = { 1.5 }
186+ d = "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
187+ />
188+ </ svg >
189+ </ div >
190+ < h3 className = "text-2xl font-bold text-gray-800 mb-3" >
191+ No Sale Products Available
192+ </ h3 >
193+ < p className = "text-gray-600 mb-8 max-w-md mx-auto" >
194+ Check back soon for amazing deals on our premium sports
195+ nutrition supplements!
196+ </ p >
197+ </ div >
198+ ) }
199+ </ div >
200+ </ div >
201+ </ section >
202+
203+ { ! loading && ! error && < RecentlyViewed /> }
204+ </ main >
205+ </ >
206+ ) ;
207+ }
0 commit comments