├── Screen Capture.png ├── public └── assets │ ├── img │ ├── 1.png │ ├── 10.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png │ └── icons │ ├── cart.svg │ ├── remove-circle-outline.svg │ ├── add-circle-outline.svg │ ├── close-circle-outline.svg │ ├── trash-outline.svg │ └── logo-github.svg ├── src ├── Context │ └── Cart │ │ ├── CartContext.jsx │ │ ├── CartTypes.js │ │ ├── CartState.jsx │ │ └── CartReducer.jsx ├── utils.js ├── main.jsx ├── App.jsx ├── pages │ ├── About.jsx │ ├── Store.jsx │ └── Cart.jsx ├── index.css ├── data.js ├── components │ ├── Checkout.jsx │ ├── CartItem.jsx │ ├── ProductCard.jsx │ └── Navbar.jsx └── favicon.svg ├── vite.config.js ├── .gitignore ├── package.json ├── README.md └── index.html /Screen Capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/Screen Capture.png -------------------------------------------------------------------------------- /public/assets/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/1.png -------------------------------------------------------------------------------- /public/assets/img/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/10.png -------------------------------------------------------------------------------- /public/assets/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/2.png -------------------------------------------------------------------------------- /public/assets/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/3.png -------------------------------------------------------------------------------- /public/assets/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/4.png -------------------------------------------------------------------------------- /public/assets/img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/5.png -------------------------------------------------------------------------------- /public/assets/img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/6.png -------------------------------------------------------------------------------- /public/assets/img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/7.png -------------------------------------------------------------------------------- /public/assets/img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/8.png -------------------------------------------------------------------------------- /public/assets/img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israelmitolu/Learn-Context-by-Building-a-Shopping-Website/HEAD/public/assets/img/9.png -------------------------------------------------------------------------------- /src/Context/Cart/CartContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const CartContext = createContext(); 4 | 5 | export default CartContext; 6 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /src/Context/Cart/CartTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TO_CART = "ADD_TO_CART"; 2 | export const REMOVE_ITEM = "REMOVE_ITEM"; 3 | export const INCREASE = "INCREASE"; 4 | export const DECREASE = "DECREASE"; 5 | export const CHECKOUT = "CHECKOUT"; 6 | export const CLEAR = "CLEAR"; 7 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const formatCurrency = (number) => { 2 | return new Intl.NumberFormat("en-NG", { 3 | style: "currency", 4 | currency: "NGN", 5 | }).format(number); 6 | }; 7 | 8 | // The Intl.NumberFormat object helps to format the currency. Learn about it https://docs.w3cub.com/javascript/global_objects/intl/numberformat 9 | -------------------------------------------------------------------------------- /public/assets/icons/cart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | import CartState from "./Context/Cart/CartState"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Local Netlify folder 27 | .netlify 28 | -------------------------------------------------------------------------------- /public/assets/icons/remove-circle-outline.svg: -------------------------------------------------------------------------------- 1 | Remove Circle -------------------------------------------------------------------------------- /public/assets/icons/add-circle-outline.svg: -------------------------------------------------------------------------------- 1 | Add Circle -------------------------------------------------------------------------------- /public/assets/icons/close-circle-outline.svg: -------------------------------------------------------------------------------- 1 | Close Circle -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "final-shopping-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "react": "^18.0.0", 12 | "react-dom": "^18.0.0", 13 | "react-router-dom": "^6.3.0", 14 | "styled-components": "^5.3.5" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.0", 18 | "@types/react-dom": "^18.0.0", 19 | "@vitejs/plugin-react": "^1.3.0", 20 | "vite": "^2.9.16" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimalist Ecommerce store 2 | 3 | Simple web app to demonstrate the use of the React Context API. 4 | 5 | ## Screenshot 📸 6 | 7 | ![Ecommerce store](Screen%20Capture.png) 8 | 9 | ## Live Site 🚀 10 | 11 | [Preview the website here](https://shop-context.netlify.app/). 12 | 13 | ## Tools 🔨 14 | 15 | This project is made using the following tools: 16 | 17 | - ReactJS 18 | - Styled components 19 | - React Router 20 | - React Hooks 21 | - Context API 22 | - Intl.NumberFormat 23 | 24 | ## Learn how to build ⚡ 25 | 26 | Learn how to build this project on [my blog.](https://israelmitolu.hashnode.dev) 27 | -------------------------------------------------------------------------------- /public/assets/icons/trash-outline.svg: -------------------------------------------------------------------------------- 1 | Trash -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import Navbar from "./components/Navbar"; 2 | import Store from "./pages/Store"; 3 | import About from "./pages/About"; 4 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 5 | import Cart from "./pages/Cart"; 6 | 7 | function App() { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | } /> 14 | } /> 15 | } /> 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | React Context Shopping App 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/assets/icons/logo-github.svg: -------------------------------------------------------------------------------- 1 | Logo Github -------------------------------------------------------------------------------- /src/pages/About.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import GithubIcon from "/assets/icons/logo-github.svg"; 3 | 4 | const About = () => { 5 | return ( 6 |
7 | 8 |

About

9 |

This project was built to learn the use of Context API and React.

10 |
11 | 12 | Read the article here 13 | 14 |
15 | 16 | 17 | View on Github 18 | 19 |
20 |
21 | ); 22 | }; 23 | 24 | const Heading = styled.div` 25 | margin-top: 8rem; 26 | text-align: center; 27 | display: block; 28 | 29 | a:nth-of-type(1) { 30 | border-bottom: violet solid 2px; 31 | } 32 | `; 33 | 34 | export default About; 35 | -------------------------------------------------------------------------------- /src/pages/Store.jsx: -------------------------------------------------------------------------------- 1 | import { products } from "../data"; 2 | import styled from "styled-components"; 3 | import ProductCard from "../components/ProductCard"; 4 | 5 | const Store = () => { 6 | return ( 7 | <> 8 | 9 |

Browse the Store!

10 |

New Arrivals for you! Check out our selection of products.

11 |
12 | 13 | {products.map((product) => ( 14 | 15 | ))} 16 | 17 | 18 | ); 19 | }; 20 | 21 | //Styled Components 22 | 23 | const Heading = styled.div` 24 | margin-top: 8rem; 25 | text-align: center; 26 | `; 27 | 28 | const ProductsContainer = styled.div` 29 | max-width: 1024px; 30 | width: 80%; 31 | margin: 70px auto 0; 32 | gap: 12px; 33 | display: grid; 34 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 35 | grid-gap: 15px; 36 | `; 37 | 38 | export default Store; 39 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | min-height: 100vh; 9 | max-width: 100%; 10 | font-family: "Work Sans", sans-serif; 11 | } 12 | 13 | a { 14 | color: inherit; 15 | text-decoration: none; 16 | } 17 | 18 | p { 19 | color: #55595c; 20 | font-size: 0.9rem; 21 | } 22 | 23 | ul { 24 | list-style: none; 25 | } 26 | 27 | h1, 28 | h2, 29 | h3 { 30 | font-family: "Cormorant Garamond", serif; 31 | margin-bottom: 0.5rem; 32 | } 33 | 34 | /* Custom Classes */ 35 | 36 | .btn { 37 | font-size: 11px; 38 | line-height: 1.5; 39 | margin-right: 0.5rem; 40 | padding: 0.5rem 1rem; 41 | color: #1a1a1a; 42 | display: inline-block; 43 | text-align: center; 44 | user-select: none; 45 | background-color: transparent; 46 | border: 0 solid transparent; 47 | transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, 48 | border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; 49 | text-transform: uppercase; 50 | } 51 | 52 | .btn:hover { 53 | text-decoration: underline; 54 | } 55 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: 1, 4 | name: "Cerveza Modelo", 5 | price: 919.11, 6 | image: "/assets/img/1.png", 7 | }, 8 | { 9 | id: 2, 10 | name: "Diesel Life", 11 | price: 1257.92, 12 | image: "/assets/img/2.png", 13 | }, 14 | { 15 | id: 3, 16 | name: "Indian Cricket Team jersey", 17 | price: 1500.85, 18 | image: "/assets/img/3.png", 19 | }, 20 | { 21 | id: 4, 22 | name: "One Punch man - OK", 23 | price: 1250.9, 24 | image: "/assets/img/4.png", 25 | }, 26 | { 27 | id: 5, 28 | name: "Hiking jacket", 29 | price: 1750.85, 30 | image: "/assets/img/5.png", 31 | }, 32 | { 33 | id: 6, 34 | name: "Real Heart", 35 | price: 3100.61, 36 | image: "/assets/img/6.png", 37 | }, 38 | { 39 | id: 7, 40 | name: "Fredd - Black and White", 41 | price: 1801.1, 42 | image: "/assets/img/7.png", 43 | }, 44 | { 45 | id: 8, 46 | name: "Star Wars - The Last", 47 | price: 1199.99, 48 | image: "/assets/img/8.png", 49 | }, 50 | { 51 | id: 9, 52 | name: "Yellow Blouse", 53 | price: 2395.16, 54 | image: "/assets/img/9.png", 55 | }, 56 | { 57 | id: 10, 58 | name: "Rick and Morty - Supreme", 59 | price: 1243.82, 60 | image: "/assets/img/10.png", 61 | }, 62 | ]; 63 | -------------------------------------------------------------------------------- /src/components/Checkout.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { formatCurrency } from "../utils"; 3 | import { useContext } from "react"; 4 | import CartContext from "../Context/Cart/CartContext"; 5 | 6 | const Checkout = () => { 7 | const { clearCart, handleCheckout, itemCount, total } = 8 | useContext(CartContext); 9 | 10 | return ( 11 | 12 |

Total Items:

13 |

{itemCount}

14 |

Total Payment:

15 |

{formatCurrency(total)}

16 |
17 |
18 | CHECKOUT 19 | CLEAR 20 |
21 |
22 | ); 23 | }; 24 | 25 | // Styled Components 26 | 27 | const Wrapper = styled.div` 28 | margin-top: 3rem; 29 | border: 1px solid #000; 30 | padding: 1.5rem; 31 | 32 | p, 33 | h4 { 34 | margin: 1rem; 35 | font-size: large; 36 | } 37 | `; 38 | 39 | const CheckBtn = styled.button` 40 | color: #fff; 41 | background-color: green; 42 | border: 1px solid #1a1a1a; 43 | padding: 0.5rem 1rem; 44 | line-height: 1.5; 45 | font-size: 10px; 46 | border-radius: 0; 47 | text-transform: uppercase; 48 | cursor: pointer; 49 | outline: none; 50 | `; 51 | 52 | const ClearBtn = styled(CheckBtn)` 53 | background: transparent; 54 | color: #000; 55 | 56 | &:hover { 57 | background-color: red; 58 | color: #fff; 59 | } 60 | `; 61 | export default Checkout; 62 | -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/CartItem.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import CartContext from "../Context/Cart/CartContext"; 3 | import styled from "styled-components"; 4 | import { formatCurrency } from "../utils"; 5 | import TrashIcon from "/assets/icons/trash-outline.svg"; 6 | import Plus from "/assets/icons/add-circle-outline.svg"; 7 | import Minus from "/assets/icons/remove-circle-outline.svg"; 8 | 9 | const CartItem = ({ product }) => { 10 | const { removeFromCart, increase, decrease } = useContext(CartContext); 11 | 12 | return ( 13 | 14 | 15 |
16 |
{product.name}
17 |

{formatCurrency(product.price)}

18 |
19 | 20 | {/* Buttons */} 21 | 22 | 28 | 29 |
30 |

Qty: {product.quantity}

31 |
32 | 33 | {product.quantity > 1 && ( 34 | 37 | )} 38 | 39 | {product.quantity === 1 && ( 40 | 43 | )} 44 |
45 |
46 | ); 47 | }; 48 | 49 | //Styled Components 50 | 51 | const SingleCartItem = styled.div` 52 | border-bottom: 1px solid gray; 53 | padding: 10px 0; 54 | margin-top: 10px; 55 | display: flex; 56 | align-items: center; 57 | justify-content: space-around; 58 | width: 100%; 59 | 60 | &:nth-child(1) { 61 | border-top: 1px solid gray; 62 | } 63 | `; 64 | 65 | const CartImage = styled.img` 66 | width: 100px; 67 | height: auto; 68 | padding-right: 2rem; 69 | `; 70 | 71 | const BtnContainer = styled.div` 72 | display: flex; 73 | align-items: center; 74 | justify-content: space-around; 75 | `; 76 | 77 | const Icon = styled.img` 78 | width: 1.6rem; 79 | height: auto; 80 | `; 81 | 82 | export default CartItem; 83 | -------------------------------------------------------------------------------- /src/Context/Cart/CartState.jsx: -------------------------------------------------------------------------------- 1 | import { useReducer } from "react"; 2 | import CartContext from "./CartContext"; 3 | import CartReducer from "./CartReducer"; 4 | import { sumItems } from "./CartReducer"; 5 | 6 | //Local Storage 7 | const storage = localStorage.getItem("cartItems") 8 | ? JSON.parse(localStorage.getItem("cartItems")) 9 | : []; 10 | 11 | const CartState = ({ children }) => { 12 | //Initial State of the cart 13 | // const initialState = { 14 | // cartItems: [], 15 | // checkout: false, 16 | // }; 17 | 18 | //Change the code above to that below to get the initial state from local storage 19 | const initialState = { 20 | cartItems: storage, 21 | ...sumItems(storage), 22 | checkout: false, 23 | }; 24 | 25 | //Set up the reducer 26 | const [state, dispatch] = useReducer(CartReducer, initialState); 27 | 28 | //Function to handle when an item is added from the store into the Cart 29 | const addToCart = (payload) => { 30 | dispatch({ type: "ADD_TO_CART", payload }); 31 | }; 32 | 33 | //Function to handle when an item that is in the cart is added again 34 | const increase = (payload) => { 35 | dispatch({ type: "INCREASE", payload }); 36 | }; 37 | 38 | //Function to handle when an item is removed from the cart 39 | const decrease = (payload) => { 40 | dispatch({ type: "DECREASE", payload }); 41 | }; 42 | 43 | //Function to remove an item from the cart 44 | const removeFromCart = (payload) => { 45 | dispatch({ type: "REMOVE_ITEM", payload }); 46 | }; 47 | 48 | //Function to clear the cart 49 | const clearCart = () => { 50 | dispatch({ type: "CLEAR" }); 51 | }; 52 | 53 | //Function to handle when the user clicks the checkout button 54 | const handleCheckout = () => { 55 | dispatch({ type: "CHECKOUT" }); 56 | }; 57 | 58 | return ( 59 | //Add the above functions into the Context provider, and pass to the children 60 | 74 | {children} 75 | 76 | ); 77 | }; 78 | 79 | export default CartState; 80 | -------------------------------------------------------------------------------- /src/components/ProductCard.jsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Link } from "react-router-dom"; 3 | import { formatCurrency } from "../utils"; 4 | import CartContext from "../Context/Cart/CartContext"; 5 | import { useContext } from "react"; 6 | 7 | const ProductCard = ({ product }) => { 8 | const { addToCart, increase, cartItems, sumItems, itemCount } = 9 | useContext(CartContext); 10 | 11 | //Check whether the product is in the cart or not 12 | const isInCart = (product) => { 13 | return !!cartItems.find((item) => item.id === product.id); 14 | }; 15 | 16 | return ( 17 | 18 | 32 | Add More 33 | 34 | )} 35 | 36 | {!isInCart(product) && ( 37 | 38 | )} 39 | 40 | 41 | ); 42 | }; 43 | 44 | //Styled Components 45 | const CardWrapper = styled.div` 46 | position: relative; 47 | display: flex; 48 | flex-direction: column; 49 | background-color: #fff; 50 | border: 1px solid rgba(0, 0, 0, 0.125); 51 | flex: 1 1 auto; 52 | padding: 1.25rem; 53 | text-align: center; 54 | `; 55 | 56 | const ProductImage = styled.img` 57 | margin: 0 auto 10px; 58 | max-height: 200px; 59 | max-width: 100%; 60 | height: auto; 61 | object-fit: cover; 62 | align-self: center; 63 | `; 64 | 65 | const ProductName = styled.p` 66 | font-size: 0.9rem; 67 | text-align: left; 68 | margin: 1rem; 69 | `; 70 | 71 | const ProductCardPrice = styled.h3` 72 | font-size: 1.5rem; 73 | font-family: "Work Sans", sans-serif; 74 | text-align: left; 75 | font-weight: 500; 76 | margin-bottom: 0.7rem; 77 | `; 78 | 79 | const ProductCardButtons = styled.div` 80 | text-align: right; 81 | `; 82 | 83 | const Button = styled.button` 84 | color: #fff; 85 | background-color: #1a1a1a; 86 | border-color: #1a1a1a; 87 | padding: 0.5rem 1rem; 88 | line-height: 1.5; 89 | font-size: 10px; 90 | border-radius: 0; 91 | text-transform: uppercase; 92 | cursor: pointer; 93 | `; 94 | 95 | const ButtonAddMore = styled(Button)` 96 | color: #1a1a1a; 97 | border: 2px solid #1a1a1a; 98 | cursor: pointer; 99 | background-color: transparent; 100 | 101 | &:hover { 102 | color: #fff; 103 | background-color: #1a1a1a; 104 | border: 2px solid #1a1a1a; 105 | } 106 | `; 107 | 108 | export default ProductCard; 109 | -------------------------------------------------------------------------------- /src/pages/Cart.jsx: -------------------------------------------------------------------------------- 1 | import CartItem from "../components/CartItem"; 2 | import { useContext } from "react"; 3 | import CartContext from "../Context/Cart/CartContext"; 4 | import styled from "styled-components"; 5 | import Checkout from "../components/Checkout"; 6 | import { Link } from "react-router-dom"; 7 | 8 | const Cart = () => { 9 | // Extract the cart state from the context 10 | const { cartItems, checkout, clearCart } = useContext(CartContext); 11 | 12 | return ( 13 | <> 14 | 15 |

16 | Shopping Cart 17 | ({cartItems.length}) 18 |

19 |
20 | 21 | {checkout && ( 22 | 23 |

Thank you for your purchase!

24 |

25 | Your order has been placed and will be delivered to you within 24 26 | hours. 27 |

28 | 29 | Continue Shopping 30 | 31 |
32 | )} 33 | 34 | 35 |
36 | { 37 | 38 | {/* If cart is empty, display message, and if not, display each cart 39 | Item in cart: {cartItems.length} */} 40 | {cartItems.length === 0 ? ( 41 |

Cart is empty

42 | ) : ( 43 |
    44 | {cartItems.map((product) => ( 45 | 46 | ))} 47 |
48 | )} 49 |
50 | } 51 |
52 | 53 |
54 | {/* Checkout component */} 55 | {cartItems.length > 0 && } 56 |
57 |
58 | 59 | ); 60 | }; 61 | 62 | //Styled Components 63 | const Heading = styled.div` 64 | margin-top: 8rem; 65 | text-align: center; 66 | `; 67 | 68 | const Layout = styled.div` 69 | display: flex; 70 | justify-content: space-around; 71 | align-items: flex-start; 72 | margin: auto; 73 | width: 85%; 74 | 75 | @media (max-width: 1024px) { 76 | flex-direction: column; 77 | align-items: center; 78 | 79 | &:nth-child(2) { 80 | margin-top: 3rem; 81 | } 82 | } 83 | `; 84 | 85 | const CartItemWrapper = styled.div` 86 | display: flex; 87 | flex-wrap: wrap; 88 | flex-direction: column; 89 | align-items: center; 90 | justify-content: space-around; 91 | text-align: center; 92 | `; 93 | 94 | const CheckoutMsg = styled.div` 95 | color: green; 96 | text-align: center; 97 | padding: 1.5rem; 98 | 99 | p { 100 | margin: 0.5rem 0 1.5rem 0; 101 | } 102 | `; 103 | 104 | const ShopBtn = styled.button` 105 | outline: none; 106 | border: 1px solid green; 107 | background-color: transparent; 108 | padding: 0.75rem; 109 | color: green; 110 | margin-top: 1rem; 111 | cursor: pointer; 112 | 113 | &:hover { 114 | background-color: green; 115 | color: white; 116 | } 117 | `; 118 | export default Cart; 119 | -------------------------------------------------------------------------------- /src/Context/Cart/CartReducer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | REMOVE_ITEM, 3 | ADD_TO_CART, 4 | INCREASE, 5 | DECREASE, 6 | CHECKOUT, 7 | CLEAR, 8 | } from "./CartTypes.js"; 9 | 10 | // Save the cartItems to local storage 11 | const Storage = (cartItems) => { 12 | localStorage.setItem( 13 | "cartItems", 14 | JSON.stringify(cartItems.length > 0 ? cartItems : []) 15 | ); 16 | }; 17 | 18 | // Export function to calculate the total price of the cart and the total quantity of the cart 19 | export const sumItems = (cartItems) => { 20 | Storage(cartItems); 21 | let itemCount = cartItems.reduce( 22 | (total, product) => total + product.quantity, 23 | 0 24 | ); 25 | let total = cartItems 26 | .reduce((total, product) => total + product.price * product.quantity, 0) 27 | .toFixed(2); 28 | return { itemCount, total }; 29 | }; 30 | 31 | // The reducer is listening for an action, which is the type that we defined in the CartTypes.js file 32 | const CartReducer = (state, action) => { 33 | // The switch statement is checking the type of action that is being passed in 34 | switch (action.type) { 35 | // If the action type is ADD_TO_CART, we want to add the item to the cartItems array 36 | case ADD_TO_CART: 37 | if (!state.cartItems.find((item) => item.id === action.payload.id)) { 38 | state.cartItems.push({ 39 | ...action.payload, 40 | quantity: 1, 41 | }); 42 | } 43 | 44 | return { 45 | ...state, 46 | ...sumItems(state.cartItems), 47 | cartItems: [...state.cartItems], 48 | }; 49 | 50 | // If the action type is REMOVE_ITEM, we want to remove the item from the cartItems array 51 | case REMOVE_ITEM: 52 | return { 53 | ...state, 54 | ...sumItems( 55 | state.cartItems.filter((item) => item.id !== action.payload.id) 56 | ), 57 | cartItems: [ 58 | ...state.cartItems.filter((item) => item.id !== action.payload.id), 59 | ], 60 | }; 61 | 62 | // If the action type is INCREASE, we want to increase the quantity of the particular item in the cartItems array 63 | case INCREASE: 64 | state.cartItems[ 65 | state.cartItems.findIndex((item) => item.id === action.payload.id) 66 | ].quantity++; 67 | return { 68 | ...state, 69 | ...sumItems(state.cartItems), 70 | cartItems: [...state.cartItems], 71 | }; 72 | 73 | // If the action type is DECREASE, we want to decrease the quantity of the particular item in the cartItems array 74 | case DECREASE: 75 | state.cartItems[ 76 | state.cartItems.findIndex((item) => item.id === action.payload.id) 77 | ].quantity--; 78 | return { 79 | ...state, 80 | ...sumItems(state.cartItems), 81 | cartItems: [...state.cartItems], 82 | }; 83 | 84 | // If the action type is CHECKOUT, we want to clear the cartItems array and set the checkout to true 85 | case CHECKOUT: 86 | return { 87 | cartItems: [], 88 | checkout: true, 89 | ...sumItems([]), 90 | }; 91 | 92 | //If the action type is CLEAR, we want to clear the cartItems array 93 | case CLEAR: 94 | return { 95 | cartItems: [], 96 | ...sumItems([]), 97 | }; 98 | 99 | //Return the state if the action type is not found 100 | default: 101 | return state; 102 | } 103 | }; 104 | 105 | export default CartReducer; 106 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | // General 2 | import { useState, useEffect } from "react"; 3 | import { Link, NavLink } from "react-router-dom"; 4 | import CartIcon from "/assets/icons/cart.svg"; 5 | import styled from "styled-components"; 6 | import CartContext from "../Context/Cart/CartContext"; 7 | import { useContext } from "react"; 8 | 9 | const Navbar = () => { 10 | const [toggle, setToggle] = useState(false); 11 | const [innerWidth, setInnerWidth] = useState(window.innerWidth); 12 | 13 | // Get Screen Size 14 | useEffect(() => { 15 | const changeWidth = () => { 16 | setInnerWidth(window.innerWidth); 17 | }; 18 | 19 | window.addEventListener("resize", changeWidth); 20 | 21 | return () => { 22 | window.removeEventListener("resize", changeWidth); 23 | }; 24 | }, []); 25 | 26 | // Extract itemscount from CartContext 27 | const { cartItems } = useContext(CartContext); 28 | 29 | return ( 30 | 85 | ); 86 | }; 87 | 88 | //Styled Components 89 | const Nav = styled.nav` 90 | width: 100%; 91 | position: fixed; 92 | top: 0; 93 | left: 0; 94 | height: 4.5rem; 95 | font-size: 1em; 96 | z-index: 50; 97 | background-color: #eee; 98 | 99 | @media (max-width: 768px) { 100 | font-size: 0.85rem; 101 | border: none; 102 | } 103 | `; 104 | 105 | const NavContainer = styled.div` 106 | width: 100%; 107 | height: 100%; 108 | max-width: 1250px; 109 | margin: 0 auto; 110 | padding: 0 2rem; 111 | display: flex; 112 | justify-content: space-between; 113 | align-items: center; 114 | 115 | @media (max-width: 768px) { 116 | padding: 0 1rem; 117 | border-bottom: 1.5px solid #cfcfd0; 118 | } 119 | 120 | @media (max-width: 500px) { 121 | background-color: #eee; 122 | } 123 | `; 124 | 125 | const NavRightContainer = styled.div` 126 | @media (max-width: 500px) { 127 | width: 100%; 128 | position: fixed; 129 | top: calc(4.5rem - 100vh); 130 | left: 0; 131 | background-color: #fff; 132 | z-index: -1; 133 | transition: transform 600ms cubic-bezier(1, 0, 0, 1) 0ms; 134 | } 135 | `; 136 | 137 | const Left = styled.div` 138 | a { 139 | color: #13122e; 140 | font-weight: 700; 141 | font-size: 1.25rem; 142 | font-family: "Cormorant Garamond", serif; 143 | } 144 | `; 145 | 146 | const Right = styled.div` 147 | font-family: "Work Sans", sans-serif; 148 | `; 149 | 150 | const NavList = styled.ul` 151 | width: 100%; 152 | display: flex; 153 | justify-content: center; 154 | align-items: center; 155 | 156 | @media (max-width: 500px) { 157 | flex-direction: column; 158 | flex-wrap: nowrap; 159 | } 160 | `; 161 | 162 | const NavItem = styled.li` 163 | margin: 0 1.25em; 164 | 165 | a, 166 | span { 167 | color: #13122e; 168 | display: flex; 169 | justify-content: center; 170 | align-items: center; 171 | 172 | @media (max-width: 500px) { 173 | width: 100%; 174 | align-items: center; 175 | justify-content: flex-start; 176 | padding: 1rem; 177 | border-bottom: 1px solid #cfcfd0; 178 | max-height: 45px; 179 | 180 | &:hover { 181 | background-color: #cfcfd0; 182 | } 183 | } 184 | 185 | img { 186 | width: 1.6rem; 187 | } 188 | } 189 | p { 190 | display: none; 191 | } 192 | 193 | @media (max-width: 500px) { 194 | width: 100%; 195 | 196 | &:nth-of-type(4) a { 197 | display: flex; 198 | justify-content: space-between !important; 199 | align-items: center; 200 | border: none; 201 | } 202 | 203 | p { 204 | display: initial; 205 | } 206 | } 207 | `; 208 | 209 | const NavCartItem = styled.div` 210 | position: relative; 211 | 212 | img { 213 | @media (max-width: 500px) { 214 | display: none; 215 | } 216 | } 217 | `; 218 | 219 | const CartCircle = styled.div` 220 | position: absolute; 221 | top: -5px; 222 | right: -10px; 223 | background-color: #13122e; 224 | width: 18px; 225 | height: 18px; 226 | border-radius: 50%; 227 | color: #fff; 228 | display: flex; 229 | justify-content: center; 230 | align-items: center; 231 | font-size: 0.85em; 232 | 233 | @media (max-width: 500px) { 234 | position: initial; 235 | } 236 | `; 237 | 238 | const MenuBtn = styled.div` 239 | display: none; 240 | 241 | @media (max-width: 500px) { 242 | display: flex; 243 | justify-content: space-between; 244 | align-items: center; 245 | flex-flow: column nowrap; 246 | width: 18px; 247 | height: 14px; 248 | 249 | span { 250 | width: 100%; 251 | height: 2px; 252 | background-color: #13122e; 253 | } 254 | } 255 | `; 256 | 257 | export default Navbar; 258 | --------------------------------------------------------------------------------