├── .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 |

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 |
357 |
358 |
359 |
360 |
Your Order
361 |
362 |
363 |
364 |
365 | Product |
366 | Total (NGN) |
367 |
368 |
369 |
370 |
371 |
372 | {cart.cartItems.map(cartItem => (
373 |
374 |
375 |
376 |
377 | {truncateString(cartItem.name, 20)}
378 | Qty: {cartItem.cartQuantity}
379 |
380 | |
381 | {(cartItem.price * cartItem.cartQuantity).toLocaleString()} |
382 |
383 | ))}
384 |
385 |
386 | Sub Total: |
387 | {cart.cartTotalAmount.toLocaleString()} |
388 |
389 |
390 | Shipping Fee: |
391 | Free |
392 |
393 |
394 | Total: |
395 | {cart.cartTotalAmount.toLocaleString()} |
396 |
397 |
398 |
399 |
400 |
{loading ? : 'Place Order'}
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
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 |
25 |
26 |
Contact Info
27 |
17 Princess Road, London, Greater London NW1 8JR, UK
28 |
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 |
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 |
90 |
Don't have an account? Create Account
91 |
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 |
80 |
Search
81 |
82 |
83 |
84 |
85 |
86 | {auth._id && auth.first_name ?
87 | (
88 |
89 |
Hi, {auth.first_name}!
90 |
91 |
160 |
161 |
162 |
163 | )
164 | :
165 | (
166 |
167 |
Account
168 |
169 |
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 |
278 |
279 |
280 |
281 |
282 |
283 |
284 | {/* Mobile Menu */}
285 | {/* Overlay */}
286 |
288 |
292 |
293 |
294 |
295 |

setNav(false)}/>
296 |
297 |
298 |
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 |
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 |

setImgIndex(0)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' />
58 |

setImgIndex(1)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' />
59 |

setImgIndex(2)} className='rounded-md px-1 hover:scale-105 ease-in duration-300 cursor-pointer' />
60 |

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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |

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 |

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 |

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 |

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 |

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 |

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 |

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 |

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 |
--------------------------------------------------------------------------------