Skip to content

Commit 8a91206

Browse files
committed
add cart/wishlist count badges
1 parent 83a1fcd commit 8a91206

5 files changed

Lines changed: 223 additions & 66 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Zappify Shoes
2+
<<<<<<< Updated upstream
23
**Zappify Shoes** is an online shoe store built to sell just *one* premium pair of shoes. Instead of a messy website with hundreds of products, Zappify gives the user a super clean and focused shopping experience for that single product.
34

45
### Simple Breakdown of What It Does:
@@ -8,6 +9,17 @@
89
- **Powerful Engine (Backend):** Behind the scenes, it uses a real database to safely store user data and orders. It's built on popular, modern technologies (React for what you see, Node.js and MongoDB for what happens behind the scenes).
910

1011
In simple words, Zappify is a complete, real-world example of an e-commerce website designed to look great and work perfectly from start to checkout.
12+
=======
13+
Zappify Shoes is a highly-optimized, premium e-commerce platform dedicated to showcasing and selling a single, flagship shoe model. Rather than overwhelming users with endless product catalogs, Zappify provides a focused, immersive shopping experience.
14+
15+
### What It Does
16+
- **Immersive Showcase:** Presents the product using smooth, performant animations and a sleek UI built with React and Framer Motion.
17+
- **Secure Shopping:** Users can securely sign up, log in, and manage their sessions via a robust JWT-based authentication system.
18+
- **Order Processing:** Includes backend capabilities for securely capturing user orders, managing cart data, and processing shipping details via a REST API.
19+
- **Scalable Architecture:** Powered by a robust Node.js/Express backend and MongoDB Atlas, offering a complete, production-ready full-stack (MERN) environment containerized with Docker.
20+
21+
Designed for both performance enthusiasts and modern shoe lovers, Zappify ensures a beautiful, secure, and seamless user journey from the landing page to checkout.
22+
>>>>>>> Stashed changes
1123
1224
## Key Features
1325

frontend/src/App.jsx

Lines changed: 159 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,99 @@ import ProductGrid from './components/ProductGrid'
55
import ProductDetail from './components/ProductDetail'
66
import { ALL_PRODUCTS } from './data/products'
77
import { motion, AnimatePresence } from 'framer-motion'
8-
import { X, ShoppingBag, Heart, User, ArrowRight } from 'lucide-react'
8+
import { X, ShoppingBag, Heart, User, Trash2 } from 'lucide-react'
99

1010
function App() {
1111
const [selectedCategories, setSelectedCategories] = useState([]);
1212
const [selectedThemes, setSelectedThemes] = useState([]);
1313
const [selectedProduct, setSelectedProduct] = useState(null);
1414
const [activeOverlay, setActiveOverlay] = useState(null);
15+
const [cartItems, setCartItems] = useState([]);
16+
const [wishlistItems, setWishlistItems] = useState([]);
17+
const [sortOption, setSortOption] = useState('recommended');
1518

1619
const toggleFilter = (item, type) => {
1720
if (type === 'category') {
18-
setSelectedCategories(prev =>
21+
setSelectedCategories(prev =>
1922
prev.includes(item) ? prev.filter(i => i !== item) : [...prev, item]
2023
);
2124
} else {
22-
setSelectedThemes(prev =>
25+
setSelectedThemes(prev =>
2326
prev.includes(item) ? prev.filter(i => i !== item) : [...prev, item]
2427
);
2528
}
2629
};
2730

31+
const [activeNav, setActiveNav] = useState('MEN');
32+
33+
const [sneakersView, setSneakersView] = useState(false);
34+
2835
const handleNavigate = (destination) => {
29-
if (destination === 'home' || destination === 'MEN' || destination === 'SNEAKERS') {
30-
setSelectedProduct(null);
31-
setSelectedCategories([]);
32-
setSelectedThemes([]);
36+
setSelectedProduct(null);
37+
setSelectedThemes([]);
38+
setSelectedCategories([]);
39+
if (destination === 'SNEAKERS') {
40+
setSneakersView(true);
41+
} else {
42+
setSneakersView(false);
3343
}
44+
if (destination !== 'home') setActiveNav(destination);
45+
};
46+
47+
const addToCart = (product, size) => {
48+
setCartItems(prev => {
49+
const exists = prev.find(i => i.id === product.id && i.size === size);
50+
if (exists) {
51+
return prev.map(i => i.id === product.id && i.size === size ? { ...i, qty: i.qty + 1 } : i);
52+
}
53+
return [...prev, { ...product, size, qty: 1 }];
54+
});
55+
};
56+
57+
const removeFromCart = (id, size) => {
58+
setCartItems(prev => prev.filter(i => !(i.id === id && i.size === size)));
59+
};
60+
61+
const toggleWishlist = (product) => {
62+
setWishlistItems(prev =>
63+
prev.find(i => i.id === product.id)
64+
? prev.filter(i => i.id !== product.id)
65+
: [...prev, product]
66+
);
3467
};
3568

69+
const isWishlisted = (id) => wishlistItems.some(i => i.id === id);
70+
3671
const filteredProducts = useMemo(() => {
37-
return ALL_PRODUCTS.filter(product => {
72+
let result = ALL_PRODUCTS.filter(product => {
73+
const sneakerMatch = sneakersView ? product.id >= 30 && product.id <= 44 : true;
3874
const categoryMatch = selectedCategories.length === 0 || selectedCategories.includes(product.category);
3975
const themeMatch = selectedThemes.length === 0 || selectedThemes.includes(product.theme);
40-
return categoryMatch && themeMatch;
76+
return sneakerMatch && categoryMatch && themeMatch;
4177
});
42-
}, [selectedCategories, selectedThemes]);
78+
79+
if (sortOption === 'low-high') result = [...result].sort((a, b) => a.price - b.price);
80+
else if (sortOption === 'high-low') result = [...result].sort((a, b) => b.price - a.price);
81+
else if (sortOption === 'newest') result = [...result].sort((a, b) => b.id - a.id);
82+
83+
return result;
84+
}, [selectedCategories, selectedThemes, sortOption, sneakersView]);
4385

4486
return (
4587
<div className="zappify-app">
46-
<Header
47-
onOpenOverlay={setActiveOverlay}
88+
<Header
89+
onOpenOverlay={setActiveOverlay}
4890
onNavigate={handleNavigate}
91+
cartCount={cartItems.reduce((sum, i) => sum + i.qty, 0)}
92+
wishlistCount={wishlistItems.length}
93+
activeNav={activeNav}
4994
/>
50-
95+
5196
<main className="app-main">
5297
<div className="container-broad main-layout">
5398
<AnimatePresence mode="wait">
5499
{!selectedProduct ? (
55-
<motion.div
100+
<motion.div
56101
key="grid"
57102
className="grid-view"
58103
initial={{ opacity: 0, x: -20 }}
@@ -61,24 +106,37 @@ function App() {
61106
transition={{ duration: 0.3 }}
62107
>
63108
<div className="layout-split">
64-
<Sidebar
109+
<Sidebar
65110
selectedCategories={selectedCategories}
66111
selectedThemes={selectedThemes}
67112
onToggleFilter={toggleFilter}
68113
/>
69-
<ProductGrid products={filteredProducts} onProductClick={setSelectedProduct} />
114+
<ProductGrid
115+
products={filteredProducts}
116+
onProductClick={setSelectedProduct}
117+
sortOption={sortOption}
118+
onSortChange={setSortOption}
119+
onToggleWishlist={toggleWishlist}
120+
isWishlisted={isWishlisted}
121+
/>
70122
</div>
71123
</motion.div>
72124
) : (
73-
<motion.div
125+
<motion.div
74126
key="detail"
75127
className="detail-view"
76128
initial={{ opacity: 0, y: 20 }}
77129
animate={{ opacity: 1, y: 0 }}
78130
exit={{ opacity: 0, y: -20 }}
79131
transition={{ duration: 0.4 }}
80132
>
81-
<ProductDetail product={selectedProduct} onBack={() => setSelectedProduct(null)} />
133+
<ProductDetail
134+
product={selectedProduct}
135+
onBack={() => setSelectedProduct(null)}
136+
onAddToCart={addToCart}
137+
onToggleWishlist={toggleWishlist}
138+
isWishlisted={isWishlisted(selectedProduct.id)}
139+
/>
82140
</motion.div>
83141
)}
84142
</AnimatePresence>
@@ -95,32 +153,38 @@ function App() {
95153

96154
<AnimatePresence>
97155
{activeOverlay && (
98-
<Overlay
99-
type={activeOverlay}
100-
onClose={() => setActiveOverlay(null)}
156+
<Overlay
157+
type={activeOverlay}
158+
onClose={() => setActiveOverlay(null)}
159+
cartItems={cartItems}
160+
wishlistItems={wishlistItems}
161+
onRemoveFromCart={removeFromCart}
162+
onToggleWishlist={toggleWishlist}
163+
onSwitchOverlay={setActiveOverlay}
101164
/>
102165
)}
103166
</AnimatePresence>
104-
105-
</div>
167+
</div>
106168
)
107169
}
108170

109-
const Overlay = ({ type, onClose }) => {
171+
const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, onToggleWishlist, onSwitchOverlay }) => {
172+
const [isSignUp, setIsSignUp] = useState(false);
110173
const isDrawer = type === 'cart' || type === 'wishlist';
174+
const cartTotal = cartItems.reduce((sum, i) => sum + i.price * i.qty, 0);
111175

112176
return (
113177
<div className="overlay-system">
114-
<motion.div
178+
<motion.div
115179
className="backdrop"
116180
initial={{ opacity: 0 }}
117181
animate={{ opacity: 1 }}
118182
exit={{ opacity: 0 }}
119183
onClick={onClose}
120184
/>
121-
185+
122186
{isDrawer ? (
123-
<motion.div
187+
<motion.div
124188
className="drawer"
125189
initial={{ x: '100%' }}
126190
animate={{ x: 0 }}
@@ -131,16 +195,66 @@ const Overlay = ({ type, onClose }) => {
131195
<h3>{type === 'cart' ? 'SHOPPING BAG' : 'MY WISHLIST'}</h3>
132196
<button className="close-btn" onClick={onClose}><X size={24} /></button>
133197
</div>
198+
134199
<div className="overlay-content">
135-
<div className="empty-state">
136-
{type === 'cart' ? <ShoppingBag size={48} /> : <Heart size={48} />}
137-
<p>Your {type} is currently empty.</p>
138-
<button className="btn-primary" onClick={onClose}>CONTINUE SHOPPING</button>
139-
</div>
200+
{type === 'cart' ? (
201+
cartItems.length === 0 ? (
202+
<div className="empty-state">
203+
<ShoppingBag size={48} />
204+
<p>Your bag is currently empty.</p>
205+
<button className="btn-primary" onClick={onClose}>CONTINUE SHOPPING</button>
206+
</div>
207+
) : (
208+
<div className="cart-items">
209+
{cartItems.map((item, i) => (
210+
<div key={i} className="cart-item">
211+
<img src={item.image} alt={item.name} />
212+
<div className="cart-item-info">
213+
<p className="cart-item-name">{item.name}</p>
214+
<p className="cart-item-size">Size: UK {item.size}</p>
215+
<p className="cart-item-qty">Qty: {item.qty}</p>
216+
<p className="cart-item-price">{item.price * item.qty}</p>
217+
</div>
218+
<button className="remove-btn" onClick={() => onRemoveFromCart(item.id, item.size)}>
219+
<Trash2 size={16} />
220+
</button>
221+
</div>
222+
))}
223+
<div className="cart-total">
224+
<span>Total</span>
225+
<span>{cartTotal}</span>
226+
</div>
227+
<button className="btn-primary checkout-btn">PROCEED TO CHECKOUT</button>
228+
</div>
229+
)
230+
) : (
231+
wishlistItems.length === 0 ? (
232+
<div className="empty-state">
233+
<Heart size={48} />
234+
<p>Your wishlist is currently empty.</p>
235+
<button className="btn-primary" onClick={onClose}>CONTINUE SHOPPING</button>
236+
</div>
237+
) : (
238+
<div className="cart-items">
239+
{wishlistItems.map((item, i) => (
240+
<div key={i} className="cart-item">
241+
<img src={item.image} alt={item.name} />
242+
<div className="cart-item-info">
243+
<p className="cart-item-name">{item.name}</p>
244+
<p className="cart-item-price">{item.price}</p>
245+
</div>
246+
<button className="remove-btn" onClick={() => onToggleWishlist(item)}>
247+
<Trash2 size={16} />
248+
</button>
249+
</div>
250+
))}
251+
</div>
252+
)
253+
)}
140254
</div>
141255
</motion.div>
142256
) : (
143-
<motion.div
257+
<motion.div
144258
className="modal"
145259
initial={{ opacity: 0, scale: 0.9, y: 20 }}
146260
animate={{ opacity: 1, scale: 1, y: 0 }}
@@ -151,23 +265,27 @@ const Overlay = ({ type, onClose }) => {
151265
</div>
152266
<div className="modal-content login-modal">
153267
<User size={48} className="user-icon-large" />
154-
<h2>Welcome Back</h2>
155-
<p>Login to your Zappify account</p>
156-
268+
<h2>{isSignUp ? 'Create Account' : 'Welcome Back'}</h2>
269+
<p>{isSignUp ? 'Join Zappify today' : 'Login to your Zappify account'}</p>
270+
157271
<div className="auth-form">
272+
{isSignUp && <input type="text" placeholder="Full Name" />}
158273
<input type="email" placeholder="Email Address" />
159274
<input type="password" placeholder="Password" />
160-
<button className="btn-primary auth-btn">SIGN IN</button>
275+
{isSignUp && <input type="password" placeholder="Confirm Password" />}
276+
<button className="btn-primary auth-btn">{isSignUp ? 'CREATE ACCOUNT' : 'SIGN IN'}</button>
161277
<div className="separator"><span>OR CONTINUE WITH</span></div>
162278
<button className="btn-outline google-btn">GOOGLE</button>
163279
</div>
164-
165-
<p className="auth-footer">Don't have an account? <span>Sign Up</span></p>
280+
281+
<p className="auth-footer">
282+
{isSignUp ? 'Already have an account? ' : "Don't have an account? "}
283+
<span onClick={() => setIsSignUp(!isSignUp)}>{isSignUp ? 'Sign In' : 'Sign Up'}</span>
284+
</p>
166285
</div>
167286
</motion.div>
168287
)}
169-
170-
</div>
288+
</div>
171289
);
172290
};
173291

0 commit comments

Comments
 (0)