├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── components │ ├── CartPage.js │ ├── Checkout.js │ ├── Footer.js │ ├── Home.js │ ├── Login.js │ ├── Navbar.js │ ├── NotFound.js │ ├── Paybutton.js │ ├── ProductPage.js │ ├── Register.js │ ├── ShopPage.js │ └── ThemeToggle.js ├── context │ └── ThemeContext.js ├── images │ ├── accesory.png │ ├── activewear.png │ ├── adidas.jpg │ ├── beach.png │ ├── beachwear.png │ ├── blog1.png │ ├── blog2.png │ ├── blog3.png │ ├── bottom.png │ ├── defacto.png │ ├── dress.png │ ├── emptycart.svg │ ├── female.png │ ├── festival.png │ ├── generation.jpg │ ├── hero.png │ ├── hero2.png │ ├── hm.png │ ├── justshoes.jpg │ ├── kay.png │ ├── kc logo.png │ ├── kc wears.png │ ├── kcl.png │ ├── kcy.png │ ├── male.png │ ├── megir.png │ ├── minifocus.png │ ├── sedge.png │ ├── shoe.png │ ├── skmei.png │ ├── top.png │ ├── tshirt.png │ └── zanzea.png ├── index.css ├── index.js ├── layout │ ├── Advert.js │ ├── Banner.js │ ├── Blog.js │ ├── Brands.js │ ├── Category.js │ ├── Deals.js │ ├── FashionInspo.js │ ├── FlashSale.js │ └── Hero.js ├── reportWebVitals.js ├── setupTests.js ├── shopLayout │ ├── BestSelling.js │ ├── InspiredByCart.js │ ├── Recommended.js │ ├── TodayDeals.js │ ├── TopDeals.js │ └── TopDiscount.js └── slices │ ├── api.js │ ├── authSlice.js │ ├── cartSlice.js │ └── cartUiSlice.js └── tailwind.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | View live demo: https://kaycelle-brand.vercel.app 2 | 3 | This E-commerce store was built with React JS and is hosted on vercel. It is a frontend project with a django backend and mongo database. 4 | 5 | I made use of the backend API for fetching all of the product data and for user authentication so users can sign-up and sign-in to their account in order to buy products. 6 | 7 | The backend was built by a friend of mine using django rest framework and mongo database. 8 | Link to the backend repo: https://github.com/KeneNwogu/store 9 | 10 | I made use of redux toolkit for all state management of the website which includes adding products to cart, deleting product from cart, clearing cart and cart checkout. 11 | The project also has an in-built light/dark theme mode which I did using Context API and CSS. 12 | 13 | I also made use of paystack for payment integration 14 | The project was styled with Tailwind CSS 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-store", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@headlessui/react": "^1.6.6", 7 | "@reduxjs/toolkit": "^1.8.3", 8 | "@testing-library/jest-dom": "^5.16.4", 9 | "@testing-library/react": "^13.3.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "axios": "^0.27.2", 12 | "embla-carousel-react": "^7.0.0-rc04", 13 | "jwt-decode": "^3.1.2", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-fast-marquee": "^1.3.2", 17 | "react-icons": "^4.4.0", 18 | "react-loading-skeleton": "^3.1.0", 19 | "react-modal": "^3.15.1", 20 | "react-paystack": "^3.0.5", 21 | "react-redux": "^8.0.2", 22 | "react-router-dom": "^6.3.0", 23 | "react-scripts": "5.0.1", 24 | "react-spinners": "^0.13.3", 25 | "react-toastify": "^9.0.6", 26 | "redux": "^4.2.0", 27 | "tailwind-scrollbar-hide": "^1.1.7", 28 | "web-vitals": "^2.1.4" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "autoprefixer": "^10.4.7", 56 | "postcss": "^8.4.14", 57 | "tailwindcss": "^3.1.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Kaycelle Clothline Store - Online Fashion Store 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import Navbar from "./components/Navbar"; 2 | import { ToastContainer } from "react-toastify"; 3 | import 'react-toastify/dist/ReactToastify.css' 4 | import {Routes, Route} from 'react-router-dom' 5 | import Login from "./components/Login"; 6 | import Register from "./components/Register"; 7 | import Home from "./components/Home"; 8 | import ProductPage from "./components/ProductPage"; 9 | import ShopPage from "./components/ShopPage"; 10 | import NotFound from "./components/NotFound"; 11 | import Checkout from "./components/Checkout"; 12 | 13 | 14 | function App() { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | }/> 21 | }/> 22 | }/> 23 | }/> 24 | }/> 25 | }/> 26 | }/> 27 | 28 | 29 | ); 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /src/components/CartPage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import {MdOutlineKeyboardBackspace, MdRemoveShoppingCart} from 'react-icons/md' 3 | import {BiPlus, BiMinus} from 'react-icons/bi' 4 | import {TbTrashX} from 'react-icons/tb' 5 | import { useSelector } from 'react-redux' 6 | import emptycart from '../images/emptycart.svg' 7 | import { Link } from 'react-router-dom' 8 | import { useDispatch } from 'react-redux' 9 | import { decreaseCart, removeFromCart, addToCart, clearCart, getTotals } from '../slices/cartSlice' 10 | import { cartUiActions } from '../slices/cartUiSlice' 11 | 12 | const CartPage = () => { 13 | const cart = useSelector((state) => state.cart); 14 | const auth = useSelector((state) => state.auth); 15 | const dispatch = useDispatch(); 16 | 17 | useEffect(() => { 18 | dispatch(getTotals()); 19 | }, [cart, dispatch]); 20 | 21 | const toggleCart = () => { 22 | dispatch(cartUiActions.toggle()) 23 | } 24 | 25 | 26 | const handleRemoveFromCart = (cartItem) => { 27 | dispatch(removeFromCart(cartItem)) 28 | 29 | } 30 | 31 | const handleDecreaseCart = (cartItem) => { 32 | dispatch(decreaseCart(cartItem)) 33 | } 34 | 35 | const handleIncreaseCart = (cartItem) => { 36 | dispatch(addToCart(cartItem)) 37 | } 38 | 39 | const handleclearCart = () => { 40 | dispatch(clearCart()) 41 | } 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |

Cart

49 | 50 |
handleclearCart()}> 51 | Clear {''} 52 |
53 |
54 | 55 | {cart.cartItems.length === 0 ? ( 56 |
57 | / 58 |
Your Cart is currently Empty
59 |
60 | 61 | Start Shopping 62 | 63 |
64 |
65 | 66 | ) :( 67 |
68 |
69 | {cart.cartItems?.map(cartItem => ( 70 |
71 |
72 | {cartItem.title} 73 |
74 | 75 |
76 |

{cartItem.brand.toUpperCase()}

77 |

{cartItem.currency + " " + (cartItem.price * cartItem.cartQuantity).toLocaleString()}

78 |

handleRemoveFromCart(cartItem)}>Remove

79 |
80 | 81 |
82 |
handleDecreaseCart(cartItem)}>
83 |

{cartItem.cartQuantity}

84 |
handleIncreaseCart(cartItem)}>
85 |
86 |
87 | ))} 88 |
89 | 90 |
91 |
92 |

Sub Total

93 |

NGN {cart.cartTotalAmount.toLocaleString()}

94 |
95 |
96 |

Shipping

97 |

Free

98 |
99 | 100 |
101 | 102 |
103 |

Total

104 |

NGN {cart.cartTotalAmount.toLocaleString()}

105 |
106 | 107 | {auth._id ? 108 | (
109 | Proceed to Checkout 110 |
) 111 | : ( 112 |
Proceed to Checkout
113 | )} 114 | 115 |
116 |
117 | )} 118 | 119 |
120 |
121 | ) 122 | } 123 | 124 | export default CartPage -------------------------------------------------------------------------------- /src/components/Checkout.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import axios from 'axios' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import { useSelector } from 'react-redux' 5 | import { useDispatch } from 'react-redux' 6 | import { clearCart, getTotals } from '../slices/cartSlice' 7 | import { Dialog, Transition } from "@headlessui/react"; 8 | import { Fragment } from "react"; 9 | import {AiOutlineClose} from 'react-icons/ai' 10 | import { ClipLoader } from 'react-spinners' 11 | import { usePaystackPayment } from 'react-paystack'; 12 | 13 | 14 | 15 | const Checkout = () => { 16 | const cart = useSelector((state) => state.cart); 17 | const auth = useSelector((state) => state.auth); 18 | const dispatch = useDispatch(); 19 | const navigate = useNavigate(); 20 | 21 | localStorage.getItem('cartItems') 22 | 23 | useEffect(() => { 24 | dispatch(getTotals()); 25 | }, [cart, dispatch]); 26 | 27 | const truncateString = (str, num) => { 28 | if (str?.length > num) { 29 | return str.slice(0, num) + '...'; 30 | } else{ 31 | return str 32 | } 33 | } 34 | 35 | const initialValue = { 36 | first_name: '', 37 | last_name: '', 38 | email: '', 39 | phone: '', 40 | state: '', 41 | address: '', 42 | country: '', 43 | city:'', 44 | }; 45 | 46 | const [user, setUser] = useState(initialValue) 47 | const [errors, setErrors] = useState({}) 48 | const [isSubmit, setIsSubmit] = useState(false) 49 | const [isOpen, setIsOpen] = useState(false); 50 | const [modal, setModal] = useState([]); 51 | const [loading, setLoading] = useState(false); 52 | 53 | const handleChange = (e) => { 54 | const { name, value} = e.target; 55 | setUser({...user, [name]: value}) 56 | // console.log(user); 57 | } 58 | 59 | const validate = (values) => { 60 | const errorss = {}; 61 | if (!values.first_name) { 62 | errorss.first_name = 'First Name is Required!' 63 | } 64 | if (!values.last_name) { 65 | errorss.last_name = 'Last Name is Required!' 66 | } 67 | if (!values.country) { 68 | errorss.country = 'Country is Required!' 69 | } 70 | if (!values.state) { 71 | errorss.state = 'State is Required!' 72 | } 73 | if (!values.address) { 74 | errorss.address = 'Street Address is Required!' 75 | } 76 | if (!values.city) { 77 | errorss.city = 'City is Required!' 78 | } 79 | if (!values.phone) { 80 | errorss.phone = 'Phone Number is Required!' 81 | } 82 | if (!values.email) { 83 | errorss.email = 'Email Address is Required!' 84 | } 85 | 86 | return errorss; 87 | } 88 | 89 | useEffect(() => { 90 | // console.log(errors); 91 | if (Object.keys(errors).length === 0 && isSubmit) { 92 | // console.log(user); 93 | } 94 | }, [errors]) 95 | 96 | function closeModal() { 97 | setIsOpen(false); 98 | } 99 | 100 | const handleCheckout = async (e) => { 101 | e.preventDefault(); 102 | setErrors(validate(user)); 103 | setIsSubmit(true); 104 | try{ 105 | await axios 106 | .post( 107 | "https://gorana.onrender.com/orders/", 108 | { 109 | orders: cart.cartItems.map((cartItem) => { 110 | return { 111 | product_id: cartItem._id, 112 | quantity: cartItem.cartQuantity, 113 | size: cartItem.brand, 114 | }; 115 | }), 116 | state: user.state, 117 | address: user.address, 118 | }, 119 | { 120 | headers: { 121 | Authorization: `${auth.token}`, 122 | }, 123 | }, 124 | setLoading(true) 125 | ) 126 | .then((response) => { 127 | console.log(response.data); 128 | if (response.data.success) { 129 | setIsOpen(true); 130 | setLoading(false); 131 | } 132 | setModal(response.data); 133 | }); 134 | } 135 | catch(error) { 136 | console.log(error.response.data); 137 | } 138 | } 139 | 140 | const handleclearCart = () => { 141 | dispatch(clearCart()) 142 | } 143 | 144 | const config = { 145 | reference: modal.transaction_reference, 146 | amount: modal.price * 100, 147 | publicKey: 'pk_test_ec28501e234f6e2d802dc2a156c2511abd2d0527', 148 | email: user.email, 149 | }; 150 | 151 | // you can call this function anything 152 | const onSuccess = (response) => { 153 | // Implementation for whatever you want to do after success call. 154 | handleclearCart(); 155 | navigate('/') 156 | }; 157 | 158 | // you can call this function anything 159 | const onClose = () => { 160 | // implementation for whatever you want to do when the Paystack dialog closed. 161 | navigate('/checkout') 162 | } 163 | 164 | const Paystack = () => { 165 | const initializePayment = usePaystackPayment(config); 166 | return ( 167 |
168 | 171 |
172 | ); 173 | }; 174 | 175 | 176 | return ( 177 |
178 |
179 |
180 |
181 |

182 | Home / Shop / Checkout 183 |

184 |
185 |
186 |

Have a coupon? Click here to enter your code

187 |
188 | 189 |
190 |
191 |

Billing Details

192 |
193 |
194 |
195 |
196 | 197 |
198 | 206 |
207 |

{errors.first_name}

208 |
209 | 210 |
211 | 212 |
213 | 222 |
223 |

{errors.last_name}

224 |
225 |
226 |
227 | 228 |
229 | 234 |
235 |
236 |
237 | 238 |
239 | 249 |
250 |

{errors.country}

251 |
252 |
253 | 254 |
255 | 264 |
265 |

{errors.address}

266 | 267 |
268 | 274 |
275 |
276 |
277 |
278 | 279 |
280 | 287 |
288 |

{errors.city}

289 |
290 | 291 |
292 | 293 |
294 | 301 |
302 |

{errors.state}

303 |
304 |
305 |
306 |
307 | 308 |
309 | 316 |
317 |

{errors.phone}

318 |
319 | 320 |
321 | 322 |
323 | 328 |
329 |
330 |
331 |
332 | 333 |
334 | 342 |
343 |

{errors.email}

344 |
345 |
346 | 347 | 352 |
353 | 354 |
355 | 356 |
357 |
358 |
359 |
360 |

Your Order

361 |
362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | {cart.cartItems.map(cartItem => ( 373 | 374 | 381 | 382 | 383 | ))} 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 |
ProductTotal (NGN)
375 | {cartItem.title} 376 |
377 |
{truncateString(cartItem.name, 20)}
378 |
Qty: {cartItem.cartQuantity}
379 |
380 |
{(cartItem.price * cartItem.cartQuantity).toLocaleString()}
Sub Total: {cart.cartTotalAmount.toLocaleString()}
Shipping Fee: Free
Total: {cart.cartTotalAmount.toLocaleString()}
399 | 400 |
{loading ? : 'Place Order'}
401 |
402 |
403 |
404 |
405 |
406 | 407 | 408 | 413 |
414 | 423 | 424 | 425 | 426 | {/* This element is to trick the browser into centering the modal contents. */} 427 | 433 | 442 |
443 | 447 | Success 448 | 449 |
450 |

451 | Successfully created order 452 |

453 |
454 |
455 |

456 | Your order: {modal.transaction_reference} has been placed successfully 457 |

458 |
459 | 460 |
461 | 462 |
463 | 464 |
465 | 472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 | ) 480 | } 481 | 482 | export default Checkout -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import logo from '../images/kcl.png' 4 | import ThemeToggle from './ThemeToggle' 5 | import { AiOutlineInstagram } from 'react-icons/ai' 6 | import { FaFacebookF, FaGithub, FaTwitter } from 'react-icons/fa' 7 | 8 | 9 | const Footer = () => { 10 | return ( 11 |
12 |
13 | 14 | / 15 | 16 | 17 |
18 |
19 |

Subscribe to Our Newsletters

20 |
21 |
22 | 23 | 24 |
25 |
26 |

Contact Info

27 |

17 Princess Road, London, Greater London NW1 8JR, UK

28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 |
37 |

Categories

38 |
    39 |
  • Dresses
  • 40 |
  • Tops
  • 41 |
  • Bottoms
  • 42 |
  • Beachwears
  • 43 |
44 |
45 | 46 |
47 |

Customer Care

48 |
    49 |
  • My account
  • 50 |
  • Cart
  • 51 |
  • Order History
  • 52 |
  • Order Tracking
  • 53 |
54 |
55 | 56 |
57 |

Pages

58 |
    59 |
  • Blog
  • 60 |
  • Browse the shop
  • 61 |
  • Categories
  • 62 |
  • Pre-built pages
  • 63 |
64 |
65 | 66 |
67 | 68 |
69 |
70 |
71 |
©2022. Powered by Kcee Api. Built by Hilary
72 |
73 | ) 74 | } 75 | 76 | export default Footer -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Marquee from 'react-fast-marquee' 3 | import Hero from '../layout/Hero' 4 | import Category from '../layout/Category' 5 | import FlashSale from '../layout/FlashSale' 6 | import Advert from '../layout/Advert' 7 | import Deals from '../layout/Deals' 8 | import FashionInspo from '../layout/FashionInspo' 9 | import Brands from '../layout/Brands' 10 | import Banner from '../layout/Banner' 11 | import Blog from '../layout/Blog' 12 | import Footer from './Footer' 13 | 14 | const Home = ({rowID}) => { 15 | return ( 16 |
17 | 18 |
19 |
What's New
20 |
Flash Sale
21 |
Dresses
22 |
Top
23 |
Lingerie & Lounge
24 |
Beachwear
25 |
Active Wear
26 |
Summer Sale
27 |
Top Brands
28 |
29 |
30 | 31 |
32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 | 41 |
42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 | ) 50 | } 51 | 52 | export default Home -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {AiOutlineMail, AiFillEyeInvisible, AiFillEye, AiOutlineClose} from 'react-icons/ai' 3 | import { Link } from 'react-router-dom' 4 | import { useState, useEffect } from 'react' 5 | import { useDispatch, useSelector } from 'react-redux' 6 | import { useNavigate } from 'react-router-dom' 7 | import { loginUser } from '../slices/authSlice' 8 | import { ClipLoader } from 'react-spinners' 9 | import { toast } from 'react-toastify'; 10 | 11 | 12 | const Login = () => { 13 | const dispatch = useDispatch(); 14 | const auth = useSelector((state) => state.auth); 15 | 16 | console.log(auth); 17 | 18 | const navigate = useNavigate() 19 | 20 | useEffect(() => { 21 | if (auth._id && auth.first_name){ 22 | navigate('/') 23 | toast.warning(`Welcome, ${auth.first_name}`, {position: 'bottom-left'}) 24 | } 25 | }, [auth._id, auth.first_name, navigate]) 26 | 27 | const [user, setUser] = useState({ 28 | email: '', 29 | password: '', 30 | }); 31 | 32 | const handleSubmit = (e) => { 33 | e.preventDefault() 34 | 35 | dispatch(loginUser(user)) 36 | } 37 | 38 | const [passwordEye, setPasswordEye] = useState(false); 39 | 40 | const handlePasswordEye = () => { 41 | setPasswordEye(!passwordEye) 42 | } 43 | return ( 44 |
45 |
46 |
47 |

Log In

48 |
49 |
50 | 51 |
52 | setUser({...user, email: e.target.value})} 58 | /> 59 | 60 |
61 |
62 | 63 |
64 | 65 |
66 | setUser({...user, password: e.target.value})} 72 | 73 | /> 74 |
75 | {(passwordEye === false) ? : } 76 |
77 |
78 |
79 | 80 |
81 |

Remember Me

82 |

forgot password?

83 |
84 | 85 | 88 | {auth.loginStatus === 'rejected' ?

{auth.loginError}

: null} 89 |
90 |

Don't have an account? Create Account

91 |
92 | 93 | 94 | 95 |
96 |
97 |
98 |
99 | ) 100 | } 101 | 102 | export default Login -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment, useEffect } from 'react' 2 | import {Link, useNavigate} from 'react-router-dom' 3 | import { AiOutlineClose, AiOutlineMenu, AiOutlineInstagram } from 'react-icons/ai'; 4 | import ThemeToggle from './ThemeToggle'; 5 | import {HiShoppingCart, HiSearch, HiUser, HiChevronDown} from 'react-icons/hi' 6 | import { Menu, Transition } from '@headlessui/react' 7 | import { FaGithub, FaRegHeart, FaFacebookF, FaTwitter } from 'react-icons/fa'; 8 | import {SiGnuprivacyguard} from 'react-icons/si' 9 | import {GoSignIn} from 'react-icons/go' 10 | import logo from '../images/kcl.png' 11 | import CartPage from './CartPage'; 12 | import { useSelector } from 'react-redux'; 13 | import { useDispatch } from 'react-redux' 14 | import { getTotals } from '../slices/cartSlice' 15 | import { logoutUser } from '../slices/authSlice'; 16 | import { toast } from 'react-toastify'; 17 | import {ImUserCheck} from 'react-icons/im' 18 | import {GoSignOut} from 'react-icons/go' 19 | import {RiCoupon3Fill} from 'react-icons/ri' 20 | import { cartUiActions } from '../slices/cartUiSlice'; 21 | 22 | 23 | 24 | const Navbar = () => { 25 | const [nav, setNav] = useState(false); 26 | const [navBg, setNavBg] = useState(false); 27 | const [carts, setCarts] = useState(false); 28 | 29 | const { cartTotalQuantity } = useSelector((state) => state.cart); 30 | const auth = useSelector((state) => state.auth); 31 | const cart = useSelector((state) => state.cart); 32 | const dispatch = useDispatch(); 33 | const navigate = useNavigate() 34 | 35 | useEffect(() => { 36 | dispatch(getTotals()); 37 | }, [cart, dispatch]); 38 | 39 | const handleNav = () => { 40 | setNav(!nav); 41 | }; 42 | 43 | const handleCart = () => { 44 | setCarts(!carts); 45 | }; 46 | 47 | const toggleCart = () => { 48 | dispatch(cartUiActions.toggle()); 49 | }; 50 | 51 | const showCart = useSelector(state => state.cartUi.cartIsVisible); 52 | 53 | const changeNavbg = () => { 54 | if (window.scrollY >= 90) { 55 | setNavBg(true); 56 | } else { 57 | setNavBg(false); 58 | } 59 | }; 60 | window.addEventListener('scroll', changeNavbg); 61 | 62 | 63 | return ( 64 |
70 |
71 | 72 | / 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 |
Search
81 |
82 | 83 |
84 |
    85 |
    86 | {auth._id && auth.first_name ? 87 | ( 88 |
    89 |

    Hi, {auth.first_name}!

    90 |
    91 | 92 |
    93 | 94 | 95 | 96 |
    97 | 98 | 107 | 108 |
    109 | 110 | {({ active }) => ( 111 | 120 | Saved Items 121 | 122 | )} 123 | 124 | 125 | 126 | {({ active }) => ( 127 |

    134 | Apply Coupon 135 |

    136 | )} 137 |
    138 | 139 | 140 | {({ active }) => ( 141 |

    { 147 | dispatch(logoutUser(null)) 148 | toast.warning('Logged out!', {position: 'bottom-left'}) 149 | navigate('/login') 150 | }} 151 | > 152 | Logout 153 |

    154 | )} 155 |
    156 |
    157 |
    158 |
    159 |
    160 |
    161 | 162 |
    163 | ) 164 | : 165 | ( 166 |
    167 |

    Account

    168 |
    169 | 170 |
    171 | 172 | 173 | 174 |
    175 | 176 | 185 | 186 |
    187 | 188 | {({ active }) => ( 189 | 198 | Login 199 | 200 | 201 | 202 | )} 203 | 204 | 205 | 206 | {({ active }) => ( 207 | 216 | Create Account 217 | 218 | )} 219 | 220 | 221 | 222 | {({ active }) => ( 223 | 232 | Saved Items 233 | 234 | )} 235 | 236 |
    237 |
    238 |
    239 |
    240 |
    241 |
    242 | ) 243 | } 244 | 245 |
    246 | 247 | 248 |
  • 249 | Cart 250 | {cartTotalQuantity > 0 ? ( 251 |
    252 |

    {cartTotalQuantity}

    253 |
    254 | ): ''} 255 |
  • 256 |
  • 257 | 258 |
  • 259 |
260 | 261 |
262 | {/* Cart icon */} 263 |
264 | 265 | {cartTotalQuantity > 0 ? ( 266 |
267 |

{cartTotalQuantity}

268 |
269 | ): ''} 270 |
271 | {/* Hamburger Icon */} 272 |
276 | 277 |
278 |
279 |
280 |
281 | 282 | 283 | 284 | {/* Mobile Menu */} 285 | {/* Overlay */} 286 |
288 |
292 |
293 |
294 | 295 | / setNav(false)}/> 296 | 297 | 298 |
302 | 303 |
304 |
305 | 306 |
307 |

308 | Your Online Shopping Store 309 |

310 |
311 | 312 |
313 | {auth._id && auth.first_name ? 314 | ( 315 |
316 |

317 | Hi, {auth.first_name}! 318 |

319 |
    320 | 321 |
  • setNav(false)}> 322 | Home 323 |
  • 324 | 325 | 326 |
  • setNav(false)}> 327 | Shop 328 |
  • 329 | 330 |
  • { 332 | dispatch(logoutUser(null)) 333 | toast.warning('Logged out!', {position: 'bottom-left'}) 334 | navigate('/login') 335 | }} 336 | > 337 | Logout 338 |
  • 339 |
  • 340 | Light/Dark Mode 341 |
  • 342 |
343 |
344 | ) : 345 | ( 346 |
347 |
    348 | 349 |
  • setNav(false)}> 350 | Home 351 |
  • 352 | 353 | 354 |
  • setNav(false)}> 355 | Shop 356 |
  • 357 | 358 | 359 |
  • setNav(false)}> 360 | Login 361 |
  • 362 | 363 | 364 |
  • setNav(false)}> 365 | Create Account 366 |
  • 367 | 368 |
  • 369 | Light/Dark Mode 370 |
  • 371 |
372 |
373 | )} 374 |
375 | 376 |
377 |

378 | Connect With Us 379 |

380 | 381 |
382 | 383 |
384 | 385 |
386 | 387 | 388 | 389 |
390 | 391 |
392 | 393 | 394 | 395 |
396 | 397 |
398 | 399 | 400 | 401 |
402 | 403 |
404 | 405 |
406 |
407 |
408 |
409 |
410 | 411 | {showCart && } 412 |
413 | ) 414 | } 415 | 416 | export default Navbar -------------------------------------------------------------------------------- /src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import Footer from './Footer' 4 | 5 | const NotFound = () => { 6 | return ( 7 |
8 |
9 |
10 |
11 |

404 ERROR

12 |

Page not found

13 |

To return to the Home page

14 |

Click Here

15 |
16 |
17 |
18 |
19 |
20 | ) 21 | } 22 | 23 | export default NotFound -------------------------------------------------------------------------------- /src/components/Paybutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Checkout from './Checkout'; 3 | import { useNavigate } from 'react-router-dom'; 4 | 5 | const Paybutton = ({cartItems}) => { 6 | const navigate = useNavigate(); 7 | 8 | const handleCheckout = () => { 9 | console.log(cartItems); 10 | navigate('/checkout') 11 | } 12 | return ( 13 |
14 | 15 |
16 | ) 17 | } 18 | 19 | export default Paybutton -------------------------------------------------------------------------------- /src/components/ProductPage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import {useParams} from 'react-router-dom' 3 | import axios from 'axios' 4 | import {Link} from 'react-router-dom' 5 | import FadeLoader from "react-spinners/FadeLoader"; 6 | import { useDispatch } from 'react-redux'; 7 | import { addToCart } from '../slices/cartSlice'; 8 | 9 | 10 | const ProductPage = (rowID) => { 11 | const [product, setProduct] = useState([]) 12 | const {productId} = useParams() 13 | const [loading, setLoading] = useState(false); 14 | 15 | const dispatch = useDispatch() 16 | 17 | const handleAddToCart = (product) => { 18 | dispatch(addToCart(product)) 19 | } 20 | 21 | const url = `https://gorana.onrender.com/products/${productId}`; 22 | 23 | useEffect(() => { 24 | const getProduct = async () => { 25 | setLoading(true) 26 | axios.get(url).then((response) => { 27 | setProduct(response.data) 28 | console.log(response.data); 29 | setLoading(false) 30 | }) 31 | } 32 | getProduct(); 33 | }, [url]); 34 | 35 | const [imgIndex, setImgIndex] = useState(0) 36 | 37 | const Loading = () => { 38 | return ( 39 |
40 | 41 |

Loading Products...

42 |
43 | ) 44 | } 45 | 46 | const ShowProduct = () => { 47 | return ( 48 |
49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 | {product._id} setImgIndex(0)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' /> 58 | {product._id} setImgIndex(1)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' /> 59 | {product._id} setImgIndex(2)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' /> 60 | {product._id} setImgIndex(3)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' /> 61 |
62 |
63 |
64 | 65 |
66 |
67 |

Home / {product.brand}

68 |
69 |
70 |

{product.name}

71 |
72 |
73 | {product.currency + " " + product.price?.toLocaleString()} 74 |
75 | 76 |
77 |
78 |
handleAddToCart(product)}>Add to Cart
79 |
80 | {/*
81 |
Save to wishlist
82 |
*/} 83 |
84 | 85 |
86 |

Product Detials

87 |

{product.description}

88 |
89 |
90 |
91 |
92 | 93 | ) 94 | } 95 | 96 | 97 | return ( 98 |
99 |
100 |
{loading ? : }
101 |
102 |
103 | 104 | ) 105 | } 106 | 107 | export default ProductPage -------------------------------------------------------------------------------- /src/components/Register.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import {Link, useNavigate } from 'react-router-dom' 3 | import {AiOutlineMail, AiFillEyeInvisible, AiFillEye} from 'react-icons/ai' 4 | import { useDispatch } from 'react-redux' 5 | import { registerUser } from '../slices/authSlice' 6 | import { useSelector } from 'react-redux' 7 | import ClipLoader from "react-spinners/ClipLoader"; 8 | 9 | 10 | const Register = () => { 11 | const initialValue = { 12 | first_name: '', 13 | last_name: '', 14 | email: '', 15 | phone: '', 16 | password: '', 17 | password2: '', 18 | }; 19 | const [user, setUser] = useState(initialValue) 20 | const [errors, setErrors] = useState({}) 21 | const [isSubmit, setIsSubmit] = useState(false) 22 | 23 | const handleChange = (e) => { 24 | const { name, value} = e.target; 25 | setUser({...user, [name]: value}) 26 | console.log(user); 27 | } 28 | 29 | const validate = (values) => { 30 | const errorss = {}; 31 | const regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/ 32 | if (!values.first_name) { 33 | errorss.first_name = 'First Name is Required!' 34 | } 35 | if (!values.email) { 36 | errorss.email = 'Email Address is Required!' 37 | }else if (!regex.test(values.email)) { 38 | errorss.email = 'This is not a valid email address' 39 | } 40 | if (!values.password) { 41 | errorss.password = 'Password is Required!' 42 | }else if (values.password.length < 8) { 43 | errorss.password = 'Password must be 8 or more than 8 characters' 44 | }else if (values.password.length > 12) { 45 | errorss.password = 'Password cannot exceed more than 12 characters' 46 | } 47 | if (!values.password2) { 48 | errorss.password2 = 'Please Confirm Password!' 49 | }else if (values.password2 !== values.password) { 50 | errorss.password2 = 'Password does not match' 51 | } 52 | 53 | return errorss; 54 | } 55 | 56 | useEffect(() => { 57 | console.log(errors); 58 | if (Object.keys(errors).length === 0 && isSubmit) { 59 | console.log(user); 60 | } 61 | }, [errors]) 62 | 63 | const dispatch = useDispatch(); 64 | const auth = useSelector((state) => state.auth); 65 | 66 | console.log(auth); 67 | 68 | const navigate = useNavigate() 69 | 70 | useEffect(() => { 71 | if (auth.success){ 72 | navigate('/login') 73 | } 74 | }, [auth.success, navigate ]) 75 | 76 | const handleSubmit = (e) => { 77 | e.preventDefault(); 78 | setErrors(validate(user)); 79 | setIsSubmit(true); 80 | dispatch(registerUser(user)); 81 | } 82 | 83 | const [passwordEye, setPasswordEye] = useState(false); 84 | const [confirmPasswordEye, setConfirmPasswordEye] = useState(false); 85 | 86 | const handlePasswordEye = () => { 87 | setPasswordEye(!passwordEye) 88 | } 89 | 90 | const handleConfirmPasswordEye = () => { 91 | setConfirmPasswordEye(!confirmPasswordEye) 92 | } 93 | return ( 94 |
95 |
96 |
97 |

Create Account

98 |
99 |
100 |
101 | 102 |
103 | 111 |
112 |

{errors.first_name}

113 |
114 | 115 |
116 | 117 |
118 | 126 |
127 |
128 |
129 | 130 |
131 |
132 | 133 |
134 | 142 |
143 |
144 | 145 |
146 | 147 |
148 | 156 | 157 |
158 |

{errors.email}

159 |
160 | 161 |
162 | 163 |
164 |
165 | 166 |
167 | 175 |
176 | {(passwordEye === false) ? : } 177 |
178 |
179 |

{errors.password}

180 |
181 | 182 | 183 |
184 | 185 |
186 | 194 |
195 | {(confirmPasswordEye === false) ? : } 196 |
197 |
198 |

{errors.password2}

199 |
200 | 201 |
202 | 203 |

By signing up you accept our terms and conditions & privacy policy

204 | 205 | 206 | 207 | {auth.registerStatus === 'rejected' ?

{auth.registerError}

: null} 208 |
209 | 210 |

Already have an account? Login

211 |
212 |
213 |
214 | ) 215 | } 216 | 217 | export default Register -------------------------------------------------------------------------------- /src/components/ShopPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import BestSelling from '../shopLayout/BestSelling' 4 | import InspiredByCart from '../shopLayout/InspiredByCart' 5 | import Recommended from '../shopLayout/Recommended' 6 | import TodayDeals from '../shopLayout/TodayDeals' 7 | import TopDeals from '../shopLayout/TopDeals' 8 | import TopDiscount from '../shopLayout/TopDiscount' 9 | import Brands from '../layout/Brands' 10 | import Footer from './Footer' 11 | 12 | const ShopPage = () => { 13 | return ( 14 |
15 | 16 |
17 |
18 |
19 |

20 | Home / Fashion Store 21 |

22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 | ) 37 | } 38 | 39 | export default ShopPage -------------------------------------------------------------------------------- /src/components/ThemeToggle.js: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react' 2 | import {HiSun, HiMoon} from 'react-icons/hi' 3 | import { ThemeContext } from '../context/ThemeContext' 4 | 5 | const ThemeToggle = () => { 6 | const {theme, setTheme} = useContext(ThemeContext) 7 | return ( 8 |
9 | {theme === 'dark' ? ( 10 |
setTheme(theme === 'dark' ? 'light' : 'dark')}> 11 | 12 |
13 | ) : (
setTheme(theme === 'dark' ? 'light' : 'dark')}> 14 | 15 |
)} 16 |
17 | ) 18 | } 19 | 20 | export default ThemeToggle -------------------------------------------------------------------------------- /src/context/ThemeContext.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect, createContext} from "react"; 2 | 3 | const getInitialTheme = () => { 4 | if (typeof window !== 'undefined' && window.localStorage){ 5 | const storedPrefs = window.localStorage.getItem('color-theme') 6 | if (typeof storedPrefs === 'string') { 7 | return storedPrefs 8 | } 9 | 10 | const userMedia = window.matchMedia('(prefers-color-scheme: dark)') 11 | if (userMedia.matches) { 12 | return 'dark' 13 | } 14 | } 15 | return 'light' 16 | } 17 | 18 | export const ThemeContext = createContext() 19 | 20 | export const ThemeProvider = ({initialTheme, children}) => { 21 | const [theme, setTheme] = useState(getInitialTheme) 22 | 23 | const rawSetTheme = (theme) => { 24 | const root = window.document.documentElement; 25 | const isDark = theme === 'dark' 26 | 27 | root.classList.remove(isDark ? 'light' : 'dark') 28 | root.classList.add(theme) 29 | 30 | localStorage.setItem('color-theme', theme) 31 | } 32 | 33 | if (initialTheme) { 34 | rawSetTheme(initialTheme) 35 | } 36 | 37 | useEffect(()=> { 38 | rawSetTheme(theme) 39 | },[theme]) 40 | 41 | return ( 42 | 43 | {children} 44 | 45 | ) 46 | } -------------------------------------------------------------------------------- /src/images/accesory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/accesory.png -------------------------------------------------------------------------------- /src/images/activewear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/activewear.png -------------------------------------------------------------------------------- /src/images/adidas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/adidas.jpg -------------------------------------------------------------------------------- /src/images/beach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/beach.png -------------------------------------------------------------------------------- /src/images/beachwear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/beachwear.png -------------------------------------------------------------------------------- /src/images/blog1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/blog1.png -------------------------------------------------------------------------------- /src/images/blog2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/blog2.png -------------------------------------------------------------------------------- /src/images/blog3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/blog3.png -------------------------------------------------------------------------------- /src/images/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/bottom.png -------------------------------------------------------------------------------- /src/images/defacto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/defacto.png -------------------------------------------------------------------------------- /src/images/dress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/dress.png -------------------------------------------------------------------------------- /src/images/emptycart.svg: -------------------------------------------------------------------------------- 1 | empty_cart -------------------------------------------------------------------------------- /src/images/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/female.png -------------------------------------------------------------------------------- /src/images/festival.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/festival.png -------------------------------------------------------------------------------- /src/images/generation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/generation.jpg -------------------------------------------------------------------------------- /src/images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/hero.png -------------------------------------------------------------------------------- /src/images/hero2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/hero2.png -------------------------------------------------------------------------------- /src/images/hm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/hm.png -------------------------------------------------------------------------------- /src/images/justshoes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/justshoes.jpg -------------------------------------------------------------------------------- /src/images/kay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/kay.png -------------------------------------------------------------------------------- /src/images/kc logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/kc logo.png -------------------------------------------------------------------------------- /src/images/kc wears.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/kc wears.png -------------------------------------------------------------------------------- /src/images/kcl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/kcl.png -------------------------------------------------------------------------------- /src/images/kcy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/kcy.png -------------------------------------------------------------------------------- /src/images/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/male.png -------------------------------------------------------------------------------- /src/images/megir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/megir.png -------------------------------------------------------------------------------- /src/images/minifocus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/minifocus.png -------------------------------------------------------------------------------- /src/images/sedge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/sedge.png -------------------------------------------------------------------------------- /src/images/shoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/shoe.png -------------------------------------------------------------------------------- /src/images/skmei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/skmei.png -------------------------------------------------------------------------------- /src/images/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/top.png -------------------------------------------------------------------------------- /src/images/tshirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/tshirt.png -------------------------------------------------------------------------------- /src/images/zanzea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hilary-Okemeziem/Ecommerce-web-reactjs/8ff8fe06d8082085422b67df95913d436f7877a9/src/images/zanzea.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800;900&display=swap'); 6 | 7 | @layer base{ 8 | body{ 9 | @apply font-[Montserrat]; 10 | } 11 | } 12 | 13 | :root { 14 | @apply bg-primary text-primary 15 | } 16 | 17 | .light { 18 | --color-bg-primary: #ffffff; 19 | --color-bg-secondary: #edf2f7; 20 | --color-text-primary: #2d3748; 21 | --color-text-secondary: #4a5568; 22 | --color-text-accent: #2b6cb0; 23 | --color-bg-input: #edf2f7; 24 | --color-bg-button: #644726; 25 | } 26 | 27 | .dark { 28 | --color-bg-primary: #121212; 29 | --color-bg-secondary: #283141; 30 | --color-text-primary: #dbdbdb; 31 | --color-text-secondary: #e2e8f0; 32 | --color-text-accent: #81e6d9; 33 | --color-bg-input: #4a5361; 34 | --color-bg-button: #644726; 35 | } 36 | 37 | .hero-section { 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | background-image: url("./images/hero2.png"); 42 | background-position: right; 43 | background-size: contain; 44 | background-repeat: no-repeat; 45 | height: 80vh; 46 | } 47 | 48 | .heading:after{ 49 | width: 60px; 50 | height: 5px; 51 | background-color: #986c55; 52 | display: block; 53 | margin: auto; 54 | margin-top: 0.5rem; 55 | content: ""; 56 | } 57 | 58 | .advert-img{ 59 | background-image: url('./images/beach.png'); 60 | } 61 | 62 | .banner-img{ 63 | background-image: url('./images/festival.png'); 64 | } 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { BrowserRouter } from 'react-router-dom'; 7 | import { Provider } from 'react-redux'; 8 | // import store from './redux/store'; 9 | import { configureStore } from '@reduxjs/toolkit'; 10 | import cartReducer, { getTotals } from './slices/cartSlice'; 11 | import { ThemeProvider } from './context/ThemeContext'; 12 | import authReducer, { loadUser } from './slices/authSlice'; 13 | import cartUiSlice from './slices/cartUiSlice'; 14 | 15 | const store = configureStore({ 16 | reducer:{ 17 | cart: cartReducer, 18 | auth: authReducer, 19 | cartUi: cartUiSlice.reducer, 20 | } 21 | }); 22 | 23 | store.dispatch(getTotals()); 24 | store.dispatch(loadUser(null)); 25 | 26 | const root = ReactDOM.createRoot(document.getElementById('root')); 27 | root.render( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | 37 | // If you want to start measuring performance in your app, pass a function 38 | // to log results (for example: reportWebVitals(console.log)) 39 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 40 | reportWebVitals(); 41 | -------------------------------------------------------------------------------- /src/layout/Advert.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {HiArrowNarrowRight} from 'react-icons/hi' 3 | import { Link } from 'react-router-dom' 4 | import beach from '../images/beach.png' 5 | 6 | const Advert = () => { 7 | return ( 8 |
9 |
10 |
11 |
12 |

Get ready for the beach

13 |

50% off vacation and beach wears. Offer available while stock lasts

14 |
15 |
16 | 17 | 20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 | / 28 | 29 |
30 |
31 |

Get ready for the beach

32 |

50% off vacation and beach wears. Offer available while stock lasts

33 |
34 |
35 | 36 | 39 | 40 |
41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export default Advert -------------------------------------------------------------------------------- /src/layout/Banner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {HiArrowNarrowRight} from 'react-icons/hi' 3 | import { Link } from 'react-router-dom' 4 | import festival from '../images/festival.png' 5 | 6 | const Banner = () => { 7 | return ( 8 |
9 |
10 |
11 |
12 |

Festivals x Concerts

13 |

60% off festival and concert wears. Offer available while stock lasts

14 |
15 |
16 | 17 | 20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 | / 28 | 29 |
30 |
31 |

Festivals x Concerts

32 |

60% off festival and concert wears. Offer available while stock lasts

33 |
34 |
35 | 36 | 39 | 40 |
41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export default Banner -------------------------------------------------------------------------------- /src/layout/Blog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { HiArrowNarrowRight } from 'react-icons/hi' 3 | import { Link } from 'react-router-dom' 4 | import blog1 from '../images/blog1.png' 5 | import blog2 from '../images/blog2.png' 6 | import blog3 from '../images/blog3.png' 7 | 8 | const Blog = () => { 9 | return ( 10 |
11 |
12 |

Catch Up On The Blog

13 |
14 |
15 |
16 | / 17 |

Budget summer wardrobe guide

18 |
19 | Read More 20 |
21 |
22 | 23 |
24 | / 25 |

Where fashion gets easy

26 |
27 | Read More 28 |
29 |
30 | 31 |
32 | / 33 |

Brighten up for summer looks

34 |
35 | Read More 36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | export default Blog -------------------------------------------------------------------------------- /src/layout/Brands.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import adidas from '../images/adidas.jpg' 3 | import defacto from '../images/defacto.png' 4 | import generation from '../images/generation.jpg' 5 | import hm from '../images/hm.png' 6 | import justshoes from '../images/justshoes.jpg' 7 | import megir from '../images/megir.png' 8 | import minifocus from '../images/minifocus.png' 9 | import sedge from '../images/sedge.png' 10 | import skmei from '../images/skmei.png' 11 | import zanzea from '../images/zanzea.png' 12 | import Marquee from 'react-fast-marquee' 13 | 14 | 15 | const Brands = () => { 16 | return ( 17 |
18 |
19 |

Fashion Brands

20 |
21 | 22 | 23 |
/
24 |
/
25 |
/
26 |
/
27 |
/
28 |
/
29 |
/
30 |
/
31 |
/
32 |
/
33 |
34 |
35 | ) 36 | } 37 | 38 | export default Brands -------------------------------------------------------------------------------- /src/layout/Category.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import dress from '../images/dress.png' 3 | import beachwear from '../images/beachwear.png' 4 | import top from '../images/top.png' 5 | import shoe from '../images/shoe.png' 6 | import bottom from '../images/bottom.png' 7 | import tshirt from '../images/tshirt.png' 8 | import activewear from '../images/activewear.png' 9 | import accesory from '../images/accesory.png' 10 | import { Link } from 'react-router-dom' 11 | import {HiArrowNarrowRight} from 'react-icons/hi' 12 | 13 | const Category = () => { 14 | return ( 15 |
16 |
17 |

Shop By Category

18 |
19 | 20 |
21 | View all 22 |
23 | 24 |
25 |
26 |
27 |
28 | / 29 |
30 |
31 |

Dresses

32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 | / 40 |
41 |
42 |

BeachWears

43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 | / 51 |
52 |
53 |

Tops

54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | / 62 |
63 |
64 |

Shoes

65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 | / 73 |
74 |
75 |

Bottoms

76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 | / 84 |
85 |
86 |

T-Shirts

87 |
88 |
89 |
90 | 91 |
92 |
93 |
94 | / 95 |
96 |
97 |

Active Wears

98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 | / 106 |
107 |
108 |

Accessories

109 |
110 |
111 |
112 |
113 |
114 | ) 115 | } 116 | 117 | export default Category -------------------------------------------------------------------------------- /src/layout/Deals.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import male from '../images/male.png' 3 | import female from '../images/female.png' 4 | 5 | const Deals = () => { 6 | return ( 7 |
8 |
9 |

Best Deals Today

10 |
11 |
12 |
13 |
14 |
15 |
Looking Good
16 |
=
17 |
Feeling Good
18 |
Get up to 70% Off on All t-shirts and accessories!
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
Looking Nice
29 |
=
30 |
Feeling Nice
31 |
50% off vacation and beach wears. Offer available while stock lasts
32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | export default Deals -------------------------------------------------------------------------------- /src/layout/FashionInspo.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const FashionInspo = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Fashion Inspo

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default FashionInspo -------------------------------------------------------------------------------- /src/layout/FlashSale.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from 'react' 2 | import {FcFlashOn} from 'react-icons/fc' 3 | import axios from 'axios' 4 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 5 | import {Link} from 'react-router-dom' 6 | 7 | 8 | const FlashSale = ({rowID}) => { 9 | const Ref = useRef(null); 10 | 11 | const [timer, setTimer] = useState('00:00:00'); 12 | 13 | const getTimeRemaining = (e) => { 14 | const total = Date.parse(e) - Date.parse(new Date()); 15 | const seconds = Math.floor((total / 1000) % 60); 16 | const minutes = Math.floor((total / 1000 / 60) % 60); 17 | const hours = Math.floor((total / 1000 / 60 / 60) % 24); 18 | return { 19 | total, hours, minutes, seconds 20 | }; 21 | } 22 | 23 | const startTimer = (e) => { 24 | let { total, hours, minutes, seconds } = getTimeRemaining(e); 25 | if (total >= 0) { 26 | setTimer( 27 | (hours > 9 ? hours : '0' + hours) + ':' + 28 | (minutes > 9 ? minutes : '0' + minutes) + ':' 29 | + (seconds > 9 ? seconds : '0' + seconds) 30 | ) 31 | } 32 | } 33 | 34 | const clearTimer = (e) => { 35 | setTimer('00:00:00'); 36 | 37 | if (Ref.current) clearInterval(Ref.current); 38 | const id = setInterval(() => { 39 | startTimer(e); 40 | }, 1000) 41 | Ref.current = id; 42 | } 43 | 44 | const getDeadTime = () => { 45 | let deadline = new Date(); 46 | 47 | deadline.setSeconds(deadline.getSeconds() + 80000); 48 | return deadline; 49 | } 50 | 51 | useEffect(() => { 52 | clearTimer(getDeadTime()); 53 | }, []); 54 | 55 | const [products, setProducts] = useState([]) 56 | 57 | const url = "https://gorana.onrender.com/products/?page=2"; 58 | 59 | useEffect(() => { 60 | axios.get(url).then((response) => { 61 | setProducts(response.data.results) 62 | console.log(response.data); 63 | }) 64 | }, [url]) 65 | 66 | const slideLeft = () => { 67 | var slider = document.getElementById('slider' + rowID) 68 | slider.scrollLeft = slider.scrollLeft - 500 69 | } 70 | 71 | const slideRight = () => { 72 | var slider = document.getElementById('slider' + rowID) 73 | slider.scrollLeft = slider.scrollLeft + 500 74 | } 75 | 76 | const truncateString = (str, num) => { 77 | if (str?.length > num) { 78 | return str.slice(0, num) + '...'; 79 | } else{ 80 | return str 81 | } 82 | } 83 | 84 | return ( 85 |
86 |
87 |
Flashsale
88 |
89 | Ends In: {timer} 90 |
91 |
92 | 93 |
94 | 95 |
96 | {products?.map(product => 97 | 98 |
99 | {product.id} 100 |
101 |
{product.brand.toUpperCase()}
102 |
103 |
{truncateString(product.name, 25)}
104 |
{product.currency + " " + product.price.toLocaleString()}
105 |
106 |
107 | 108 | )} 109 |
110 | 111 |
112 |
113 | ) 114 | } 115 | 116 | export default FlashSale -------------------------------------------------------------------------------- /src/layout/Hero.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import hero from '../images/hero.png' 4 | 5 | const Hero = () => { 6 | return ( 7 |
8 |
9 |
10 |
11 |

Summer 2022

12 |

NEW COLLECTION

13 |

Shop the hottest, newest sets of fits for your vacation and summer activities

14 | 17 |
18 |
19 | / 20 |
21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |

Summer 2022

29 |

NEW COLLECTION

30 |

Shop the hottest, newest sets of fits for your vacation and summer activities

31 | 34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | export default Hero -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/shopLayout/BestSelling.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const BestSelling = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=4"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Best Selling

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default BestSelling -------------------------------------------------------------------------------- /src/shopLayout/InspiredByCart.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const InspiredByCart = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=9"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Inspired By Cart

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default InspiredByCart -------------------------------------------------------------------------------- /src/shopLayout/Recommended.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const Recommended = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=5"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

New Arrivals

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default Recommended -------------------------------------------------------------------------------- /src/shopLayout/TodayDeals.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const TodayDeals = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=6"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Featured Products

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default TodayDeals -------------------------------------------------------------------------------- /src/shopLayout/TopDeals.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const TopDeals = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=2"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Top Deals

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default TopDeals -------------------------------------------------------------------------------- /src/shopLayout/TopDiscount.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import axios from 'axios' 3 | import {MdChevronLeft, MdChevronRight} from 'react-icons/md' 4 | import {Link} from 'react-router-dom' 5 | 6 | const TopDiscount = ({rowID}) => { 7 | const [products, setProducts] = useState([]) 8 | 9 | const url = "https://gorana.onrender.com/products/?page=8"; 10 | 11 | useEffect(() => { 12 | axios.get(url).then((response) => { 13 | setProducts(response.data.results) 14 | console.log(response.data); 15 | }) 16 | }, [url]) 17 | 18 | const slideLeft = () => { 19 | let slider = document.getElementById('slider' + rowID) 20 | slider.scrollLeft = slider.scrollLeft - 500 21 | } 22 | 23 | const slideRight = () => { 24 | let slider = document.getElementById('slider' + rowID) 25 | slider.scrollLeft = slider.scrollLeft + 500 26 | } 27 | 28 | const truncateString = (str, num) => { 29 | if (str?.length > num) { 30 | return str.slice(0, num) + '...'; 31 | } else{ 32 | return str 33 | } 34 | } 35 | return ( 36 |
37 |
38 |

Top Discounts

39 |
40 | 41 |
42 | 43 |
44 | {products?.map(product => 45 | 46 |
47 | {product.id} 48 |
49 |
{product.brand.toUpperCase()}
50 |
51 |
{truncateString(product.name, 25)}
52 |
{product.currency + " " + product.price.toLocaleString()}
53 |
54 | 55 | )} 56 |
57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | export default TopDiscount -------------------------------------------------------------------------------- /src/slices/api.js: -------------------------------------------------------------------------------- 1 | export const url = "https://gorana.onrender.com/users"; -------------------------------------------------------------------------------- /src/slices/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import jwt_decode from "jwt-decode"; 4 | import { url } from "./api"; 5 | 6 | const initialState = { 7 | registerToken: null, 8 | token: window.localStorage.getItem("token"), 9 | first_name: window.localStorage.getItem('first_name'), 10 | last_name: '', 11 | email: '', 12 | phone: '', 13 | password: '', 14 | password2: '', 15 | _id: '', 16 | registerStatus: '', 17 | registerError: '', 18 | loginStatus: '', 19 | loginError: '', 20 | loading: false, 21 | userLoaded: false, 22 | success: false, 23 | } 24 | 25 | export const registerUser = createAsyncThunk( 26 | 'auth/registerUser', 27 | async (user, {rejectWithValue}) => { 28 | try{ 29 | await axios.post(`${url}/register/`, { 30 | first_name: user.first_name, 31 | last_name: user.last_name, 32 | email: user.email, 33 | phone: user.phone, 34 | password: user.password, 35 | password2: user.password2, 36 | }).then((response) => { 37 | if (response.status === 200) { 38 | console.log('Success!!'); 39 | } else{ 40 | throw response 41 | } 42 | }) 43 | }catch(err){ 44 | console.log(err.response.data.email); 45 | 46 | return rejectWithValue(err.response.data.email) 47 | } 48 | } 49 | ) 50 | 51 | export const loginUser = createAsyncThunk( 52 | 'auth/loginUser', 53 | async (user, {rejectWithValue}) => { 54 | try{ 55 | const response = await axios.post(`${url}/login/`, { 56 | email: user.email, 57 | password: user.password, 58 | }) 59 | window.localStorage.setItem('token', response.data.token) 60 | window.localStorage.setItem('first_name', response.data.first_name) 61 | return response.data 62 | }catch(err){ 63 | console.log(err.response.data.non_field_errors); 64 | 65 | return rejectWithValue(err.response.data.non_field_errors) 66 | } 67 | } 68 | 69 | ) 70 | 71 | const authSlice = createSlice({ 72 | name: 'auth', 73 | initialState, 74 | reducers: { 75 | loadUser(state, action){ 76 | const loadToken = state.token 77 | const name = localStorage.getItem('first_name') 78 | 79 | if (loadToken && name){ 80 | const user = jwt_decode(loadToken) 81 | console.log(user); 82 | console.log(name); 83 | 84 | return{ 85 | ...state, 86 | token: loadToken, 87 | first_name: name, 88 | email: user.email, 89 | _id: user.user_id, 90 | userLoaded: true, 91 | } 92 | } else return {...state, userLoaded: true} 93 | }, 94 | logoutUser(state, action){ 95 | localStorage.removeItem('token') 96 | localStorage.removeItem('first_name') 97 | 98 | return{ 99 | ...state, 100 | token: '', 101 | first_name: '', 102 | last_name: '', 103 | email: '', 104 | phone: '', 105 | password: '', 106 | password2: '', 107 | _id: '', 108 | loading: false, 109 | registerStatus: '', 110 | registerError: '', 111 | loginStatus: '', 112 | loginError: '', 113 | userLoaded: false, 114 | success: false, 115 | } 116 | }, 117 | }, 118 | extraReducers: (builder) => { 119 | builder.addCase(registerUser.pending, (state, action) => { 120 | return { 121 | ...state, 122 | registerStatus: 'pending', 123 | loading: true 124 | } 125 | }); 126 | builder.addCase(registerUser.fulfilled, (state, action) => { 127 | return { 128 | ...state, 129 | registerStatus: 'success', 130 | loading: false, 131 | success: true, 132 | } 133 | }); 134 | builder.addCase(registerUser.rejected, (state, action) => { 135 | return { 136 | ...state, 137 | registerStatus: 'rejected', 138 | registerError: action.payload, 139 | loading: false, 140 | } 141 | }); 142 | 143 | builder.addCase(loginUser.pending, (state, action) => { 144 | return { 145 | ...state, 146 | loginStatus: 'pending', 147 | loading: true 148 | } 149 | }); 150 | builder.addCase(loginUser.fulfilled, (state, action) => { 151 | if (action.payload){ 152 | const user = jwt_decode(action.payload.token) 153 | console.log(user); 154 | const name = localStorage.getItem('first_name') 155 | 156 | 157 | return { 158 | ...state, 159 | token: action.payload.token, 160 | first_name: name, 161 | _id: user.user_id, 162 | loginStatus: 'success', 163 | loading: false, 164 | success: true, 165 | } 166 | }else return state 167 | }); 168 | builder.addCase(loginUser.rejected, (state, action) => { 169 | return { 170 | ...state, 171 | loginStatus: 'rejected', 172 | loginError: action.payload, 173 | loading: false, 174 | } 175 | }); 176 | }, 177 | }); 178 | 179 | export const {loadUser, logoutUser} = authSlice.actions; 180 | 181 | export default authSlice.reducer -------------------------------------------------------------------------------- /src/slices/cartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { toast } from "react-toastify"; 3 | 4 | const initialState = { 5 | cartItems: localStorage.getItem("cartItems") 6 | ? JSON.parse(localStorage.getItem("cartItems")) 7 | : [], 8 | cartTotalQuantity: 0, 9 | cartTotalAmount: 0, 10 | }; 11 | 12 | const cartSlice = createSlice({ 13 | name: "cart", 14 | initialState, 15 | reducers:{ 16 | addToCart(state, action){ 17 | const itemIndex = state.cartItems.findIndex( 18 | (item) => item._id === action.payload._id 19 | ); 20 | if (itemIndex >= 0){ 21 | state.cartItems[itemIndex].cartQuantity += 1; 22 | toast.info(`Increased ${state.cartItems[itemIndex].brand} Cart Quantity`,{ 23 | position: 'bottom-left', 24 | }) 25 | } else { 26 | const tempProduct = {...action.payload, cartQuantity: 1}; 27 | state.cartItems.push(tempProduct); 28 | toast.success(`${action.payload.brand} added to cart`, { 29 | position: "bottom-left", 30 | }) 31 | } 32 | 33 | localStorage.setItem('cartItems', JSON.stringify(state.cartItems)) 34 | }, 35 | 36 | removeFromCart(state, action){ 37 | const nextCartItems = state.cartItems.filter( 38 | cartItem => cartItem._id !== action.payload._id 39 | ) 40 | 41 | state.cartItems = nextCartItems; 42 | localStorage.setItem('cartItems', JSON.stringify(state.cartItems)) 43 | 44 | toast.error(`${action.payload.brand} removed from cart`, { 45 | position: "bottom-left", 46 | }) 47 | }, 48 | 49 | decreaseCart(state, action){ 50 | const itemIndex = state.cartItems.findIndex( 51 | cartItem => cartItem._id === action.payload._id 52 | ) 53 | 54 | if(state.cartItems[itemIndex].cartQuantity > 1){ 55 | state.cartItems[itemIndex].cartQuantity -= 1 56 | 57 | toast.info(`Decreased ${action.payload.brand} Cart Quantity`, { 58 | position: "bottom-left", 59 | }) 60 | } else if (state.cartItems[itemIndex].cartQuantity === 1){ 61 | const nextCartItems = state.cartItems.filter( 62 | cartItem => cartItem._id !== action.payload._id 63 | ) 64 | 65 | state.cartItems = nextCartItems; 66 | 67 | toast.error(`${action.payload.brand} removed from cart`, { 68 | position: "bottom-left", 69 | }) 70 | } 71 | localStorage.setItem('cartItems', JSON.stringify(state.cartItems)) 72 | }, 73 | 74 | clearCart(state, action){ 75 | state.cartItems = [] 76 | 77 | toast.error('Cart cleared', { 78 | position: "bottom-left", 79 | }); 80 | localStorage.setItem('cartItems', JSON.stringify(state.cartItems)) 81 | }, 82 | 83 | getTotals(state, action) { 84 | let { total, quantity } = state.cartItems.reduce( 85 | (cartTotal, cartItem) => { 86 | const { price, cartQuantity } = cartItem; 87 | const itemTotal = price * cartQuantity; 88 | 89 | cartTotal.total += itemTotal; 90 | cartTotal.quantity += cartQuantity; 91 | 92 | return cartTotal; 93 | }, 94 | { 95 | total: 0, 96 | quantity: 0, 97 | } 98 | ); 99 | total = parseFloat(total.toFixed(2)); 100 | state.cartTotalQuantity = quantity; 101 | state.cartTotalAmount = total; 102 | }, 103 | }, 104 | }); 105 | 106 | export const { addToCart, removeFromCart, decreaseCart, clearCart, getTotals} = cartSlice.actions; 107 | 108 | export default cartSlice.reducer; -------------------------------------------------------------------------------- /src/slices/cartUiSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const cartUiSlice = createSlice({ 4 | name: "cartUi", 5 | initialState: { cartIsVisible: false }, 6 | 7 | reducers: { 8 | toggle(state) { 9 | state.cartIsVisible = !state.cartIsVisible; 10 | }, 11 | }, 12 | }); 13 | 14 | export const cartUiActions = cartUiSlice.actions; 15 | export default cartUiSlice; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | ], 6 | darkMode: 'class', 7 | theme: { 8 | extend: { 9 | backgroundColor: { 10 | primary: 'var(--color-bg-primary)', 11 | secondary: 'var(--color-bg-secondary)', 12 | button: 'var(--color-bg-button)', 13 | }, 14 | textColor: { 15 | accent: 'var(--color-text-accent)', 16 | primary: 'var(--color-text-primary)', 17 | secondary: 'var(--color-text-secondary)', 18 | btnText: 'var(--color-bg-secondary)', 19 | }, 20 | borderColor: { 21 | primary: 'var(--color-bg-primary)', 22 | secondary: 'var(--color-bg-secondary)', 23 | input: 'var(--color-bg-input)', 24 | accent: 'var(--color-text-accent)', 25 | }, 26 | }, 27 | }, 28 | plugins: [require('tailwind-scrollbar-hide')], 29 | } 30 | --------------------------------------------------------------------------------