Skip to content

Commit 7ce5526

Browse files
Merge branch 'OpenCodeChicago:main' into prelint
2 parents fceba3b + bb8bd1d commit 7ce5526

8 files changed

Lines changed: 809 additions & 256 deletions

File tree

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@
139139
"contributions": [
140140
"code"
141141
]
142+
},
143+
{
144+
"login": "codervipul775",
145+
"name": "Vipul",
146+
"avatar_url": "https://avatars.githubusercontent.com/u/184411035?v=4",
147+
"profile": "https://github.com/codervipul775",
148+
"contributions": [
149+
"code"
150+
]
142151
}
143152
],
144153
"commitType": "docs",

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ Thanks goes to these wonderful people:
279279
<td align="center" valign="top" width="14.28%"><a href="http://prisca.vercel.app"><img src="https://avatars.githubusercontent.com/u/60774343?v=4?s=100" width="100px;" alt="Onwudebelu Prisca Ebubechukwu"/><br /><sub><b>Onwudebelu Prisca Ebubechukwu</b></sub></a><br /><a href="https://github.com/OpenCodeChicago/hacktoberfest-2025-frontend/commits?author=PriscaTonia" title="Code">💻</a></td>
280280
<td align="center" valign="top" width="14.28%"><a href="https://github.com/shourya2006"><img src="https://avatars.githubusercontent.com/u/75479705?v=4?s=100" width="100px;" alt="Shourya"/><br /><sub><b>Shourya</b></sub></a><br /><a href="https://github.com/OpenCodeChicago/hacktoberfest-2025-frontend/commits?author=shourya2006" title="Code">💻</a></td>
281281
</tr>
282+
<tr>
283+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/codervipul775"><img src="https://avatars.githubusercontent.com/u/184411035?v=4?s=100" width="100px;" alt="Vipul"/><br /><sub><b>Vipul</b></sub></a><br /><a href="https://github.com/OpenCodeChicago/hacktoberfest-2025-frontend/commits?author=codervipul775" title="Code">💻</a></td>
284+
</tr>
282285
</tbody>
283286
</table>
284287

src/components/Products/ProductCard.jsx

Lines changed: 357 additions & 235 deletions
Large diffs are not rendered by default.

src/components/Search/SearchBox.jsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,20 @@ export default function SearchBox({ onClose, isOpen }) {
114114
<div className="relative w-full">
115115
<input
116116
type="text"
117+
id="search"
118+
class="w-full px-4 py-3 pr-16 bg-gray-100 border border-gray-200 rounded-md
119+
text-gray-700 appearance-none focus:outline-none focus:ring-2
120+
focus:ring-blue-600 peer hover:shadow-sm focus:shadow-md"
121+
placeholder=" "
117122
value={searchQuery}
118123
onChange={(e) => setSearchQuery(e.target.value)}
119-
placeholder="Searching for..."
120-
className="w-full px-4 py-3 pr-16 bg-gray-100 border border-gray-200 rounded-md
121-
text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-2
122-
focus:ring-blue-600 hover:shadow-sm focus:shadow-md transition-all duration-200"
123124
/>
125+
<label
126+
for="search"
127+
class="absolute text-sm text-gray-400 duration-300 transform -translate-y-4 scale-75 top-4 left-3 z-10 origin-[0] px-2 peer-focus:px-2 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:text-gray-400 peer-focus:top-3 peer-focus:scale-75 peer-focus:-translate-y-3 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto start-1"
128+
>
129+
Search for...
130+
</label>
124131

125132
{/* Clear button */}
126133
{searchQuery && !loading && (

src/components/TopHeader/TopHeader.jsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
import React, { useEffect, useState } from 'react';
2-
import { Facebook, Twitter, Instagram, Linkedin } from 'lucide-react';
2+
import {
3+
FaFacebookF,
4+
FaTwitter,
5+
FaInstagram,
6+
FaLinkedinIn,
7+
} from 'react-icons/fa';
38

49
export default function TopHeader() {
5-
const messages = ['Buy 1 get 1 50% off', 'Free shipping on orders over $110'];
10+
const messages = [
11+
<>
12+
Buy 1 get 1{' '}
13+
<span className="underline decoration-red-500 decoration-2 underline-offset-4 text-white">
14+
50% off
15+
</span>
16+
</>,
17+
<>
18+
<span className="underline decoration-red-500 decoration-2 underline-offset-4 text-white">
19+
Free shipping
20+
</span>{' '}
21+
on orders over $110
22+
</>,
23+
];
624

725
const [index, setIndex] = useState(0);
826

@@ -21,26 +39,27 @@ export default function TopHeader() {
2139
{messages[index]}
2240
</p>
2341
</div>
42+
{/* Right Side Icons */}
2443
<div className="flex space-x-4 ml-4">
2544
<a href="#" aria-label="Facebook" className="hover:text-gray-300">
26-
<Facebook
45+
<FaFacebookF
2746
size={18}
2847
className="fill-current text-white hover:text-gray-300"
2948
stroke="none"
3049
/>
3150
</a>
3251
<a href="#" aria-label="Twitter" className="hover:text-gray-300">
33-
<Twitter
52+
<FaTwitter
3453
size={18}
3554
className="fill-current text-white hover:text-gray-300"
3655
stroke="none"
3756
/>
3857
</a>
3958
<a href="#" aria-label="Instagram" className="hover:text-gray-300">
40-
<Instagram size={18} />
59+
<FaInstagram size={18} />
4160
</a>
4261
<a href="#" aria-label="LinkedIn" className="hover:text-gray-300">
43-
<Linkedin
62+
<FaLinkedinIn
4463
size={18}
4564
className="fill-current text-white hover:text-gray-300"
4665
stroke="none"
Lines changed: 206 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,207 @@
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

Comments
 (0)