├── 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 |
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 |

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 |
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 |
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 |
--------------------------------------------------------------------------------