├── Screenshot_1.jpg ├── Screenshot_2.jpg ├── images ├── hero-bcg.jpeg ├── product-1.jpeg ├── product-2.jpeg ├── product-3.jpeg ├── product-4.jpeg ├── product-5.jpeg ├── product-6.jpeg ├── product-7.jpeg ├── product-8.jpeg └── logo.svg ├── todo.txt ├── js ├── script.js ├── products.js └── cart.js ├── index.html ├── products.json ├── README.md └── css └── style.css /Screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/Screenshot_1.jpg -------------------------------------------------------------------------------- /Screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/Screenshot_2.jpg -------------------------------------------------------------------------------- /images/hero-bcg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/hero-bcg.jpeg -------------------------------------------------------------------------------- /images/product-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-1.jpeg -------------------------------------------------------------------------------- /images/product-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-2.jpeg -------------------------------------------------------------------------------- /images/product-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-3.jpeg -------------------------------------------------------------------------------- /images/product-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-4.jpeg -------------------------------------------------------------------------------- /images/product-5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-5.jpeg -------------------------------------------------------------------------------- /images/product-6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-6.jpeg -------------------------------------------------------------------------------- /images/product-7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-7.jpeg -------------------------------------------------------------------------------- /images/product-8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cudi7/JavaScript_Shopping-cart/HEAD/images/product-8.jpeg -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | Shopping Cart 2 | 3 | - Display product list 4 | 5 | - ✅Add product 6 | - ✅change quantities 7 | - ✅ Save to localStorage 8 | 9 | - View Cart 10 | - ✅Delete product 11 | - ✅Edit quantity 12 | - ✅ View Total 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | import { displayCartItems } from './cart.js'; 2 | import { displayData } from './products.js'; 3 | 4 | const iconQtyAction = document.querySelector('.header__icon'); 5 | 6 | //fetch all data 7 | function fetchData() { 8 | fetch('../products.json') 9 | .then((data) => data.json()) 10 | .then((response) => displayData(response)) 11 | .catch((err) => console.error(err.message)); 12 | } 13 | 14 | function getLocalStorage(description) { 15 | return localStorage.getItem(description) 16 | ? JSON.parse(localStorage.getItem(description)) 17 | : undefined; 18 | } 19 | 20 | function setLocalStorage(description, article) { 21 | localStorage.setItem(description, JSON.stringify(article)); 22 | if (description === 'currentCart') displayCartItems(article); 23 | } 24 | 25 | iconQtyAction.addEventListener('click', handlePageView); 26 | 27 | function handlePageView() { 28 | const allProductsApp = document.querySelector('.allProductsApp'); 29 | const shoppingCartApp = document.querySelector('.shoppingCartApp'); 30 | allProductsApp.style.display = 'none'; 31 | shoppingCartApp.style.display = 'block'; 32 | } 33 | 34 | //start app loading all data 35 | fetchData(); 36 | 37 | export { getLocalStorage, setLocalStorage }; 38 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | Shopping Cart 19 | 20 | 21 |
22 |
23 |

Shopping Cart

24 |
25 | 🛒 0 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |

36 | Go back 37 |

38 |
39 |
40 |
41 |

Total price: 0$

42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /products.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "sys": { "id": "1" }, 5 | "fields": { 6 | "title": "queen panel bed", 7 | "price": 10.99, 8 | "image": { "fields": { "file": { "url": "./images/product-1.jpeg" } } } 9 | } 10 | }, 11 | { 12 | "sys": { "id": "2" }, 13 | "fields": { 14 | "title": "king panel bed", 15 | "price": 12.99, 16 | "image": { "fields": { "file": { "url": "./images/product-2.jpeg" } } } 17 | } 18 | }, 19 | { 20 | "sys": { "id": "3" }, 21 | "fields": { 22 | "title": "single panel bed", 23 | "price": 12.99, 24 | "image": { "fields": { "file": { "url": "./images/product-3.jpeg" } } } 25 | } 26 | }, 27 | { 28 | "sys": { "id": "4" }, 29 | "fields": { 30 | "title": "twin panel bed", 31 | "price": 22.99, 32 | "image": { "fields": { "file": { "url": "./images/product-4.jpeg" } } } 33 | } 34 | }, 35 | { 36 | "sys": { "id": "5" }, 37 | "fields": { 38 | "title": "fridge", 39 | "price": 88.99, 40 | "image": { "fields": { "file": { "url": "./images/product-5.jpeg" } } } 41 | } 42 | }, 43 | { 44 | "sys": { "id": "6" }, 45 | "fields": { 46 | "title": "dresser", 47 | "price": 32.99, 48 | "image": { "fields": { "file": { "url": "./images/product-6.jpeg" } } } 49 | } 50 | }, 51 | { 52 | "sys": { "id": "7" }, 53 | "fields": { 54 | "title": "couch", 55 | "price": 45.99, 56 | "image": { "fields": { "file": { "url": "./images/product-7.jpeg" } } } 57 | } 58 | }, 59 | { 60 | "sys": { "id": "8" }, 61 | "fields": { 62 | "title": "table", 63 | "price": 33.99, 64 | "image": { "fields": { "file": { "url": "./images/product-8.jpeg" } } } 65 | } 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shopping Cart app build with JavaScript 2 | 3 | # Live demo 4 | 5 | > https://pensive-mccarthy-f9998a.netlify.app 6 | 7 | ![Design preview for the coding challenge](./Screenshot_1.jpg) 8 | ![Design preview for the coding challenge](./Screenshot_2.jpg) 9 | 10 | ## Table of contents 11 | 12 | - [General info](#general-info) 13 | - [Code Examples](#code-examples) 14 | - [Features](#features) 15 | - [Contact](#contact) 16 | 17 | ## General info 18 | 19 | - Responsive, build with HTML/CSS/JS. 20 | 21 | ## Code Examples 22 | 23 | ./js/products.js 24 | 25 | ``` 26 | // loop thorough data and display each field 27 | function displayData(data) { 28 | data.items.forEach((item, index) => { 29 | const div = document.createElement('div'); 30 | div.classList.add('product-item'); 31 | div.id = item.sys.id; 32 | 33 | div.innerHTML = ` 34 |
35 |

${item.fields.title}

36 |
37 | product ${index + 1} 43 |
44 |
${item.fields.price} $
45 | 46 |
47 | `; 48 | 49 | productContainer.append(div); 50 | }); 51 | 52 | loadListeners(); 53 | loadPreviousCart(); 54 | loadPreviousQtyCart(); 55 | } 56 | 57 | function loadListeners() { 58 | const addCartBtn = document.querySelectorAll('.add-cart'); 59 | 60 | addCartBtn.forEach((btn) => btn.addEventListener('click', handleAddProduct)); 61 | } 62 | 63 | function loadPreviousCart() { 64 | const prevCart = getLocalStorage('currentCart'); 65 | currentCart = prevCart ? prevCart : []; 66 | prevCart && displayCartItems(currentCart); 67 | } 68 | 69 | function loadPreviousQtyCart() { 70 | displayCartIcon('initialState'); 71 | } 72 | ``` 73 | 74 | ## Features 75 | 76 | - Everything saves to localStorage 77 | - You can see your cart number updating as you add more items 78 | 79 | You can: 80 | 81 | - Add a product 82 | - Change quantity 83 | - Delete product 84 | 85 | ## Technology used 86 | 87 | The project is created with: 88 | 89 | - JavaScript 90 | 91 | ## Contact 92 | 93 | Coded by Cudi - feel free to contact me! 94 | -------------------------------------------------------------------------------- /js/products.js: -------------------------------------------------------------------------------- 1 | import { displayCartItems, selectElementsForCart } from './cart.js'; 2 | import { getLocalStorage, setLocalStorage } from './script.js'; 3 | 4 | const iconQty = document.querySelector('.header__icon--total'); 5 | const productContainer = document.querySelector('.products'); 6 | let currentCart; 7 | 8 | // loop thorough data and display each field 9 | function displayData(data) { 10 | data.items.forEach((item, index) => { 11 | const div = document.createElement('div'); 12 | div.classList.add('product-item'); 13 | div.id = item.sys.id; 14 | 15 | div.innerHTML = ` 16 |
17 |

${item.fields.title}

18 |
19 | product ${index + 1} 25 |
26 |
${item.fields.price} $
27 | 28 |
29 | `; 30 | 31 | productContainer.append(div); 32 | }); 33 | 34 | loadListeners(); 35 | loadPreviousCart(); 36 | loadPreviousQtyCart(); 37 | } 38 | 39 | function loadListeners() { 40 | const addCartBtn = document.querySelectorAll('.add-cart'); 41 | 42 | addCartBtn.forEach((btn) => btn.addEventListener('click', handleAddProduct)); 43 | } 44 | 45 | function loadPreviousCart() { 46 | const prevCart = getLocalStorage('currentCart'); 47 | currentCart = prevCart ? prevCart : []; 48 | prevCart && displayCartItems(currentCart); 49 | } 50 | 51 | function loadPreviousQtyCart() { 52 | displayCartIcon('initialState'); 53 | } 54 | 55 | function handleAddProduct(e) { 56 | const price = e.target.previousElementSibling.innerText.split(' ')[0]; 57 | 58 | displayCartIcon(); 59 | console.log(currentCart); 60 | addToCart(e, price); 61 | } 62 | 63 | function addToCart(e, price) { 64 | const productCard = e.target.closest('.product-item'); 65 | const [image, id, title] = selectElementsForCart(productCard); 66 | let existingItem = false; 67 | if (currentCart.length !== 0) { 68 | currentCart = currentCart.map((el) => { 69 | if (el.id === id) { 70 | existingItem = true; 71 | return { ...el, qty: el.qty + 1 }; 72 | } 73 | return { ...el }; 74 | }); 75 | } 76 | if (!existingItem) { 77 | const newArticle = { 78 | id, 79 | image, 80 | price, 81 | qty: 1, 82 | title, 83 | }; 84 | currentCart.push(newArticle); 85 | } 86 | setLocalStorage('currentCart', currentCart); 87 | } 88 | 89 | function displayCartIcon(type) { 90 | if (type !== 'initialState') { 91 | const currentQty = Number(iconQty.innerText) + 1; 92 | iconQty.innerText = currentQty; 93 | setLocalStorage('qtyCart', currentQty); 94 | } else { 95 | iconQty.innerText = getLocalStorage('qtyCart') || 0; 96 | } 97 | } 98 | 99 | export { displayData, displayCartIcon }; 100 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-primary: 'Roboto', sans-serif; 3 | --font-secondary: 'Space Mono', monospace; 4 | } 5 | html { 6 | box-sizing: border-box; 7 | scroll-behavior: smooth; 8 | font-size: 62.5%; 9 | } 10 | *, 11 | *::before, 12 | *::after { 13 | margin: 0; 14 | padding: 0; 15 | box-sizing: inherit; 16 | } 17 | body { 18 | font-family: var(--font-primary); 19 | font-size: clamp( 20 | 1.6rem, 21 | calc(1.6rem + (18 - 16) * ((100vw - 375px) / (1200 - 375))), 22 | 1.8rem 23 | ); 24 | line-height: 1.3; 25 | } 26 | a, 27 | a:visited, 28 | a:hover { 29 | text-decoration: none; 30 | } 31 | ul { 32 | list-style-type: none; 33 | } 34 | .heading-1, 35 | .heading-2, 36 | .heading-3 { 37 | font-family: var(--font-secondary); 38 | } 39 | .heading-1 { 40 | font-size: clamp( 41 | 3.2rem, 42 | calc(3.2rem + (54 - 32) * ((100vw - 375px) / (1200 - 375))), 43 | 5.4rem 44 | ); 45 | } 46 | .heading-2 { 47 | font-size: clamp( 48 | 2.4rem, 49 | calc(2.4rem + (41 - 24) * ((100vw - 375px) / (1200 - 375))), 50 | 4.1rem 51 | ); 52 | } 53 | .heading-3 { 54 | font-size: clamp( 55 | 1.9rem, 56 | calc(1.9rem + (32 - 19) * ((100vw - 375px) / (1200 - 375))), 57 | 3.2rem 58 | ); 59 | } 60 | .btn, 61 | .btn:link, 62 | .btn:visited { 63 | color: rebeccapurple; 64 | background-color: transparent; 65 | border: solid rebeccapurple 2px; 66 | border-radius: 0.4rem; 67 | line-height: 1; 68 | transition: 0.3s all ease-in-out; 69 | padding: 1rem 1.6rem; 70 | transition: all 0.3s; 71 | font-family: inherit; 72 | cursor: pointer; 73 | backface-visibility: hidden; 74 | text-transform: uppercase; 75 | } 76 | .btn:hover, 77 | .btn:link:hover, 78 | .btn:visited:hover { 79 | background-color: rebeccapurple; 80 | color: white; 81 | } 82 | .btn:focus, 83 | .btn:link:focus, 84 | .btn:visited:focus { 85 | outline: none; 86 | } 87 | .btn:active, 88 | .btn:link:active, 89 | .btn:visited:active { 90 | transform: scale(0.95); 91 | } 92 | .header { 93 | border: 1px solid rgba(0, 0, 0, 0.4); 94 | padding: 2rem 5rem; 95 | display: flex; 96 | justify-content: space-between; 97 | align-items: center; 98 | background-color: #b3adad; 99 | } 100 | .header > * { 101 | cursor: pointer; 102 | } 103 | .header__icon { 104 | position: relative; 105 | } 106 | .header__icon--total { 107 | border-radius: 30%; 108 | font-size: 1.5rem; 109 | position: absolute; 110 | bottom: -25%; 111 | right: -65%; 112 | } 113 | .container { 114 | padding: 10rem 5rem 1rem; 115 | background-color: #f8f0ff; 116 | min-height: 100vh; 117 | } 118 | .container .products { 119 | display: grid; 120 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 121 | gap: 1rem; 122 | } 123 | .container .products > * { 124 | border: 1px solid grey; 125 | text-align: center; 126 | padding: 1rem 0; 127 | } 128 | .container .products > * img { 129 | max-width: 100%; 130 | padding: 1rem 0; 131 | } 132 | .shoppingCartApp { 133 | display: none; 134 | } 135 | .cartItem { 136 | display: flex; 137 | align-items: center; 138 | gap: 5rem; 139 | margin: 6rem 0 8rem; 140 | } 141 | .cartInfo > * { 142 | margin-bottom: 1rem; 143 | } 144 | .item img { 145 | max-width: 15rem; 146 | width: 100%; 147 | } 148 | .item .buttons-action { 149 | display: flex; 150 | gap: 5vw; 151 | justify-content: center; 152 | align-items: center; 153 | } 154 | input[type='number'] { 155 | height: 30px; 156 | } 157 | input[type='number']:hover::-webkit-inner-spin-button { 158 | width: 14px; 159 | height: 30px; 160 | } 161 | -------------------------------------------------------------------------------- /js/cart.js: -------------------------------------------------------------------------------- 1 | import { getLocalStorage, setLocalStorage } from './script.js'; 2 | import { displayCartIcon } from './products.js'; 3 | 4 | const currentCartItemsContainer = document.querySelector('.item'); 5 | let totalPrice = document.querySelector('.totalPrice'); 6 | let currentPrice = []; 7 | 8 | function selectElementsForCart(card) { 9 | const image = card.querySelector('img').src; 10 | const id = card.id; 11 | const title = card.querySelector('h3').innerText; 12 | 13 | return [image, id, title]; 14 | } 15 | 16 | function displayCartItems(currentCart) { 17 | currentCartItemsContainer.innerHTML = ''; 18 | 19 | currentPrice = []; 20 | currentCart.forEach((item) => { 21 | const currentQtyPrice = Number(item.price) * item.qty; 22 | currentPrice.push(currentQtyPrice); 23 | 24 | currentCartItemsContainer.innerHTML += ` 25 |
26 |
27 |

${item.title}

28 |
29 | product 1 35 |
36 |
${item.price} $
37 |
38 |
39 |
40 | 41 | 51 |
52 | 53 |
54 |
55 | `; 56 | }); 57 | 58 | totalPrice.innerText = currentPrice 59 | .reduce((curr, acc) => curr + acc, 0) 60 | .toFixed(2); 61 | 62 | loadCartListeners(); 63 | } 64 | 65 | function loadCartListeners() { 66 | const deleteBtn = document.querySelectorAll('.deleteBtn'); 67 | const qtyInput = document.querySelectorAll('.quantity'); 68 | const form = document.querySelectorAll('.form'); 69 | 70 | deleteBtn.forEach((btn) => btn.addEventListener('click', handleDelete)); 71 | form.forEach((el) => 72 | el.addEventListener('submit', (e) => e.preventDefault()) 73 | ); 74 | qtyInput.forEach((btn) => btn.addEventListener('change', handleChange)); 75 | } 76 | 77 | function handleDelete(e) { 78 | const id = e.target.closest('.cartItem').id; 79 | const qty = e.target.previousElementSibling.querySelector('.quantity').value; 80 | 81 | const currentCart = getLocalStorage('currentCart'); 82 | const currentQty = getLocalStorage('qtyCart'); 83 | 84 | const filteredCart = currentCart.filter((product) => product.id !== id); 85 | const filteredQty = currentQty - qty; 86 | 87 | setLocalStorage('currentCart', filteredCart); 88 | setLocalStorage('qtyCart', filteredQty); 89 | } 90 | 91 | function handleChange(e) { 92 | const newQty = e.target.closest('.quantity').value; 93 | const id = e.target.closest('.cartItem').id; 94 | 95 | const currentCart = getLocalStorage('currentCart'); 96 | 97 | const filteredCart = currentCart.map((product) => 98 | product.id === id ? { ...product, qty: newQty } : { ...product } 99 | ); 100 | 101 | const filteredQty = filteredCart.reduce( 102 | (acc, curr) => acc + Number(curr.qty), 103 | 0 104 | ); 105 | 106 | setLocalStorage('currentCart', filteredCart); 107 | setLocalStorage('qtyCart', filteredQty); 108 | } 109 | 110 | export { displayCartItems, selectElementsForCart }; 111 | -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | --------------------------------------------------------------------------------