├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.css ├── App.js ├── containers ├── Header.jsx ├── ProductComponent.jsx ├── ProductDetail.jsx └── ProductListing.jsx ├── index.js └── redux ├── actions └── productActions.js ├── contants └── action-types.js ├── reducers ├── index.js └── productReducer.js └── store.js /README.md: -------------------------------------------------------------------------------- 1 | # My Very First Redux Project 2 | 3 | ### Fake shopping website 4 | 5 | * React 6 | * Redux 7 | * FetchAPI 8 | * Semantic UI 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake-shop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.2", 11 | "react-dom": "^17.0.2", 12 | "react-redux": "^7.2.4", 13 | "react-router-dom": "^5.2.1", 14 | "react-scripts": "4.0.3", 15 | "redux": "^4.1.1", 16 | "web-vitals": "^1.1.2" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EienMosu/React-Redux-ExampleShopProject/bdefbc783c0dc652b6b08da060164ae7e4e51eae/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | React App 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: "Roboto", sans-serif; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | .ui.cards > .card > .image { 9 | height: 250px; 10 | padding: 20px; 11 | background: #fff; 12 | margin: auto; 13 | } 14 | .ui.cards > .card > .image > img { 15 | height: 100%; 16 | max-width: 100%; 17 | width: auto; 18 | } 19 | .ui.cards > .card > .content > .header { 20 | height: 48px; 21 | overflow: hidden; 22 | margin-bottom: 5px; 23 | } 24 | .ui.cards > .card > .content > .description { 25 | height: 36px; 26 | margin-bottom: 0px; 27 | overflow: hidden; 28 | } 29 | .ui.cards > .card .meta.price { 30 | margin-bottom: 5px; 31 | font-size: 18px; 32 | color: #333; 33 | font-weight: 600; 34 | } 35 | 36 | .ui.cards > .card .meta.price > a { 37 | font-size: 1.3rem; 38 | color: #222; 39 | } 40 | 41 | .ui.menu.fixed { 42 | height: 60px; 43 | padding-top: 15px; 44 | } 45 | 46 | .ui.grid.container { 47 | margin-top: 45px; 48 | } 49 | 50 | .ui.grid > .row { 51 | background: #fff; 52 | } 53 | .ui.grid > .row > .column.lp { 54 | padding: 20px 40px 20px 20px; 55 | align-self: flex-start !important; 56 | } 57 | .ui.grid > .row > .column.rp { 58 | padding: 20px 20px 20px 40px; 59 | text-align: left; 60 | align-self: flex-start !important; 61 | } 62 | 63 | .ui.grid > .row > .column > img, 64 | .ui.grid > .row > img { 65 | height: 100%; 66 | } 67 | .ui.placeholder .header:not(:first-child):before, 68 | .ui.placeholder .image:not(:first-child):before, 69 | .ui.placeholder .paragraph:not(:first-child):before { 70 | display: none; 71 | } 72 | 73 | .ui.label, 74 | .ui.labels .label { 75 | font-size: 22px; 76 | } 77 | 78 | .column.rp h1 { 79 | color: #333; 80 | } 81 | .column.rp p { 82 | font-size: 18px; 83 | color: #777; 84 | } 85 | .ui.placeholder.segment .column .button, 86 | .ui.placeholder.segment .column .field, 87 | .ui.placeholder.segment .column textarea, 88 | .ui.placeholder.segment .column > .ui.input { 89 | background-color: #ff3e6c; 90 | border: 1px solid #ff3e6c; 91 | color: #fff; 92 | font-size: 18px; 93 | margin-left: 0; 94 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 3 | import "./App.css"; 4 | import Header from "./containers/Header"; 5 | import ProductDetail from "./containers/ProductDetail"; 6 | import ProductListing from "./containers/ProductListing"; 7 | 8 | function App() { 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 | 16 | 404 Not Found! 17 | 18 | 19 |
20 | ); 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /src/containers/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Header = () => { 4 | return ( 5 |
6 |
7 |

FakeShop

8 |
9 |
10 | ); 11 | }; 12 | 13 | export default Header; 14 | -------------------------------------------------------------------------------- /src/containers/ProductComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const ProductComponent = () => { 6 | const products = useSelector((state) => state.allProducts.products); 7 | const renderList = products.map((product) => { 8 | const { id, title, image, price, category } = product; 9 | 10 | return ( 11 |
12 | 13 |
14 |
15 |
16 | {title} 17 |
18 |
19 |
{title}
20 |
$ {price}
21 |
{category}
22 |
23 |
24 |
25 | 26 |
27 | ); 28 | }); 29 | 30 | return <>{renderList}; 31 | }; 32 | 33 | export default ProductComponent; 34 | -------------------------------------------------------------------------------- /src/containers/ProductDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import axios from "axios"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { 6 | selectedProduct, 7 | removeSelectedProduct, 8 | } from "../redux/actions/productActions"; 9 | 10 | const ProductDetail = () => { 11 | const product = useSelector((state) => state.product); 12 | const { title, image, price, category, description } = product; 13 | const { productId } = useParams(); 14 | const dispatch = useDispatch(); 15 | 16 | console.log(product); 17 | 18 | const fetchProductDetail = async () => { 19 | const response = await axios 20 | .get(`https://fakestoreapi.com/products/${productId}`) 21 | .catch((err) => { 22 | console.log("Err ", err); 23 | }); 24 | dispatch(selectedProduct(response.data)); 25 | }; 26 | 27 | useEffect(() => { 28 | if (productId && productId !== "") fetchProductDetail(); 29 | 30 | return () => { 31 | dispatch(removeSelectedProduct()); 32 | }; 33 | }, [productId]); 34 | 35 | return ( 36 |
37 | {Object.keys(product).length === 0 ? ( 38 |
...Loading
39 | ) : ( 40 |
41 |
42 |
AND
43 |
44 |
45 | 46 |
47 |
48 |

{title}

49 |

50 | ${price} 51 |

52 |

{category}

53 |

{description}

54 |
55 |
56 | 57 |
58 |
Add to Cart
59 |
60 |
61 |
62 |
63 |
64 | )} 65 |
66 | ); 67 | }; 68 | 69 | export default ProductDetail; 70 | -------------------------------------------------------------------------------- /src/containers/ProductListing.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { setProducts } from "../redux/actions/productActions"; 4 | import axios from "axios"; 5 | import ProductComponent from "./ProductComponent"; 6 | 7 | const ProductListing = () => { 8 | const products = useSelector((state) => state); 9 | const dispatch = useDispatch(); 10 | 11 | const fetchProducts = async () => { 12 | const response = await axios 13 | .get("https://fakestoreapi.com/products") 14 | .catch((err) => { 15 | console.log("Err", err); 16 | }); 17 | dispatch(setProducts(response.data)); 18 | }; 19 | 20 | 21 | useEffect(() => { 22 | fetchProducts(); 23 | }, []); 24 | console.log("Products: ", products); 25 | return ( 26 |
27 | 28 |
29 | ); 30 | }; 31 | 32 | export default ProductListing; 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import store from "./redux/store"; 5 | import App from "./App"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | -------------------------------------------------------------------------------- /src/redux/actions/productActions.js: -------------------------------------------------------------------------------- 1 | import { ActionTypes } from "../contants/action-types"; 2 | 3 | export const setProducts = (products) => { 4 | return { 5 | type: ActionTypes.SET_PRODUCTS, 6 | payload: products, 7 | }; 8 | }; 9 | 10 | export const selectedProduct = (product) => { 11 | return { 12 | type: ActionTypes.SELECTED_PRODUCT, 13 | payload: product, 14 | }; 15 | }; 16 | 17 | export const removeSelectedProduct = () => { 18 | return { 19 | type: ActionTypes.REMOVE_SELECTED_PRODUCT, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/redux/contants/action-types.js: -------------------------------------------------------------------------------- 1 | export const ActionTypes = { 2 | SET_PRODUCTS: "SET_PRODUCTS", 3 | SELECTED_PRODUCT: "SELECTED_ PRODUCT", 4 | REMOVE_SELECTED_PRODUCT: "REMOVE_SELECTED_PRODUCT", 5 | }; 6 | -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import { productReducer, selectedProductReducer } from "./productReducer"; 3 | 4 | const reducers = combineReducers({ 5 | allProducts: productReducer, 6 | product: selectedProductReducer 7 | }); 8 | 9 | export default reducers; 10 | -------------------------------------------------------------------------------- /src/redux/reducers/productReducer.js: -------------------------------------------------------------------------------- 1 | import { ActionTypes } from "../contants/action-types"; 2 | 3 | const initialState = { 4 | products: [], 5 | }; 6 | 7 | export const productReducer = (state = initialState, { type, payload }) => { 8 | switch (type) { 9 | case ActionTypes.SET_PRODUCTS: 10 | return { ...state, products: payload }; 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export const selectedProductReducer = (state = {}, { type, payload }) => { 17 | switch (type) { 18 | case ActionTypes.SELECTED_PRODUCT: 19 | return { ...state, ...payload }; 20 | 21 | case ActionTypes.remove_SELECTED_PRODUCT: 22 | return {}; 23 | 24 | default: 25 | return state; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "redux"; 2 | import reducers from "./reducers/index"; 3 | 4 | const store = createStore( 5 | reducers, 6 | {}, 7 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 8 | ); 9 | 10 | export default store; 11 | --------------------------------------------------------------------------------