├── 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 |
--------------------------------------------------------------------------------
/public/assets/icons/add-circle-outline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/icons/close-circle-outline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
22 | {product.name}
23 | {formatCurrency(product.price)}
24 |
25 | {isInCart(product) && (
26 | {
28 | increase(product);
29 | }}
30 | className="btn"
31 | >
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 |
--------------------------------------------------------------------------------