├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── db.json
├── demo.gif
├── favicon.ico
├── index.html
├── manifest.json
└── products
│ ├── 10547961582846888_1.jpg
│ ├── 10547961582846888_2.jpg
│ ├── 11600983276356164_1.jpg
│ ├── 11600983276356164_2.jpg
│ ├── 11854078013954528_1.jpg
│ ├── 11854078013954528_2.jpg
│ ├── 18532669286405344_1.jpg
│ ├── 18532669286405344_2.jpg
│ ├── 18644119330491310_1.jpg
│ ├── 18644119330491310_2.jpg
│ ├── 5619496040738316_1.jpg
│ ├── 5619496040738316_2.jpg
│ ├── 6090484789343891_1.jpg
│ ├── 6090484789343891_2.jpg
│ ├── 876661122392077_1.jpg
│ ├── 876661122392077_2.jpg
│ ├── 9197907543445676_1.jpg
│ └── 9197907543445676_2.jpg
└── src
├── App.css
├── App.js
├── App.test.js
├── actions
├── cartActions.js
├── productActions.js
└── types.js
├── components
├── Basket.js
├── Copyright.js
├── Filter.js
└── Products.js
├── index.css
├── index.js
├── logo.svg
├── reducers
├── cartReducers.js
├── index.js
└── productReducers.js
├── serviceWorker.js
├── store.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | yarn.lock
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # E-commerce Shopping Cart Application
2 | Learn React & Redux From 0 to 100 in this course at https://codingwithbasir.com
3 |
4 | ## Demo
5 | Open [Shopping Cart Demo](https://basir.github.io/ecommerce-shopping-cart/)
6 |
7 | 
8 |
9 | ## Install
10 | ```
11 | $ clone git@github.com:basir/ecommerce-shopping-cart.git
12 | $ yarn
13 | ```
14 |
15 | ## Run Server
16 | ```
17 | $ npm install -g json-server
18 | Then
19 | $ yarn server
20 | or
21 | $ json-server public/db.json --port 8000
22 | ```
23 |
24 | ## Run App
25 | ```
26 | $ yarn start
27 | ```
28 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce-shopping-cart",
3 | "version": "0.1.0",
4 | "homepage": "https://basir.github.io/ecommerce-shopping-cart",
5 | "private": true,
6 | "dependencies": {
7 | "cors": "^2.8.5",
8 | "express": "^4.16.4",
9 | "gh-pages": "^2.0.1",
10 | "react": "^16.8.4",
11 | "react-dom": "^16.8.4",
12 | "react-redux": "^7.2.0",
13 | "react-scripts": "2.1.8",
14 | "redux": "^4.0.5",
15 | "redux-thunk": "^2.3.0"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "server": "json-server public/db.json --port 8000",
20 | "build": "react-scripts build",
21 | "predeploy": "yarn run build",
22 | "deploy": "gh-pages -d build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": "react-app"
28 | },
29 | "browserslist": [
30 | ">0.2%",
31 | "not dead",
32 | "not ie <= 11",
33 | "not op_mini all"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/public/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "products": [
3 | {
4 | "id": 1,
5 | "sku": 18644119330491312,
6 | "title": "Sphynx Tie Dye Grey T-Shirt",
7 | "description": "Sphynx Tie Dye Grey",
8 | "availableSizes": ["X", "L", "XL", "XXL"],
9 | "price": 10,
10 | "isFreeShipping": true
11 | },
12 |
13 | {
14 | "id": 2,
15 | "sku": 11854078013954528,
16 | "title": "Danger Knife Grey T-Shirt",
17 | "description": "Danger Knife Grey",
18 | "availableSizes": ["X", "M", "L"],
19 | "price": 14.9,
20 | "isFreeShipping": true
21 | },
22 |
23 | {
24 | "id": 3,
25 | "sku": 876661122392077,
26 | "title": "White DGK Script Tee",
27 | "description": "White DGK Script",
28 | "availableSizes": ["X", "M", "L"],
29 | "price": 14.9,
30 | "isFreeShipping": true
31 | },
32 |
33 | {
34 | "id": 4,
35 | "sku": 9197907543445677,
36 | "title": "Born On The Streets T-Shirt",
37 | "description": "Born On The Streets",
38 | "availableSizes": ["XL"],
39 | "price": 25.9,
40 | "isFreeShipping": false
41 | },
42 |
43 | {
44 | "id": 5,
45 | "sku": 10547961582846888,
46 | "title": "Tso 3D Short Sleeve T-Shirt A",
47 | "description": "Tso 3D Short Sleeve",
48 | "availableSizes": ["X", "L", "XL"],
49 | "price": 10.9,
50 | "isFreeShipping": false
51 | },
52 |
53 | {
54 | "id": 6,
55 | "sku": 6090484789343891,
56 | "title": "Man Tie Dye Cinza Grey T-Shirt",
57 | "description": "Man Tie Dye Cinza Grey",
58 | "availableSizes": ["XL", "XXL"],
59 | "price": 49.9,
60 | "isFreeShipping": false
61 | },
62 |
63 | {
64 | "id": 7,
65 | "sku": 18532669286405342,
66 | "title": "Crazy Monkey Black T-Shirt",
67 | "description": "1977 Infantil",
68 | "availableSizes": ["S"],
69 | "style": "Preto com listras brancas",
70 | "price": 22.5,
71 | "isFreeShipping": true
72 | },
73 |
74 | {
75 | "id": 8,
76 | "sku": 5619496040738316,
77 | "title": "Tso 3D Black T-Shirt",
78 | "description": "",
79 | "availableSizes": ["XL"],
80 | "style": "Azul escuro",
81 | "price": 18.7,
82 | "isFreeShipping": false
83 | },
84 | {
85 | "id": 9,
86 | "sku": 11600983276356165,
87 | "title": "Crazy Monkey Grey",
88 | "description": "",
89 | "availableSizes": ["L", "XL"],
90 | "price": 134.9,
91 | "isFreeShipping": true
92 | }
93 | ]
94 | }
95 |
--------------------------------------------------------------------------------
/public/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/demo.gif
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
25 |
26 | React App
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/products/10547961582846888_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/10547961582846888_1.jpg
--------------------------------------------------------------------------------
/public/products/10547961582846888_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/10547961582846888_2.jpg
--------------------------------------------------------------------------------
/public/products/11600983276356164_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/11600983276356164_1.jpg
--------------------------------------------------------------------------------
/public/products/11600983276356164_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/11600983276356164_2.jpg
--------------------------------------------------------------------------------
/public/products/11854078013954528_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/11854078013954528_1.jpg
--------------------------------------------------------------------------------
/public/products/11854078013954528_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/11854078013954528_2.jpg
--------------------------------------------------------------------------------
/public/products/18532669286405344_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/18532669286405344_1.jpg
--------------------------------------------------------------------------------
/public/products/18532669286405344_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/18532669286405344_2.jpg
--------------------------------------------------------------------------------
/public/products/18644119330491310_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/18644119330491310_1.jpg
--------------------------------------------------------------------------------
/public/products/18644119330491310_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/18644119330491310_2.jpg
--------------------------------------------------------------------------------
/public/products/5619496040738316_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/5619496040738316_1.jpg
--------------------------------------------------------------------------------
/public/products/5619496040738316_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/5619496040738316_2.jpg
--------------------------------------------------------------------------------
/public/products/6090484789343891_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/6090484789343891_1.jpg
--------------------------------------------------------------------------------
/public/products/6090484789343891_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/6090484789343891_2.jpg
--------------------------------------------------------------------------------
/public/products/876661122392077_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/876661122392077_1.jpg
--------------------------------------------------------------------------------
/public/products/876661122392077_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/876661122392077_2.jpg
--------------------------------------------------------------------------------
/public/products/9197907543445676_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/9197907543445676_1.jpg
--------------------------------------------------------------------------------
/public/products/9197907543445676_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/ecommerce-shopping-cart/9ff8a4242e4072ee774c35cf1c9d139e3c013a31/public/products/9197907543445676_2.jpg
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | .App-header {
12 | background-color: #282c34;
13 | min-height: 100vh;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | justify-content: center;
18 | font-size: calc(10px + 2vmin);
19 | color: white;
20 | }
21 |
22 | .App-link {
23 | color: #61dafb;
24 | }
25 |
26 | @keyframes App-logo-spin {
27 | from {
28 | transform: rotate(0deg);
29 | }
30 | to {
31 | transform: rotate(360deg);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Provider } from "react-redux";
3 | import Products from "./components/Products";
4 | import Filter from "./components/Filter";
5 | import Basket from "./components/Basket";
6 | import store from "./store";
7 | import "./App.css";
8 | import Copyright from "./components/Copyright";
9 |
10 | class App extends Component {
11 | render() {
12 | return (
13 |
14 |
15 |
E-commerce Shopping Cart Application
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/actions/cartActions.js:
--------------------------------------------------------------------------------
1 | import { ADD_TO_CART, REMOVE_FROM_CART } from "./types";
2 |
3 | export const addToCart = (items, product) => (dispatch) => {
4 | const cartItems = items.slice();
5 | let productAlreadyInCart = false;
6 |
7 | cartItems.forEach((cp) => {
8 | if (cp.id === product.id) {
9 | cp.count += 1;
10 | productAlreadyInCart = true;
11 | }
12 | });
13 |
14 | if (!productAlreadyInCart) {
15 | cartItems.push({ ...product, count: 1 });
16 | }
17 | localStorage.setItem("cartItems", JSON.stringify(cartItems));
18 | dispatch({ type: ADD_TO_CART, payload: { cartItems } });
19 | };
20 |
21 | export const removeFromCart = (items, product) => (dispatch) => {
22 | const cartItems = items.slice().filter((a) => a.id !== product.id);
23 | localStorage.setItem("cartItems", JSON.stringify(cartItems));
24 | dispatch({ type: REMOVE_FROM_CART, payload: { cartItems } });
25 | };
26 |
--------------------------------------------------------------------------------
/src/actions/productActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | FETCH_PRODUCTS,
3 | FILTER_PRODUCTS_BY_SIZE,
4 | ORDER_PRODUCTS_BY_PRICE,
5 | } from "./types";
6 |
7 | export const fetchProducts = () => (dispatch) => {
8 | fetch("http://localhost:8000/products")
9 | .then((res) => res.json())
10 | .catch((err) =>
11 | fetch("db.json")
12 | .then((res) => res.json())
13 | .then((data) => data.products)
14 | )
15 | .then((data) => {
16 | dispatch({ type: FETCH_PRODUCTS, payload: data });
17 | });
18 | };
19 |
20 | export const filterProducts = (products, size) => (dispatch) => {
21 | dispatch({
22 | type: FILTER_PRODUCTS_BY_SIZE,
23 | payload: {
24 | size: size,
25 | items:
26 | size === ""
27 | ? products
28 | : products.filter(
29 | (x) => x.availableSizes.indexOf(size.toUpperCase()) >= 0
30 | ),
31 | },
32 | });
33 | };
34 |
35 | export const sortProducts = (items, sort) => (dispatch) => {
36 | const products = items.slice();
37 | if (sort !== "") {
38 | products.sort((a, b) =>
39 | sort === "lowestprice"
40 | ? a.price > b.price
41 | ? 1
42 | : -1
43 | : a.price < b.price
44 | ? 1
45 | : -1
46 | );
47 | } else {
48 | products.sort((a, b) => (a.id > b.id ? 1 : -1));
49 | }
50 | dispatch({
51 | type: ORDER_PRODUCTS_BY_PRICE,
52 | payload: {
53 | sort: sort,
54 | items: products,
55 | },
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const FETCH_PRODUCTS = "FETCH_PRODUCTS";
2 | export const FILTER_PRODUCTS_BY_SIZE = "FILTER_PRODUCTS_BY_SIZE";
3 | export const ORDER_PRODUCTS_BY_PRICE = "ORDER_PRODUCTS_BY_PRICE";
4 | export const ADD_TO_CART = "ADD_TO_CART";
5 | export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
6 |
--------------------------------------------------------------------------------
/src/components/Basket.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import util from "../util";
4 | import { addToCart, removeFromCart } from "../actions/cartActions";
5 | class Basket extends Component {
6 | render() {
7 | const { cartItems } = this.props;
8 |
9 | return (
10 |
11 | {cartItems.length === 0 ? (
12 | "Basket is empty"
13 | ) : (
14 |
15 | You have {cartItems.length} items in the basket.
16 |
17 | )}
18 | {cartItems.length > 0 && (
19 |
20 |
21 | {cartItems.map((item) => (
22 | -
23 | {item.title}
24 |
33 |
34 | {item.count} X {util.formatCurrency(item.price)}
35 |
36 | ))}
37 |
38 |
39 |
40 | Sum:{" "}
41 | {util.formatCurrency(
42 | cartItems.reduce((a, c) => a + c.price * c.count, 0)
43 | )}
44 |
45 |
51 |
52 | )}
53 |
54 | );
55 | }
56 | }
57 | const mapStateToProps = (state) => ({
58 | cartItems: state.cart.items,
59 | });
60 | export default connect(mapStateToProps, { addToCart, removeFromCart })(Basket);
61 |
--------------------------------------------------------------------------------
/src/components/Copyright.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | export default class Copyright extends Component {
3 | render() {
4 | return (
5 |
6 |
IMPORTANT LINKS
7 |
24 |
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Filter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { filterProducts, sortProducts } from "../actions/productActions";
4 | class Filter extends Component {
5 | render() {
6 | return (
7 |
8 |
{`${this.props.filteredProducts.length} products found.`}
9 |
10 |
27 |
28 |
29 |
51 |
52 |
53 | );
54 | }
55 | }
56 | const mapStateToProps = (state) => ({
57 | products: state.products.items,
58 | filteredProducts: state.products.filteredItems,
59 | size: state.products.size,
60 | sort: state.products.sort,
61 | });
62 | export default connect(mapStateToProps, { filterProducts, sortProducts })(
63 | Filter
64 | );
65 |
--------------------------------------------------------------------------------
/src/components/Products.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import util from "../util";
4 | import { addToCart } from "../actions/cartActions";
5 | import { fetchProducts } from "../actions/productActions";
6 | class Products extends Component {
7 | componentDidMount() {
8 | this.props.fetchProducts();
9 | }
10 | render() {
11 | const productItems = this.props.products.map((product) => (
12 |
30 | ));
31 |
32 | return {productItems}
;
33 | }
34 | }
35 | const mapStateToProps = (state) => ({
36 | products: state.products.filteredItems,
37 | cartItems: state.cart.items,
38 | });
39 | export default connect(mapStateToProps, { fetchProducts, addToCart })(Products);
40 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/reducers/cartReducers.js:
--------------------------------------------------------------------------------
1 | import { ADD_TO_CART, REMOVE_FROM_CART } from "../actions/types";
2 |
3 | export default function (state = {}, action) {
4 | switch (action.type) {
5 | case ADD_TO_CART:
6 | return { ...state, items: action.payload.cartItems };
7 | case REMOVE_FROM_CART:
8 | return { ...state, items: action.payload.cartItems };
9 |
10 | default:
11 | return state;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import productReducers from "./productReducers";
3 | import cartReducers from "./cartReducers";
4 |
5 | export default combineReducers({
6 | products: productReducers,
7 | cart: cartReducers,
8 | });
9 |
--------------------------------------------------------------------------------
/src/reducers/productReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | FETCH_PRODUCTS,
3 | FILTER_PRODUCTS_BY_SIZE,
4 | ORDER_PRODUCTS_BY_PRICE,
5 | } from "../actions/types";
6 |
7 | const initState = { items: [], filteredItems: [], size: "", sort: "" };
8 | export default function (state = initState, action) {
9 | switch (action.type) {
10 | case FETCH_PRODUCTS:
11 | return { ...state, items: action.payload, filteredItems: action.payload };
12 | case FILTER_PRODUCTS_BY_SIZE:
13 | return {
14 | ...state,
15 | filteredItems: action.payload.items,
16 | size: action.payload.size,
17 | };
18 | case ORDER_PRODUCTS_BY_PRICE:
19 | return {
20 | ...state,
21 | filteredItems: action.payload.items,
22 | sort: action.payload.sort,
23 | };
24 |
25 | default:
26 | return state;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from "redux";
2 | import thunk from "redux-thunk";
3 | import rootReducers from "./reducers";
4 |
5 | const cartItems = localStorage.getItem("cartItems")
6 | ? JSON.parse(localStorage.getItem("cartItems"))
7 | : [];
8 | const initState = { cart: { items: cartItems } };
9 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
10 | const store = createStore(
11 | rootReducers,
12 | initState,
13 | composeEnhancer(applyMiddleware(thunk))
14 | );
15 | export default store;
16 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | export default {
2 | formatCurrency: function (num) {
3 | return '$' + Number(num.toFixed(1)).toLocaleString() + ' ';
4 | }
5 | }
--------------------------------------------------------------------------------