├── 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 |
32 |
33 |
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 | 
8 | 
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 |

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 |

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 |

35 |
36 |
${item.price} $
37 |
38 |
39 |
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 |
5 |
--------------------------------------------------------------------------------