1- import React , { useState , useMemo } from 'react'
1+ import { useState , useMemo } from 'react'
22import Header from './components/Header'
3- import Sidebar from './components/Sidebar'
43import ProductGrid from './components/ProductGrid'
54import ProductDetail from './components/ProductDetail'
65import { ALL_PRODUCTS } from './data/products'
76import { motion , AnimatePresence } from 'framer-motion'
87import { X , ShoppingBag , Heart , User , Trash2 } from 'lucide-react'
9- import { GoogleLogin , useGoogleLogin } from '@react-oauth/google'
8+ import { useGoogleLogin } from '@react-oauth/google'
109import Checkout from './components/Checkout'
1110import AccountModal from './components/AccountModal'
1211
12+ const CATEGORIES = [ 'All' , 'Men Low Top Sneakers' , 'Men High Top Sneakers' , 'Men Mid Top Sneakers' , 'Men Clogs' , 'Men Slip-ons' ] ;
13+
1314function App ( ) {
1415 const [ selectedCategories , setSelectedCategories ] = useState ( [ ] ) ;
15- const [ selectedThemes , setSelectedThemes ] = useState ( [ ] ) ;
1616 const [ selectedProduct , setSelectedProduct ] = useState ( null ) ;
1717 const [ activeOverlay , setActiveOverlay ] = useState ( null ) ;
18+ const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
19+ const [ sneakersView , setSneakersView ] = useState ( false ) ;
20+ const [ showCheckout , setShowCheckout ] = useState ( false ) ;
21+ const [ showAccount , setShowAccount ] = useState ( false ) ;
22+
1823 const [ cartItems , setCartItems ] = useState ( ( ) => {
1924 try { return JSON . parse ( localStorage . getItem ( 'zappify_cart' ) ) || [ ] ; } catch { return [ ] ; }
2025 } ) ;
26+
2127 const [ wishlistItems , setWishlistItems ] = useState ( ( ) => {
2228 try { return JSON . parse ( localStorage . getItem ( 'zappify_wishlist' ) ) || [ ] ; } catch { return [ ] ; }
2329 } ) ;
24- const [ sortOption , setSortOption ] = useState ( 'recommended' ) ;
25- const [ activeNav , setActiveNav ] = useState ( 'MEN' ) ;
26- const [ sneakersView , setSneakersView ] = useState ( false ) ;
27- const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
30+
2831 const [ loggedInUser , setLoggedInUser ] = useState ( ( ) => {
2932 try { return JSON . parse ( localStorage . getItem ( 'zappify_user' ) ) || null ; } catch { return null ; }
3033 } ) ;
31- const [ showCheckout , setShowCheckout ] = useState ( false ) ;
32- const [ showAccount , setShowAccount ] = useState ( false ) ;
33-
34- const getOrdersKey = ( user ) => `zappify_orders_${ user ?. email || 'guest' } ` ;
3534
3635 const [ placedOrders , setPlacedOrders ] = useState ( ( ) => {
3736 try {
@@ -41,12 +40,13 @@ function App() {
4140 } catch { return [ ] ; }
4241 } ) ;
4342
43+ const userOrdersKey = ( user ) => `zappify_orders_${ user ?. email } ` ;
44+
4445 const handleLogin = ( user ) => {
4546 setLoggedInUser ( user ) ;
4647 localStorage . setItem ( 'zappify_user' , JSON . stringify ( user ) ) ;
47- // Load this user's orders
48- const userOrders = JSON . parse ( localStorage . getItem ( `zappify_orders_${ user . email } ` ) ) || [ ] ;
49- setPlacedOrders ( userOrders ) ;
48+ const orders = JSON . parse ( localStorage . getItem ( userOrdersKey ( user ) ) ) || [ ] ;
49+ setPlacedOrders ( orders ) ;
5050 } ;
5151
5252 const handleLogout = ( ) => {
@@ -55,28 +55,10 @@ function App() {
5555 setPlacedOrders ( [ ] ) ;
5656 } ;
5757
58- const toggleFilter = ( item , type ) => {
59- if ( type === 'category' ) {
60- setSelectedCategories ( prev =>
61- prev . includes ( item ) ? prev . filter ( i => i !== item ) : [ ...prev , item ]
62- ) ;
63- } else {
64- setSelectedThemes ( prev =>
65- prev . includes ( item ) ? prev . filter ( i => i !== item ) : [ ...prev , item ]
66- ) ;
67- }
68- } ;
69-
7058 const handleNavigate = ( destination ) => {
7159 setSelectedProduct ( null ) ;
7260 setSelectedCategories ( [ ] ) ;
73- setSelectedThemes ( [ ] ) ;
74- if ( destination === 'SNEAKERS' ) {
75- setSneakersView ( true ) ;
76- } else {
77- setSneakersView ( false ) ;
78- }
79- if ( destination !== 'home' ) setActiveNav ( destination ) ;
61+ setSneakersView ( destination === 'SNEAKERS' ) ;
8062 } ;
8163
8264 const addToCart = ( product , size ) => {
@@ -111,23 +93,16 @@ function App() {
11193 const isWishlisted = ( id ) => wishlistItems . some ( i => i . id === id ) ;
11294
11395 const filteredProducts = useMemo ( ( ) => {
114- let result = ALL_PRODUCTS . filter ( product => {
96+ return ALL_PRODUCTS . filter ( product => {
11597 const sneakerMatch = sneakersView ? product . id >= 30 && product . id <= 44 : true ;
11698 const categoryMatch = selectedCategories . length === 0 || selectedCategories . includes ( product . category ) ;
117- const themeMatch = selectedThemes . length === 0 || selectedThemes . includes ( product . theme ) ;
118- const searchMatch = searchQuery . trim ( ) === '' ||
99+ const searchMatch = searchQuery . trim ( ) === '' ||
119100 product . name . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
120101 product . brand . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
121102 product . category . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ;
122- return sneakerMatch && categoryMatch && themeMatch && searchMatch ;
103+ return sneakerMatch && categoryMatch && searchMatch ;
123104 } ) ;
124-
125- if ( sortOption === 'low-high' ) result = [ ...result ] . sort ( ( a , b ) => a . price - b . price ) ;
126- else if ( sortOption === 'high-low' ) result = [ ...result ] . sort ( ( a , b ) => b . price - a . price ) ;
127- else if ( sortOption === 'newest' ) result = [ ...result ] . sort ( ( a , b ) => b . id - a . id ) ;
128-
129- return result ;
130- } , [ selectedCategories , selectedThemes , sortOption , sneakersView , searchQuery ] ) ;
105+ } , [ selectedCategories , sneakersView , searchQuery ] ) ;
131106
132107 return (
133108 < div className = "zappify-app" >
@@ -136,9 +111,7 @@ function App() {
136111 onNavigate = { handleNavigate }
137112 cartCount = { cartItems . reduce ( ( sum , i ) => sum + i . qty , 0 ) }
138113 wishlistCount = { wishlistItems . length }
139- activeNav = { activeNav }
140114 loggedInUser = { loggedInUser }
141- onLogout = { handleLogout }
142115 onOpenAccount = { ( ) => setShowAccount ( true ) }
143116 searchQuery = { searchQuery }
144117 onSearch = { setSearchQuery }
@@ -156,20 +129,27 @@ function App() {
156129 exit = { { opacity : 0 , x : - 20 } }
157130 transition = { { duration : 0.3 } }
158131 >
159- < div className = "layout-split" > < Sidebar
160- selectedCategories = { selectedCategories }
161- selectedThemes = { selectedThemes }
162- onToggleFilter = { toggleFilter }
163- />
164- < ProductGrid
165- products = { filteredProducts }
166- onProductClick = { setSelectedProduct }
167- sortOption = { sortOption }
168- onSortChange = { setSortOption }
169- onToggleWishlist = { toggleWishlist }
170- isWishlisted = { isWishlisted }
171- />
172- </ div > </ motion . div >
132+ < div className = "category-chips" >
133+ { CATEGORIES . map ( cat => (
134+ < button
135+ key = { cat }
136+ className = { `cat-chip ${ selectedCategories . length === 0 && cat === 'All' ? 'active' : selectedCategories . includes ( cat ) ? 'active' : '' } ` }
137+ onClick = { ( ) => cat === 'All'
138+ ? setSelectedCategories ( [ ] )
139+ : setSelectedCategories ( prev => prev . includes ( cat ) ? prev . filter ( c => c !== cat ) : [ ...prev , cat ] )
140+ }
141+ >
142+ { cat }
143+ </ button >
144+ ) ) }
145+ </ div >
146+ < ProductGrid
147+ products = { filteredProducts }
148+ onProductClick = { setSelectedProduct }
149+ onToggleWishlist = { toggleWishlist }
150+ isWishlisted = { isWishlisted }
151+ />
152+ </ motion . div >
173153 ) : (
174154 < motion . div
175155 key = "detail"
@@ -231,7 +211,7 @@ function App() {
231211 } ) ) ;
232212 const updated = [ ...placedOrders , ...newOrders ] ;
233213 setPlacedOrders ( updated ) ;
234- localStorage . setItem ( getOrdersKey ( loggedInUser ) , JSON . stringify ( updated ) ) ;
214+ localStorage . setItem ( userOrdersKey ( loggedInUser ) , JSON . stringify ( updated ) ) ;
235215 setCartItems ( [ ] ) ;
236216 localStorage . removeItem ( 'zappify_cart' ) ;
237217 } }
@@ -247,12 +227,12 @@ function App() {
247227 onCancelOrder = { ( orderId ) => {
248228 const updated = placedOrders . map ( o => o . orderId === orderId ? { ...o , status : 'Cancelled' } : o ) ;
249229 setPlacedOrders ( updated ) ;
250- localStorage . setItem ( getOrdersKey ( loggedInUser ) , JSON . stringify ( updated ) ) ;
230+ localStorage . setItem ( userOrdersKey ( loggedInUser ) , JSON . stringify ( updated ) ) ;
251231 } }
252232 />
253233 ) }
254234 </ div >
255- )
235+ ) ;
256236}
257237
258238const Overlay = ( { type, onClose, cartItems, wishlistItems, onRemoveFromCart, onToggleWishlist, onLoginSuccess, onCheckout, loggedInUser, onSwitchOverlay } ) => {
@@ -266,16 +246,16 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
266246 onSuccess : async ( tokenResponse ) => {
267247 try {
268248 const res = await fetch ( 'https://www.googleapis.com/oauth2/v3/userinfo' , {
269- headers : { Authorization : `Bearer ${ tokenResponse . access_token } ` }
249+ headers : { Authorization : `Bearer ${ tokenResponse . access_token } ` } ,
270250 } ) ;
271251 const data = await res . json ( ) ;
272252 onLoginSuccess ( { name : data . name , email : data . email , picture : data . picture } ) ;
273253 onClose ( ) ;
274- } catch ( e ) {
275- console . log ( 'Google login failed' , e ) ;
254+ } catch {
255+ setError ( 'Google sign in failed' ) ;
276256 }
277257 } ,
278- onError : ( ) => console . log ( 'Google login failed' ) ,
258+ onError : ( ) => setError ( 'Google sign in failed' ) ,
279259 } ) ;
280260
281261 const handleAuth = ( ) => {
@@ -285,7 +265,11 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
285265 if ( ! formData . name ) { setError ( 'Please enter your name' ) ; return ; }
286266 if ( formData . password !== formData . confirm ) { setError ( 'Passwords do not match' ) ; return ; }
287267 if ( formData . password . length < 6 ) { setError ( 'Password must be at least 6 characters' ) ; return ; }
288- const user = { name : formData . name , email : formData . email , picture : `https://ui-avatars.com/api/?name=${ encodeURIComponent ( formData . name ) } &background=e85d04&color=fff` } ;
268+ const user = {
269+ name : formData . name ,
270+ email : formData . email ,
271+ picture : `https://ui-avatars.com/api/?name=${ encodeURIComponent ( formData . name ) } &background=e85d04&color=fff` ,
272+ } ;
289273 onLoginSuccess ( user ) ;
290274 onClose ( ) ;
291275 } else {
@@ -301,22 +285,10 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
301285
302286 return (
303287 < div className = "overlay-system" >
304- < motion . div
305- className = "backdrop"
306- initial = { { opacity : 0 } }
307- animate = { { opacity : 1 } }
308- exit = { { opacity : 0 } }
309- onClick = { onClose }
310- />
288+ < motion . div className = "backdrop" initial = { { opacity : 0 } } animate = { { opacity : 1 } } exit = { { opacity : 0 } } onClick = { onClose } />
311289
312290 { isDrawer ? (
313- < motion . div
314- className = "drawer"
315- initial = { { x : '100%' } }
316- animate = { { x : 0 } }
317- exit = { { x : '100%' } }
318- transition = { { type : 'spring' , damping : 25 , stiffness : 200 } }
319- >
291+ < motion . div className = "drawer" initial = { { x : '100%' } } animate = { { x : 0 } } exit = { { x : '100%' } } transition = { { type : 'spring' , damping : 25 , stiffness : 200 } } >
320292 < div className = "overlay-header" >
321293 < h3 > { type === 'cart' ? 'SHOPPING BAG' : 'MY WISHLIST' } </ h3 >
322294 < button className = "close-btn" onClick = { onClose } > < X size = { 24 } /> </ button >
@@ -332,36 +304,36 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
332304 </ div >
333305 ) : (
334306 < >
335- < div className = "cart-items" >
336- { cartItems . map ( ( item , i ) => (
337- < div key = { i } className = "cart-item" >
338- < img src = { item . image } alt = { item . name } />
339- < div className = "cart-item-info" >
340- < p className = "cart-item-name" > { item . name } </ p >
341- < p className = "cart-item-size" > Size: UK { item . size } </ p >
342- < p className = "cart-item-qty" > Qty: { item . qty } </ p >
343- < p className = "cart-item-price" > ₹ { ( item . price * item . qty ) . toLocaleString ( 'en-IN' ) } </ p >
307+ < div className = "cart-items" >
308+ { cartItems . map ( ( item , i ) => (
309+ < div key = { i } className = "cart-item" >
310+ < img src = { item . image } alt = { item . name } />
311+ < div className = "cart-item-info" >
312+ < p className = "cart-item-name" > { item . name } </ p >
313+ < p className = "cart-item-size" > Size: UK { item . size } </ p >
314+ < p className = "cart-item-qty" > Qty: { item . qty } </ p >
315+ < p className = "cart-item-price" > ₹ { ( item . price * item . qty ) . toLocaleString ( 'en-IN' ) } </ p >
316+ </ div >
317+ < button className = "remove-btn" onClick = { ( ) => onRemoveFromCart ( item . id , item . size ) } >
318+ < Trash2 size = { 16 } />
319+ </ button >
344320 </ div >
345- < button className = "remove-btn" onClick = { ( ) => onRemoveFromCart ( item . id , item . size ) } >
346- < Trash2 size = { 16 } />
347- </ button >
321+ ) ) }
322+ </ div >
323+ < div className = "cart-footer" >
324+ < div className = "cart-total" >
325+ < span > Total</ span >
326+ < span > ₹ { cartTotal . toLocaleString ( 'en-IN' ) } </ span >
348327 </ div >
349- ) ) }
350- </ div >
351- < div className = "cart-footer" >
352- < div className = "cart-total" >
353- < span > Total</ span >
354- < span > ₹ { cartTotal . toLocaleString ( 'en-IN' ) } </ span >
328+ < button className = "btn-primary checkout-btn" onClick = { ( ) => {
329+ if ( ! loggedInUser ) {
330+ onClose ( ) ;
331+ setTimeout ( ( ) => onSwitchOverlay ( 'login' ) , 100 ) ;
332+ } else {
333+ onCheckout ( ) ;
334+ }
335+ } } > PROCEED TO CHECKOUT</ button >
355336 </ div >
356- < button className = "btn-primary checkout-btn" onClick = { ( ) => {
357- if ( ! loggedInUser ) {
358- onClose ( ) ;
359- setTimeout ( ( ) => onSwitchOverlay ( 'login' ) , 100 ) ;
360- } else {
361- onCheckout ( ) ;
362- }
363- } } > PROCEED TO CHECKOUT</ button >
364- </ div >
365337 </ >
366338 )
367339 ) : (
@@ -391,12 +363,7 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
391363 </ div >
392364 </ motion . div >
393365 ) : (
394- < motion . div
395- className = "modal"
396- initial = { { opacity : 0 , scale : 0.9 , y : 20 } }
397- animate = { { opacity : 1 , scale : 1 , y : 0 } }
398- exit = { { opacity : 0 , scale : 0.9 , y : 20 } }
399- >
366+ < motion . div className = "modal" initial = { { opacity : 0 , scale : 0.9 , y : 20 } } animate = { { opacity : 1 , scale : 1 , y : 0 } } exit = { { opacity : 0 , scale : 0.9 , y : 20 } } >
400367 < div className = "modal-header" >
401368 < button className = "close-btn" onClick = { onClose } > < X size = { 24 } /> </ button >
402369 </ div >
@@ -406,10 +373,10 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
406373 < p > { isSignUp ? 'Join Zappify today' : 'Login to your Zappify account' } </ p >
407374
408375 < div className = "auth-form" >
409- { isSignUp && < input type = "text" placeholder = "Full Name" value = { formData . name } onChange = { e => setFormData ( { ...formData , name : e . target . value } ) } /> }
410- < input type = "email" placeholder = "Email Address" value = { formData . email } onChange = { e => setFormData ( { ...formData , email : e . target . value } ) } />
411- < input type = "password" placeholder = "Password" value = { formData . password } onChange = { e => setFormData ( { ...formData , password : e . target . value } ) } />
412- { isSignUp && < input type = "password" placeholder = "Confirm Password" value = { formData . confirm } onChange = { e => setFormData ( { ...formData , confirm : e . target . value } ) } /> }
376+ { isSignUp && < input type = "text" placeholder = "Full Name" value = { formData . name } onChange = { e => setFormData ( { ...formData , name : e . target . value } ) } /> }
377+ < input type = "email" placeholder = "Email Address" value = { formData . email } onChange = { e => setFormData ( { ...formData , email : e . target . value } ) } />
378+ < input type = "password" placeholder = "Password" value = { formData . password } onChange = { e => setFormData ( { ...formData , password : e . target . value } ) } />
379+ { isSignUp && < input type = "password" placeholder = "Confirm Password" value = { formData . confirm } onChange = { e => setFormData ( { ...formData , confirm : e . target . value } ) } /> }
413380 { error && < p className = "auth-error" > { error } </ p > }
414381 < button className = "btn-primary auth-btn" onClick = { handleAuth } > { isSignUp ? 'CREATE ACCOUNT' : 'SIGN IN' } </ button >
415382 < div className = "separator" > < span > OR CONTINUE WITH</ span > </ div >
@@ -430,4 +397,4 @@ const Overlay = ({ type, onClose, cartItems, wishlistItems, onRemoveFromCart, on
430397 ) ;
431398} ;
432399
433- export default App
400+ export default App ;
0 commit comments