├── frontend
├── src
│ ├── App.css
│ ├── index.css
│ ├── components
│ │ ├── shared
│ │ │ ├── Message.jsx
│ │ │ ├── FromContainer.jsx
│ │ │ ├── Loader.jsx
│ │ │ └── CheckoutStep.jsx
│ │ ├── footer.jsx
│ │ ├── Rating.jsx
│ │ └── Header.jsx
│ ├── setupTests.js
│ ├── constants
│ │ ├── cartConstant.js
│ │ ├── productConstant.js
│ │ ├── orderConstant.js
│ │ └── userContants.js
│ ├── App.test.js
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── screens
│ │ ├── ProductScreen.jsx
│ │ ├── HomeScreen.jsx
│ │ ├── PaymentScreen.jsx
│ │ ├── LoginScreen.jsx
│ │ ├── ShippingScreen.jsx
│ │ ├── ProductDetails.jsx
│ │ ├── RegisterScreen.jsx
│ │ ├── CartScreen.jsx
│ │ ├── PlaceOrderScreen.jsx
│ │ ├── ProfileScreen.jsx
│ │ └── OrderScreen.jsx
│ ├── reducers
│ │ ├── productReducer.js
│ │ ├── cartReducer.js
│ │ ├── userReducers.js
│ │ └── orderReducer.js
│ ├── actions
│ │ ├── productActions.js
│ │ ├── cartAction.js
│ │ ├── orderAction.js
│ │ └── userAction.js
│ ├── App.js
│ ├── store.js
│ ├── logo.svg
│ └── products.js
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── images
│ │ ├── alexa.jpg
│ │ ├── camera.jpg
│ │ ├── mouse.jpg
│ │ ├── boatHeadfone.jpg
│ │ ├── micromaxInB.jpg
│ │ └── playstation.jpg
│ ├── manifest.json
│ └── index.html
├── package.json
├── README.md
└── .eslintcache
├── backend
├── utils
│ └── generateToken.js
├── middlewares
│ ├── errorMiddleware.js
│ └── authMiddleware.js
├── routes
│ ├── productsRoute.js
│ ├── orderRoute.js
│ └── UsersRoute.js
├── data
│ ├── users.js
│ ├── products.js
│ └── books.js
├── config
│ └── config.js
├── controllers
│ ├── productsController.js
│ ├── orderController.js
│ └── usersController.js
├── models
│ ├── UserModel.js
│ ├── ProductModel.js
│ └── OrderModel.js
├── server.js
└── seeder.js
├── README.md
├── .gitignore
└── package.json
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | main {
2 | min-height: 80vh;
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/images/alexa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/alexa.jpg
--------------------------------------------------------------------------------
/frontend/public/images/camera.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/camera.jpg
--------------------------------------------------------------------------------
/frontend/public/images/mouse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/mouse.jpg
--------------------------------------------------------------------------------
/frontend/public/images/boatHeadfone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/boatHeadfone.jpg
--------------------------------------------------------------------------------
/frontend/public/images/micromaxInB.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/micromaxInB.jpg
--------------------------------------------------------------------------------
/frontend/public/images/playstation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/playstation.jpg
--------------------------------------------------------------------------------
/backend/utils/generateToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | const generateToken = (id) => {
4 | return jwt.sign({ id }, process.env.JWT_KEY, {
5 | expiresIn: "15d",
6 | });
7 | };
8 |
9 | module.exports = generateToken;
10 |
--------------------------------------------------------------------------------
/frontend/src/components/shared/Message.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Alert } from "react-bootstrap";
3 |
4 | const Message = ({ variant, children }) => {
5 | return {children} ;
6 | };
7 |
8 | export default Message;
9 |
--------------------------------------------------------------------------------
/frontend/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/frontend/src/constants/cartConstant.js:
--------------------------------------------------------------------------------
1 | export const CART_ADD_ITEM = "CART_ADD_ITEM";
2 | export const CART_REMOVE_ITEM = "CART_REMOVE_ITEM";
3 | export const CART_SAVE_SHIPPING_ADDRESS = "CART_SAVE_SHIPPING_ADDRESS";
4 | export const CART_SAVE_PAYMENT_METHOD = "CART_SAVE_PAYMENT_METHOD";
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ecommerce-mern-project-code
2 | complete ecommerce app project
3 |
4 | @
5 | # watch videos on youtube channel
6 | https://youtube.com/playlist?list=PLuHGmgpyHfRw6vj1vd_Pf0Zjd2muTvjI5
7 |
8 | # you can fork or zip
9 | # please give credit add our channel name or channel link
10 |
--------------------------------------------------------------------------------
/frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/backend/middlewares/errorMiddleware.js:
--------------------------------------------------------------------------------
1 | const errorHandler = (err, req, res, next) => {
2 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
3 | res.status(statusCode);
4 | res.json({
5 | message: err.message,
6 | stack: process.env.NODE_ENV === "production" ? null : err.stack,
7 | });
8 | };
9 | module.exports = { errorHandler };
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/backend/routes/productsRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | getProduct,
4 | getProducts,
5 | } = require("../controllers/productsController");
6 | const router = express.Router();
7 |
8 | //GET ROUTE FOR ALL PRODUCTS
9 | router.route("/products").get(getProducts);
10 |
11 | //GET ROUTE FOR SINGLE PRODUCT
12 | router.route("/products/:id").get(getProduct);
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/frontend/src/constants/productConstant.js:
--------------------------------------------------------------------------------
1 | export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST";
2 | export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS";
3 | export const PRODUCT_LIST_FAILS = "PRODUCT_LIST_FAILS";
4 |
5 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST";
6 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS";
7 | export const PRODUCT_DETAILS_FAILS = "PRODUCT_DETAILS_FAILS";
8 |
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/shared/FromContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Container, Row, Col } from "react-bootstrap";
4 |
5 | const FromContainer = ({ children }) => {
6 | return (
7 |
8 |
9 |
10 | {children}
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default FromContainer;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Container, Row, Col } from "react-bootstrap";
3 | const footer = () => {
4 | return (
5 | <>
6 |
7 |
8 |
9 |
10 | Copyright © Techinfo YT
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
19 | export default footer;
20 |
--------------------------------------------------------------------------------
/backend/data/users.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcryptjs");
2 | const users = [
3 | {
4 | name: "admin",
5 | email: "admin@admin.com",
6 | password: bcrypt.hashSync("123456", 10),
7 | isAdmin: true,
8 | },
9 | {
10 | name: "techinfoyt",
11 | email: "techinfoyt@xyz.com",
12 | password: bcrypt.hashSync("123456", 10),
13 | },
14 | {
15 | name: "user",
16 | email: "user@user.com",
17 | password: bcrypt.hashSync("123456", 10),
18 | },
19 | ];
20 | module.exports = users;
21 |
--------------------------------------------------------------------------------
/frontend/src/components/shared/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Spinner } from "react-bootstrap";
3 |
4 | const Loader = () => {
5 | return (
6 |
16 | Loading...
17 |
18 | );
19 | };
20 |
21 | export default Loader;
22 |
--------------------------------------------------------------------------------
/backend/config/config.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | require("colors");
3 | const connectDb = async () => {
4 | try {
5 | const conn = await mongoose.connect(process.env.MONGO_URI, {
6 | useUnifiedTopology: true,
7 | useNewUrlParser: true,
8 | useCreateIndex: true,
9 | });
10 | console.log(`Mongodb Connected ${conn.connection.host}`.yellow);
11 | } catch (error) {
12 | console.error(`Error : ${error.message}`.red);
13 | process.exit(1);
14 | }
15 | };
16 |
17 | module.exports = connectDb;
18 |
--------------------------------------------------------------------------------
/frontend/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/backend/routes/orderRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | addOrderItem,
4 | getOrderById,
5 | updateOrderToPaid,
6 | getMyOrders,
7 | } = require("../controllers/orderController");
8 | const { protect } = require("../middlewares/authMiddleware");
9 | const router = express.Router();
10 |
11 | //getUserOrder
12 | router.route("/myorders").get(protect, getMyOrders);
13 | //get order by id
14 | router.route("/:id").get(protect, getOrderById);
15 | //craete new order
16 | router.route("/").post(protect, addOrderItem);
17 | //update order
18 | router.route("/:id/pay").put(protect, updateOrderToPaid);
19 | module.exports = router;
20 |
--------------------------------------------------------------------------------
/backend/controllers/productsController.js:
--------------------------------------------------------------------------------
1 | const Product = require("../models/ProductModel");
2 | const asyncHandler = require("express-async-handler");
3 |
4 | const getProducts = asyncHandler(async (req, res) => {
5 | const products = await Product.find({});
6 | // throw new Error("Some Eror");
7 | res.json(products);
8 | });
9 |
10 | const getProduct = asyncHandler(async (req, res) => {
11 | const product = await Product.findById(req.params.id);
12 | if (product) {
13 | res.json(product);
14 | } else {
15 | res.status(404).json({ message: "Product Not Found" });
16 | }
17 | });
18 |
19 | module.exports = { getProducts, getProduct };
20 |
--------------------------------------------------------------------------------
/backend/routes/UsersRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | authController,
4 | getUserProfile,
5 | registerUser,
6 | updateUserProfile,
7 | } = require("../controllers/usersController");
8 | const { protect } = require("../middlewares/authMiddleware");
9 |
10 | const router = express.Router();
11 |
12 | //user registration
13 | router.route("/").post(registerUser);
14 |
15 | //post email and password auth
16 | router.post("/login", authController);
17 |
18 | //get user profile Private Route
19 | router
20 | .route("/profile")
21 | .get(protect, getUserProfile)
22 | .put(protect, updateUserProfile);
23 |
24 | module.exports = router;
25 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import store from "./store";
5 | import "./index.css";
6 | import App from "./App";
7 | import reportWebVitals from "./reportWebVitals";
8 | import "./bootstrap.min.css";
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById("root")
15 | );
16 |
17 | // If you want to start measuring performance in your app, pass a function
18 | // to log results (for example: reportWebVitals(console.log))
19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
20 | reportWebVitals();
21 |
--------------------------------------------------------------------------------
/frontend/src/constants/orderConstant.js:
--------------------------------------------------------------------------------
1 | export const ORDER_CREATE_REQUEST = "ORDER_CREATE_REQUEST";
2 | export const ORDER_CREATE_SUCCESS = "ORDER_CREATE_SUCCESS";
3 | export const ORDER_CREATE_FAIL = "ORDER_CREATE_FAIL";
4 |
5 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST";
6 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS";
7 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL";
8 |
9 | export const ORDER_PAY_REQUEST = "ORDER_PAY_REQUEST";
10 | export const ORDER_PAY_SUCCESS = "ORDER_PAY_SUCCESS";
11 | export const ORDER_PAY_FAIL = "ORDER_PAY_FAIL";
12 | export const ORDER_PAY_RESET = "ORDER_PAY_RESET";
13 |
14 | export const ORDER_LIST_MY_REQUEST = "ORDER_LIST_MY_REQUEST";
15 | export const ORDER_LIST_MY_SUCCESS = "ORDER_LIST_MY_SUCCESS";
16 | export const ORDER_LIST_MY_FAIL = "ORDER_LIST_MY_FAIL";
17 | export const ORDER_LIST_MY_RESET = "ORDER_LIST_MY_RESET";
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shopping-app",
3 | "version": "1.0.0",
4 | "description": "mern app",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "server": "nodemon backend/server",
9 | "client": "npm start --prefix frontend",
10 | "dev": "concurrently \"npm run server\" \"npm run client\" ",
11 | "data:import": "node backend/seeder.js",
12 | "data:destroy": "node backend/seeder.js -d"
13 | },
14 | "author": "techinfoyt",
15 | "license": "MIT",
16 | "dependencies": {
17 | "bcryptjs": "^2.4.3",
18 | "colors": "^1.4.0",
19 | "concurrently": "^5.3.0",
20 | "dotenv": "^8.2.0",
21 | "express": "^4.17.1",
22 | "express-async-handler": "^1.1.4",
23 | "jsonwebtoken": "^8.5.1",
24 | "mongodb": "^2.2.16",
25 | "mongoose": "^5.11.8",
26 | "nodemon": "^2.0.6"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/middlewares/authMiddleware.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const User = require("../models/UserModel");
3 | const asyncHandler = require("express-async-handler");
4 |
5 | const protect = asyncHandler(async (req, res, next) => {
6 | let token;
7 | if (
8 | req.headers.authorization &&
9 | req.headers.authorization.startsWith("Bearer")
10 | )
11 | try {
12 | token = req.headers.authorization.split(" ")[1];
13 | const decode = jwt.verify(token, process.env.JWT_KEY);
14 | req.user = await User.findById(decode.id).select("-password");
15 | next();
16 | } catch (error) {
17 | console.error(error);
18 | res.status(401);
19 | throw new Error("Not Authorized , Token failed");
20 | }
21 | if (!token) {
22 | res.status(401);
23 | throw new Error("Not Authorized, not token");
24 | }
25 | });
26 | module.exports = { protect };
27 |
--------------------------------------------------------------------------------
/frontend/src/constants/userContants.js:
--------------------------------------------------------------------------------
1 | export const USER_LOGIN_REQUEST = "USER_LOGIN_REQUEST";
2 | export const USER_LOGIN_SUCCESS = "USER_LOGIN_SUCCESS";
3 | export const USER_LOGIN_FAIL = "USER_LOGIN_FAIL";
4 | export const USER_LOGOUT = "USER_LOGOUT";
5 |
6 | export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST";
7 | export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS";
8 | export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL";
9 |
10 | export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST";
11 | export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS";
12 | export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL";
13 | export const USER_DETAILS_RESET = "USER_DETAILS_RESET";
14 |
15 | export const USER_UPDATE_PROFILE_REQUEST = "USER_UPDATE_PROFILE_REQUEST";
16 | export const USER_UPDATE_PROFILE_SUCCESS = "USER_UPDATE_PROFILE_SUCCESS";
17 | export const USER_UPDATE_PROFILE_FAIL = "USER_UPDATE_PROFILE_FAIL";
18 | export const USER_UPDATE_PROFILE_RESET = "USER_UPDATE_PROFILE_RESET";
19 |
--------------------------------------------------------------------------------
/frontend/src/screens/ProductScreen.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card } from "react-bootstrap";
3 | import Rating from "../components/Rating";
4 | import { Link } from "react-router-dom";
5 |
6 | const ProductScreen = ({ product }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {product.name}
17 |
18 |
19 |
20 |
24 |
25 | $ {product.price}
26 |
27 |
28 | >
29 | );
30 | };
31 |
32 | export default ProductScreen;
33 |
--------------------------------------------------------------------------------
/backend/models/UserModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const bcrypt = require("bcryptjs");
3 | const userSchema = mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | email: {
10 | type: String,
11 | required: true,
12 | },
13 | password: {
14 | type: String,
15 | required: true,
16 | },
17 | isAdmin: {
18 | type: Boolean,
19 | required: true,
20 | default: false,
21 | },
22 | },
23 | {
24 | timestamps: true,
25 | }
26 | );
27 |
28 | userSchema.methods.matchPassword = async function (enterPassword) {
29 | return await bcrypt.compare(enterPassword, this.password);
30 | };
31 |
32 | //middlware for password
33 | userSchema.pre("save", async function (next) {
34 | if (!this.isModified("password")) {
35 | next();
36 | }
37 | const salt = await bcrypt.genSalt(10);
38 | this.password = await bcrypt.hash(this.password, salt);
39 | });
40 |
41 | const User = mongoose.model("User", userSchema);
42 | module.exports = User;
43 |
--------------------------------------------------------------------------------
/frontend/src/screens/HomeScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { listProducts } from "../actions/productActions";
4 | import { Row, Col } from "react-bootstrap";
5 | import ProductScreen from "./ProductScreen";
6 | import Loader from "../components/shared/Loader";
7 | import Message from "../components/shared/Message";
8 | const HomeScreen = () => {
9 | const dispatch = useDispatch();
10 | const productList = useSelector((state) => state.productList);
11 | const { loading, error, products } = productList;
12 |
13 | useEffect(() => {
14 | dispatch(listProducts());
15 | }, [dispatch]);
16 |
17 | return (
18 | <>
19 | {loading ? (
20 |
21 | ) : error ? (
22 | {error}
23 | ) : (
24 |
25 | {products.map((product) => (
26 |
27 |
28 |
29 | ))}
30 |
31 | )}
32 | >
33 | );
34 | };
35 |
36 | export default HomeScreen;
37 |
--------------------------------------------------------------------------------
/frontend/src/reducers/productReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | PRODUCT_LIST_REQUEST,
3 | PRODUCT_LIST_SUCCESS,
4 | PRODUCT_LIST_FAILS,
5 | PRODUCT_DETAILS_REQUEST,
6 | PRODUCT_DETAILS_SUCCESS,
7 | PRODUCT_DETAILS_FAILS,
8 | } from "../constants/productConstant";
9 |
10 | export const productListReducer = (state = { products: [] }, action) => {
11 | switch (action.type) {
12 | case PRODUCT_LIST_REQUEST:
13 | return { loading: true, products: [] };
14 | case PRODUCT_LIST_SUCCESS:
15 | return { loading: false, products: action.payload };
16 | case PRODUCT_LIST_FAILS:
17 | return { loading: false, error: action.payload };
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export const productDetailsReducer = (
24 | state = { product: { reviews: [] } },
25 | action
26 | ) => {
27 | switch (action.type) {
28 | case PRODUCT_DETAILS_REQUEST:
29 | return { loading: true, ...state };
30 | case PRODUCT_DETAILS_SUCCESS:
31 | return { loading: false, product: action.payload };
32 | case PRODUCT_DETAILS_FAILS:
33 | return { loading: false, error: action.payload };
34 | default:
35 | return state;
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const { errorHandler } = require("./middlewares/errorMiddleware");
3 | require("colors");
4 | const products = require("./data/products");
5 | const dotenv = require("dotenv");
6 | const connectDb = require("./config/config");
7 | const productRoutes = require("./routes/productsRoute");
8 | const usersRoutes = require("./routes/UsersRoute");
9 | const orderRoutes = require("./routes/orderRoute");
10 |
11 | dotenv.config();
12 | //connecting to mongodb database
13 | connectDb();
14 | const app = express();
15 | //middleware bodyparser
16 | app.use(express.json());
17 |
18 | //dotenv config
19 | app.get("/", (req, res) => {
20 | res.send("
Welcome to Node Server ");
21 | });
22 |
23 | app.use("/api", productRoutes);
24 | app.use("/api/users", usersRoutes);
25 | app.use("/api/orders", orderRoutes);
26 | app.get("/api/config/paypal", (req, res) => {
27 | res.send(process.env.PAYPAL_CLIENT_ID);
28 | });
29 |
30 | app.use(errorHandler);
31 |
32 | const PORT = 8080;
33 | app.listen(process.env.PORT || PORT, () => {
34 | console.log(
35 | `Server Running in ${process.env.NODE_ENV} Mode on Port ${process.env.PORT}`
36 | .inverse
37 | );
38 | });
39 |
--------------------------------------------------------------------------------
/frontend/src/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | CART_ADD_ITEM,
3 | CART_REMOVE_ITEM,
4 | CART_SAVE_PAYMENT_METHOD,
5 | CART_SAVE_SHIPPING_ADDRESS,
6 | } from "../constants/cartConstant";
7 |
8 | export const cartReducer = (state = { cartItems: [] }, action) => {
9 | switch (action.type) {
10 | case CART_ADD_ITEM:
11 | const item = action.payload;
12 | const existItem = state.cartItems.find((x) => x.product === item.product);
13 | if (existItem) {
14 | return {
15 | ...state,
16 | cartItems: state.cartItems.map((x) =>
17 | x.product === existItem.product ? item : x
18 | ),
19 | };
20 | } else {
21 | return {
22 | ...state,
23 | cartItems: [...state.cartItems, item],
24 | };
25 | }
26 | case CART_REMOVE_ITEM:
27 | return {
28 | ...state,
29 | cartItems: state.cartItems.filter((x) => x.product !== action.payload),
30 | };
31 |
32 | case CART_SAVE_SHIPPING_ADDRESS:
33 | return { ...state, shippingAddress: action.payload };
34 | case CART_SAVE_PAYMENT_METHOD:
35 | return {
36 | ...state,
37 | paymentMethod: action.payload,
38 | };
39 | default:
40 | return state;
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/frontend/src/actions/productActions.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | PRODUCT_LIST_REQUEST,
4 | PRODUCT_LIST_SUCCESS,
5 | PRODUCT_LIST_FAILS,
6 | PRODUCT_DETAILS_REQUEST,
7 | PRODUCT_DETAILS_SUCCESS,
8 | PRODUCT_DETAILS_FAILS,
9 | } from "../constants/productConstant";
10 |
11 | export const listProducts = () => async (dispatch) => {
12 | try {
13 | dispatch({ type: PRODUCT_LIST_REQUEST });
14 | const { data } = await axios.get("/api/products");
15 | dispatch({
16 | type: PRODUCT_LIST_SUCCESS,
17 | payload: data,
18 | });
19 | } catch (error) {
20 | dispatch({
21 | type: PRODUCT_LIST_FAILS,
22 | payload:
23 | error.response && error.response.data.message
24 | ? error.response.data.message
25 | : error.message,
26 | });
27 | }
28 | };
29 |
30 | export const listProductDetails = (id) => async (dispatch) => {
31 | try {
32 | dispatch({ type: PRODUCT_DETAILS_REQUEST });
33 | const { data } = await axios.get(`/api/products/${id}`);
34 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
35 | } catch (error) {
36 | dispatch({
37 | type: PRODUCT_DETAILS_FAILS,
38 | payload:
39 | error.response && error.response.data.message
40 | ? error.response.data.message
41 | : error.message,
42 | });
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "proxy": "http://localhost:8080",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "axios": "^0.21.1",
11 | "bootstrap": "^4.5.3",
12 | "react": "^17.0.1",
13 | "react-bootstrap": "^1.4.0",
14 | "react-dom": "^17.0.1",
15 | "react-paypal-button-v2": "^2.6.3",
16 | "react-redux": "^7.2.2",
17 | "react-router-bootstrap": "^0.25.0",
18 | "react-router-dom": "^5.2.0",
19 | "react-scripts": "4.0.1",
20 | "redux": "^4.0.5",
21 | "redux-devtools-extension": "^2.13.9",
22 | "redux-thunk": "^2.3.0",
23 | "web-vitals": "^0.2.4"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/actions/cartAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | CART_ADD_ITEM,
4 | CART_REMOVE_ITEM,
5 | CART_SAVE_PAYMENT_METHOD,
6 | CART_SAVE_SHIPPING_ADDRESS,
7 | } from "../constants/cartConstant";
8 |
9 | export const addToCart = (id, qty) => async (dispatch, getState) => {
10 | const { data } = await axios.get(`/api/products/${id}`);
11 | dispatch({
12 | type: CART_ADD_ITEM,
13 | payload: {
14 | product: data._id,
15 | name: data.name,
16 | image: data.image,
17 | price: data.price,
18 | countInStock: data.countInStock,
19 | qty,
20 | },
21 | });
22 |
23 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
24 | };
25 |
26 | export const removeFromCart = (id) => (dispatch, getState) => {
27 | dispatch({
28 | type: CART_REMOVE_ITEM,
29 | payload: id,
30 | });
31 |
32 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
33 | };
34 |
35 | export const saveShippingAddress = (data) => (dispatch) => {
36 | dispatch({ type: CART_SAVE_SHIPPING_ADDRESS, payload: data });
37 | localStorage.setItem("shippingAddress", JSON.stringify(data));
38 | };
39 |
40 | export const savePaymentMethod = (data) => (dispatch) => {
41 | dispatch({
42 | type: CART_SAVE_PAYMENT_METHOD,
43 | payload: data,
44 | });
45 | localStorage.setItem("paymentMethod", JSON.stringify(data));
46 | };
47 |
--------------------------------------------------------------------------------
/backend/seeder.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const dotenv = require("dotenv");
3 | require("colors");
4 | const users = require("./data/users");
5 | const User = require("./models/UserModel");
6 | const Product = require("./models/ProductModel");
7 | const Order = require("./models/OrderModel");
8 | const products = require("./data/products");
9 | const connectDb = require("./config/config");
10 |
11 | dotenv.config();
12 | connectDb();
13 |
14 | const importData = async () => {
15 | try {
16 | await Order.deleteMany();
17 | await Product.deleteMany();
18 | await User.deleteMany();
19 | const createUser = await User.insertMany(users);
20 | const adminUser = createUser[0]._id;
21 | const sampleData = products.map((product) => {
22 | return { ...product, user: adminUser };
23 | });
24 | await Product.insertMany(sampleData);
25 | console.log("Data Imported!!".green.inverse);
26 | process.exit();
27 | } catch (error) {
28 | console.log(`${error}`.red.inverse);
29 | process.exit(1);
30 | }
31 | };
32 |
33 | const dataDestory = async () => {
34 | try {
35 | await Order.deleteMany();
36 | await Product.deleteMany();
37 | await User.deleteMany();
38 | console.log("Data Destory".green.inverse);
39 | process.exit();
40 | } catch (error) {
41 | console.log(`${error}`.red.inverse);
42 | process.exit(1);
43 | }
44 | };
45 |
46 | if (process.argv[2] === "-d") {
47 | dataDestory();
48 | } else {
49 | importData();
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/src/components/shared/CheckoutStep.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Nav } from "react-bootstrap";
3 | import { LinkContainer } from "react-router-bootstrap";
4 |
5 | const CheckoutStep = ({ step1, step2, step3, step4 }) => {
6 | return (
7 | <>
8 |
9 |
10 | {step1 ? (
11 |
12 | SignIn
13 |
14 | ) : (
15 | SignIn
16 | )}
17 |
18 |
19 | {step2 ? (
20 |
21 | Shipping
22 |
23 | ) : (
24 | Shipping
25 | )}
26 |
27 |
28 | {step3 ? (
29 |
30 | Payment
31 |
32 | ) : (
33 | Payment
34 | )}
35 |
36 |
37 | {step4 ? (
38 |
39 | Place Order
40 |
41 | ) : (
42 | Place Order
43 | )}
44 |
45 |
46 | >
47 | );
48 | };
49 |
50 | export default CheckoutStep;
51 |
--------------------------------------------------------------------------------
/backend/models/ProductModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const reviewSchema = mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | rating: {
10 | type: Number,
11 | required: true,
12 | },
13 | comment: {
14 | type: String,
15 | required: true,
16 | },
17 | },
18 | { timestamps: true }
19 | );
20 |
21 | const productSchema = mongoose.Schema(
22 | {
23 | user: {
24 | type: mongoose.Schema.Types.ObjectId,
25 | required: true,
26 | ref: "User",
27 | },
28 | name: {
29 | type: String,
30 | required: true,
31 | },
32 | image: {
33 | type: String,
34 | required: true,
35 | },
36 | brand: {
37 | type: String,
38 | required: true,
39 | },
40 | category: {
41 | type: String,
42 | required: true,
43 | },
44 | description: {
45 | type: String,
46 | required: true,
47 | },
48 | reviews: [reviewSchema],
49 | rating: {
50 | type: Number,
51 | required: true,
52 | default: 0,
53 | },
54 | numReviews: {
55 | type: Number,
56 | required: true,
57 | defualt: 0,
58 | },
59 | price: {
60 | type: Number,
61 | required: true,
62 | default: 0,
63 | },
64 | countInStock: {
65 | type: Number,
66 | required: true,
67 | default: 0,
68 | },
69 | },
70 | { timestamps: true }
71 | );
72 |
73 | const Product = mongoose.model("Product", productSchema);
74 | module.exports = Product;
75 |
--------------------------------------------------------------------------------
/frontend/src/screens/PaymentScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Form, Button, Col } from "react-bootstrap";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { savePaymentMethod } from "../actions/cartAction";
5 | import CheckoutStep from "../components/shared/CheckoutStep";
6 | const PaymentScreen = ({ history }) => {
7 | const cart = useSelector((state) => state.cart);
8 | const { shippingAddress } = cart;
9 | if (!shippingAddress.address) {
10 | history.push("/shipping");
11 | }
12 | const dispatch = useDispatch();
13 | const [paymentMethod, setPaymentMethod] = useState("paypal");
14 | const submitHandler = (e) => {
15 | e.preventDefault();
16 | dispatch(savePaymentMethod(paymentMethod));
17 | history.push("/placeorder");
18 | };
19 | return (
20 | <>
21 |
22 | Payment Method
23 |
25 | Select Payment Method
26 |
27 | setPaymentMethod(e.target.value)}
34 | >
35 |
36 |
37 |
38 | Continue
39 |
40 |
41 | >
42 | );
43 | };
44 |
45 | export default PaymentScreen;
46 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import { Container } from "react-bootstrap";
2 | import { BrowserRouter as Router, Route } from "react-router-dom";
3 |
4 | import "./App.css";
5 | import Footer from "./components/footer";
6 | import Header from "./components/Header";
7 | import CartScreen from "./screens/CartScreen";
8 | import HomeScreen from "./screens/HomeScreen";
9 | import ProductDetails from "./screens/ProductDetails";
10 | import LoginScreen from "./screens/LoginScreen";
11 | import RegisterScreen from "./screens/RegisterScreen";
12 | import ProfileScreen from "./screens/ProfileScreen";
13 | import ShippingScreen from "./screens/ShippingScreen";
14 | import PaymentScreen from "./screens/PaymentScreen";
15 | import PlaceOrderScreen from "./screens/PlaceOrderScreen";
16 | import OrderScreen from "./screens/OrderScreen";
17 |
18 | function App() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | export default App;
43 |
--------------------------------------------------------------------------------
/frontend/src/components/Rating.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Rating = ({ value, text }) => {
4 | return (
5 |
6 |
7 | = 1
10 | ? "fas fa-star"
11 | : value >= 0.5
12 | ? "fas fa-star-half-alt"
13 | : "fas fa-start"
14 | }
15 | >
16 |
17 |
18 | = 2
21 | ? "fas fa-star"
22 | : value >= 1.5
23 | ? "fas fa-star-half-alt"
24 | : "fas fa-start"
25 | }
26 | >
27 |
28 |
29 | = 3
32 | ? "fas fa-star"
33 | : value >= 2.5
34 | ? "fas fa-star-half-alt"
35 | : "fas fa-start"
36 | }
37 | >
38 |
39 |
40 | = 4
43 | ? "fas fa-star"
44 | : value >= 3.5
45 | ? "fas fa-star-half-alt"
46 | : "fas fa-start"
47 | }
48 | >
49 |
50 |
51 | = 5
54 | ? "fas fa-star"
55 | : value >= 4.5
56 | ? "fas fa-star-half-alt"
57 | : "fas fa-start"
58 | }
59 | >
60 |
61 | {text && text}
62 |
63 | );
64 | };
65 |
66 | export default Rating;
67 |
--------------------------------------------------------------------------------
/frontend/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, applyMiddleware } from "redux";
2 | import thunk from "redux-thunk";
3 | import { composeWithDevTools } from "redux-devtools-extension";
4 | import {
5 | productListReducer,
6 | productDetailsReducer,
7 | } from "./reducers/productReducer";
8 | import {
9 | userLoginReducer,
10 | userRegisterReducer,
11 | userDetailsReducer,
12 | userUpdateProfileReducer,
13 | } from "./reducers/userReducers";
14 | import { cartReducer } from "./reducers/cartReducer";
15 | import {
16 | orderCreateReducer,
17 | orderDetailsReducer,
18 | orderPayReducer,
19 | orderListMyReducer,
20 | } from "./reducers/orderReducer";
21 |
22 | const shippingAddressFromStorage = localStorage.getItem("shippingAddress")
23 | ? JSON.parse(localStorage.getItem("shippingAddress"))
24 | : {};
25 |
26 | const userInfoFromStorage = localStorage.getItem("userInfo")
27 | ? JSON.parse(localStorage.getItem("userInfo"))
28 | : null;
29 |
30 | const cartItemsFromStorage = localStorage.getItem("cartItems")
31 | ? JSON.parse(localStorage.getItem("cartItems"))
32 | : [];
33 |
34 | const reducer = combineReducers({
35 | productList: productListReducer,
36 | productDetails: productDetailsReducer,
37 | cart: cartReducer,
38 | userLogin: userLoginReducer,
39 | userRegister: userRegisterReducer,
40 | userDetails: userDetailsReducer,
41 | userUpdateProfile: userUpdateProfileReducer,
42 | orderCreate: orderCreateReducer,
43 | orderDetails: orderDetailsReducer,
44 | orderPay: orderPayReducer,
45 | orderListMy: orderListMyReducer,
46 | });
47 | const initialState = {
48 | // cart: { cartItems: "techinfo" },
49 | cart: {
50 | cartItems: cartItemsFromStorage,
51 | shippingAddress: shippingAddressFromStorage,
52 | },
53 | userLogin: { userInfo: userInfoFromStorage },
54 | };
55 | const middleware = [thunk];
56 | const store = createStore(
57 | reducer,
58 | initialState,
59 | composeWithDevTools(applyMiddleware(...middleware))
60 | );
61 |
62 | export default store;
63 |
--------------------------------------------------------------------------------
/frontend/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navbar, Nav, Container, NavDropdown } from "react-bootstrap";
3 | import { LinkContainer } from "react-router-bootstrap";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { logout } from "../actions/userAction";
6 |
7 | const Header = () => {
8 | const userLogin = useSelector((state) => state.userLogin);
9 | const { userInfo } = userLogin;
10 | const dispatch = useDispatch();
11 |
12 | const logoutHandler = () => {
13 | dispatch(logout());
14 | };
15 |
16 | return (
17 | <>
18 |
19 |
20 |
21 | Online Shop
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | cart
31 |
32 |
33 | {userInfo ? (
34 |
35 |
36 | Profile
37 |
38 |
39 | Logout
40 |
41 |
42 | ) : (
43 |
44 |
45 |
46 | singin
47 |
48 |
49 | )}
50 |
51 |
52 |
53 |
54 | >
55 | );
56 | };
57 |
58 | export default Header;
59 |
--------------------------------------------------------------------------------
/backend/controllers/orderController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require("express-async-handler");
2 | const Order = require("../models/OrderModel");
3 |
4 | const addOrderItem = asyncHandler(async (req, res) => {
5 | const {
6 | orderItems,
7 | shippingAddress,
8 | paymentMethod,
9 | itemsPrice,
10 | taxPrice,
11 | shippingPrice,
12 | totalPrice,
13 | } = req.body;
14 | if (orderItems && orderItems.length === 0) {
15 | res.status(400);
16 | throw new Error("No Order Found");
17 | return;
18 | } else {
19 | const order = new Order({
20 | orderItems,
21 | user: req.user._id,
22 | shippingAddress,
23 | paymentMethod,
24 | itemsPrice,
25 | taxPrice,
26 | shippingPrice,
27 | totalPrice,
28 | });
29 |
30 | const createOrder = await order.save();
31 | res.status(201).json(createOrder);
32 | }
33 | });
34 |
35 | //getOrderByID
36 | const getOrderById = asyncHandler(async (req, res) => {
37 | const order = await Order.findById(req.params.id).populate(
38 | "user",
39 | "name email"
40 | );
41 | if (order) {
42 | res.json(order);
43 | } else {
44 | res.status(404);
45 | throw new Error("Order Not Found");
46 | }
47 | });
48 |
49 | //paidendpoint
50 | const updateOrderToPaid = asyncHandler(async (req, res) => {
51 | const order = await Order.findById(req.params.id);
52 | if (order) {
53 | (order.isPaid = true),
54 | (order.paidAt = Date.now()),
55 | (order.paymentResult = {
56 | id: req.body.id,
57 | status: req.body.status,
58 | update_time: req.body.update_time,
59 | email_address: req.body.payer.email_address,
60 | });
61 | const updateOrder = await order.save();
62 | res.json(updateOrder);
63 | } else {
64 | res.status(404);
65 | throw new Error("Order Not Found");
66 | }
67 | });
68 |
69 | const getMyOrders = asyncHandler(async (req, res) => {
70 | const orders = await Order.find({ user: req.user._id });
71 | res.json(orders);
72 | });
73 |
74 | module.exports = { addOrderItem, getOrderById, updateOrderToPaid, getMyOrders };
75 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
28 | Shopping App
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/frontend/src/reducers/userReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | USER_LOGIN_REQUEST,
3 | USER_LOGIN_SUCCESS,
4 | USER_LOGIN_FAIL,
5 | USER_LOGOUT,
6 | USER_REGISTER_REQUEST,
7 | USER_REGISTER_SUCCESS,
8 | USER_REGISTER_FAIL,
9 | USER_DETAILS_REQUEST,
10 | USER_DETAILS_SUCCESS,
11 | USER_DETAILS_FAIL,
12 | USER_UPDATE_PROFILE_REQUEST,
13 | USER_UPDATE_PROFILE_SUCCESS,
14 | USER_UPDATE_PROFILE_FAIL,
15 | USER_DETAILS_RESET,
16 | } from "../constants/userContants";
17 |
18 | export const userLoginReducer = (state = {}, action) => {
19 | switch (action.type) {
20 | case USER_LOGIN_REQUEST:
21 | return { loading: true };
22 | case USER_LOGIN_SUCCESS:
23 | return { loading: false, userInfo: action.payload };
24 | case USER_LOGIN_FAIL:
25 | return { loading: false, error: action.payload };
26 | case USER_LOGOUT:
27 | return {};
28 | default:
29 | return state;
30 | }
31 | };
32 | export const userRegisterReducer = (state = {}, action) => {
33 | switch (action.type) {
34 | case USER_REGISTER_REQUEST:
35 | return { loading: true };
36 | case USER_REGISTER_SUCCESS:
37 | return { loading: false, userInfo: action.payload };
38 | case USER_REGISTER_FAIL:
39 | return { loading: false, error: action.payload };
40 |
41 | default:
42 | return state;
43 | }
44 | };
45 | export const userDetailsReducer = (state = { user: {} }, action) => {
46 | switch (action.type) {
47 | case USER_DETAILS_REQUEST:
48 | return { ...state, loading: true };
49 | case USER_DETAILS_SUCCESS:
50 | return { loading: false, user: action.payload };
51 | case USER_DETAILS_FAIL:
52 | return { loading: false, error: action.payload };
53 | case USER_DETAILS_RESET:
54 | return { user: {} };
55 | default:
56 | return state;
57 | }
58 | };
59 |
60 | export const userUpdateProfileReducer = (state = {}, action) => {
61 | switch (action.type) {
62 | case USER_UPDATE_PROFILE_REQUEST:
63 | return { loading: true };
64 | case USER_UPDATE_PROFILE_SUCCESS:
65 | return { loading: false, success: true, userInfo: action.payload };
66 | case USER_UPDATE_PROFILE_FAIL:
67 | return { loading: false, error: action.payload };
68 | default:
69 | return state;
70 | }
71 | };
72 |
--------------------------------------------------------------------------------
/backend/models/OrderModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const orderSchema = mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "User",
9 | },
10 | orderItems: [
11 | {
12 | name: {
13 | type: String,
14 | required: true,
15 | },
16 | qty: {
17 | type: Number,
18 | required: true,
19 | },
20 | image: {
21 | type: String,
22 | required: true,
23 | },
24 | price: {
25 | type: Number,
26 | required: true,
27 | },
28 | product: {
29 | type: mongoose.Schema.Types.ObjectId,
30 | required: true,
31 | ref: "Product",
32 | },
33 | },
34 | ],
35 | shippingAddress: {
36 | address: {
37 | type: String,
38 | required: true,
39 | },
40 | city: {
41 | type: String,
42 | required: true,
43 | },
44 | postalcode: {
45 | type: String,
46 | required: true,
47 | },
48 | country: {
49 | type: String,
50 | required: true,
51 | },
52 | },
53 | paymentMethod: {
54 | type: String,
55 | // required: true,
56 | },
57 | paymentResult: {
58 | id: {
59 | type: String,
60 | },
61 | status: {
62 | type: String,
63 | },
64 | upload_status: {
65 | type: String,
66 | },
67 | email_address: {
68 | type: String,
69 | },
70 | },
71 | taxPrice: {
72 | type: Number,
73 | required: true,
74 | defualt: 0.0,
75 | },
76 | shippingPrice: {
77 | type: Number,
78 | required: true,
79 | default: 0.0,
80 | },
81 | totalPrice: {
82 | type: Number,
83 | required: true,
84 | default: 0.0,
85 | },
86 | isPaid: {
87 | type: Boolean,
88 | required: true,
89 | default: false,
90 | },
91 | paidAt: {
92 | type: Date,
93 | },
94 | isDeliverd: {
95 | type: Boolean,
96 | required: true,
97 | default: false,
98 | },
99 | deliverAt: {
100 | type: Date,
101 | },
102 | },
103 | { timestamps: true }
104 | );
105 |
106 | const Order = mongoose.model("Order", orderSchema);
107 |
108 | module.exports = Order;
109 |
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/controllers/usersController.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/UserModel");
2 | const asyncHandler = require("express-async-handler");
3 | const generateToken = require("../utils/generateToken");
4 |
5 | const registerUser = asyncHandler(async (req, res) => {
6 | const { name, email, password } = req.body;
7 | const userExist = await User.findOne({ email });
8 | if (userExist) {
9 | res.status(400);
10 | throw new Error("User Already Exists!");
11 | }
12 |
13 | const user = await User.create({ name, email, password });
14 | if (user) {
15 | res.status(201).json({
16 | _id: user._id,
17 | name: user.name,
18 | email: user.email,
19 | isAdmin: user.isAdmin,
20 | token: generateToken(user._id),
21 | });
22 | } else {
23 | res.status(404);
24 | throw new Error("User Not Found");
25 | }
26 | });
27 |
28 | const authController = asyncHandler(async (req, res) => {
29 | const { email, password } = req.body;
30 | const user = await User.findOne({ email });
31 | if (user && (await user.matchPassword(password))) {
32 | res.json({
33 | _id: user._id,
34 | name: user.name,
35 | email: user.email,
36 | isAdmin: user.isAdmin,
37 | token: generateToken(user._id),
38 | });
39 | } else {
40 | res.status(401);
41 | throw new Error("Invalid Email or Password");
42 | }
43 | });
44 |
45 | const getUserProfile = asyncHandler(async (req, res) => {
46 | const user = await User.findById(req.user._id);
47 | if (user) {
48 | res.json({
49 | _id: user._id,
50 | name: user.name,
51 | email: user.email,
52 | isAdmin: user.isAdmin,
53 | });
54 | } else {
55 | res.status(404);
56 | throw new Error("User Not Found");
57 | }
58 | });
59 |
60 | const updateUserProfile = asyncHandler(async (req, res) => {
61 | const user = await User.findById(req.user._id);
62 | if (user) {
63 | user.name = req.body.name || user.name;
64 | user.email = req.body.email || user.email;
65 | if (req.body.password) {
66 | user.password = req.body.password;
67 | }
68 | const updateUser = await user.save();
69 | res.json({
70 | _id: updateUser._id,
71 | name: updateUser.name,
72 | email: updateUser.email,
73 | isAdmin: updateUser.isAdmin,
74 | token: generateToken(updateUser._id),
75 | });
76 | } else {
77 | res.status(404);
78 | throw new Error("user Not Found!");
79 | }
80 | });
81 |
82 | module.exports = {
83 | authController,
84 | getUserProfile,
85 | registerUser,
86 | updateUserProfile,
87 | };
88 |
--------------------------------------------------------------------------------
/frontend/src/screens/LoginScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link } from "react-router-dom";
3 | import { Form, Button, Row, Col } from "react-bootstrap";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import Message from "../components/shared/Message";
6 | import Loader from "../components/shared/Loader";
7 | import { login } from "../actions/userAction";
8 | import FormContainer from "../components/shared/FromContainer";
9 |
10 | const LoginScreen = ({ location, history }) => {
11 | const [email, setEmail] = useState("");
12 | const [password, setPassword] = useState("");
13 |
14 | const redirect = location.search ? location.search.split("=")[1] : "/";
15 |
16 | const dispatch = useDispatch();
17 | const userLogin = useSelector((state) => state.userLogin);
18 | const { loading, error, userInfo } = userLogin;
19 |
20 | useEffect(() => {
21 | if (userInfo) {
22 | history.push(redirect);
23 | }
24 | }, [history, userInfo, redirect]);
25 |
26 | const submitHandler = (e) => {
27 | e.preventDefault();
28 | //dispatch
29 | dispatch(login(email, password));
30 | };
31 |
32 | return (
33 | <>
34 |
35 | SIGN IN
36 | {error && {error} }
37 | {loading && }
38 | {Loader}
39 |
41 | Email Address
42 | setEmail(e.target.value)}
47 | >
48 |
49 |
50 | Password
51 | setPassword(e.target.value)}
56 | >
57 |
58 |
59 | SING IN
60 |
61 |
62 |
63 |
64 | New Customer ?
65 |
66 | Register
67 |
68 |
69 |
70 |
71 | >
72 | );
73 | };
74 |
75 | export default LoginScreen;
76 |
--------------------------------------------------------------------------------
/backend/data/products.js:
--------------------------------------------------------------------------------
1 | const products = [
2 | {
3 | name: "boAt Airdopes 121v2 TWS Earbuds",
4 | image: "/images/boatHeadfone.jpg",
5 | description:
6 | "boAt Airdopes 121v2 TWS Earbuds with Bluetooth V5.0, Immersive Audio, Up to 14H Total Playback, Instant Voice Assistant, Easy Access Controls with Mic and Dual Tone Ergonomic Design(Active Black) ",
7 | brand: "Boat",
8 | category: "Electronics",
9 | price: 20.99,
10 | countInStock: 10,
11 | rating: 4.5,
12 | numReviews: 12,
13 | },
14 | {
15 | name: "Micromax IN 1b (Purple, 32 GB)",
16 | image: "/images/micromaxInB.jpg",
17 | description:
18 | "Say hello to the Micromax IN 1b smartphone whose powerful MediaTek Helio G35 gaming processor and a 5000 mAh battery will pave the way for effortless multitasking and usage.",
19 | brand: "Micromax",
20 | category: "Electronics",
21 | price: 599.99,
22 | countInStock: 7,
23 | rating: 4.0,
24 | numReviews: 8,
25 | },
26 | {
27 | name: "Cannon EOS 80D DSLR Camera",
28 | image: "/images/camera.jpg",
29 | description:
30 | "Characterized by versatile imaging specs, the Canon EOS 80D further clarifies itself using a pair of robust focusing systems and an intuitive design",
31 | brand: "Cannon",
32 | category: "Electronics",
33 | price: 929.99,
34 | countInStock: 5,
35 | rating: 3,
36 | numReviews: 12,
37 | },
38 | {
39 | name: "Sony Playstation 4 Pro White Version",
40 | image: "/images/playstation.jpg",
41 | description:
42 | "The ultimate home entertainment center starts with PlayStation. Whether you are into gaming, HD movies, television, music",
43 | brand: "Sony",
44 | category: "Electronics",
45 | price: 399.99,
46 | countInStock: 11,
47 | rating: 5,
48 | numReviews: 12,
49 | },
50 | {
51 | name: "Logitech G-Series Gaming Mouse",
52 | image: "/images/mouse.jpg",
53 | description:
54 | "Get a better handle on your games with this Logitech LIGHTSYNC gaming mouse. The six programmable buttons allow customization for a smooth playing experience",
55 | brand: "Logitech",
56 | category: "Electronics",
57 | price: 49.99,
58 | countInStock: 7,
59 | rating: 3.5,
60 | numReviews: 10,
61 | },
62 | {
63 | name: "Amazon Echo Dot 3rd Generation",
64 | image: "/images/alexa.jpg",
65 | description:
66 | "Meet Echo Dot - Our most popular smart speaker with a fabric design. It is our most compact smart speaker that fits perfectly into small space",
67 | brand: "Amazon",
68 | category: "Electronics",
69 | price: 29.99,
70 | countInStock: 0,
71 | rating: 4,
72 | numReviews: 12,
73 | },
74 | ];
75 |
76 | module.exports = products;
77 |
--------------------------------------------------------------------------------
/frontend/src/products.js:
--------------------------------------------------------------------------------
1 | const products = [
2 | {
3 | _id: '1',
4 | name: 'boAt Airdopes 121v2 TWS Earbuds',
5 | image: '/images/boatHeadfone.jpg',
6 | description:
7 | 'boAt Airdopes 121v2 TWS Earbuds with Bluetooth V5.0, Immersive Audio, Up to 14H Total Playback, Instant Voice Assistant, Easy Access Controls with Mic and Dual Tone Ergonomic Design(Active Black) ',
8 | brand: 'Boat',
9 | category: 'Electronics',
10 | price: 20.99,
11 | countInStock: 10,
12 | rating: 4.5,
13 | numReviews: 12,
14 | },
15 | {
16 | _id: '2',
17 | name: 'Micromax IN 1b (Purple, 32 GB)',
18 | image: '/images/micromaxInB.jpg',
19 | description:
20 | 'Say hello to the Micromax IN 1b smartphone whose powerful MediaTek Helio G35 gaming processor and a 5000 mAh battery will pave the way for effortless multitasking and usage.',
21 | brand: 'Micromax',
22 | category: 'Electronics',
23 | price: 599.99,
24 | countInStock: 7,
25 | rating: 4.0,
26 | numReviews: 8,
27 | },
28 | {
29 | _id: '3',
30 | name: 'Cannon EOS 80D DSLR Camera',
31 | image: '/images/camera.jpg',
32 | description:
33 | 'Characterized by versatile imaging specs, the Canon EOS 80D further clarifies itself using a pair of robust focusing systems and an intuitive design',
34 | brand: 'Cannon',
35 | category: 'Electronics',
36 | price: 929.99,
37 | countInStock: 5,
38 | rating: 3,
39 | numReviews: 12,
40 | },
41 | {
42 | _id: '4',
43 | name: 'Sony Playstation 4 Pro White Version',
44 | image: '/images/playstation.jpg',
45 | description:
46 | 'The ultimate home entertainment center starts with PlayStation. Whether you are into gaming, HD movies, television, music',
47 | brand: 'Sony',
48 | category: 'Electronics',
49 | price: 399.99,
50 | countInStock: 11,
51 | rating: 5,
52 | numReviews: 12,
53 | },
54 | {
55 | _id: '5',
56 | name: 'Logitech G-Series Gaming Mouse',
57 | image: '/images/mouse.jpg',
58 | description:
59 | 'Get a better handle on your games with this Logitech LIGHTSYNC gaming mouse. The six programmable buttons allow customization for a smooth playing experience',
60 | brand: 'Logitech',
61 | category: 'Electronics',
62 | price: 49.99,
63 | countInStock: 7,
64 | rating: 3.5,
65 | numReviews: 10,
66 | },
67 | {
68 | _id: '6',
69 | name: 'Amazon Echo Dot 3rd Generation',
70 | image: '/images/alexa.jpg',
71 | description:
72 | 'Meet Echo Dot - Our most popular smart speaker with a fabric design. It is our most compact smart speaker that fits perfectly into small space',
73 | brand: 'Amazon',
74 | category: 'Electronics',
75 | price: 29.99,
76 | countInStock: 0,
77 | rating: 4,
78 | numReviews: 12,
79 | },
80 | ]
81 |
82 | module.exports = products;
83 |
--------------------------------------------------------------------------------
/frontend/src/screens/ShippingScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Form, Button } from "react-bootstrap";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import FromContainer from "../components/shared/FromContainer";
5 | import { saveShippingAddress } from "../actions/cartAction";
6 | import ChekcoutStep from "../components/shared/CheckoutStep";
7 | import CheckoutStep from "../components/shared/CheckoutStep";
8 |
9 | const ShippingScreen = ({ history }) => {
10 | const dispatch = useDispatch();
11 |
12 | const cart = useSelector((state) => state.cart);
13 | const { shippingAddress } = cart;
14 |
15 | const [address, setAddress] = useState(shippingAddress.address);
16 | const [city, setCity] = useState(shippingAddress.city);
17 | const [postalcode, setPostalcode] = useState(shippingAddress.postalcode);
18 | const [country, setCountry] = useState(shippingAddress.country);
19 |
20 | const submitHandler = (e) => {
21 | e.preventDefault();
22 | //dispatch
23 | dispatch(saveShippingAddress({ address, city, postalcode, country }));
24 | history.push("/payment");
25 | };
26 |
27 | return (
28 | <>
29 |
30 |
31 |
33 | Address
34 | setAddress(e.target.value)}
39 | required
40 | >
41 |
42 |
43 | City
44 | setCity(e.target.value)}
49 | required
50 | >
51 |
52 |
53 | PostalCode
54 | setPostalcode(e.target.value)}
59 | required
60 | >
61 |
62 |
63 | Country
64 | setCountry(e.target.value)}
69 | required
70 | >
71 |
72 |
73 | continue
74 |
75 |
76 |
77 | >
78 | );
79 | };
80 |
81 | export default ShippingScreen;
82 |
--------------------------------------------------------------------------------
/frontend/src/screens/ProductDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import Rating from "../components/Rating";
4 | import { Link } from "react-router-dom";
5 | import { listProductDetails } from "../actions/productActions";
6 | import {
7 | Row,
8 | Col,
9 | ListGroup,
10 | Button,
11 | Image,
12 | ListGroupItem,
13 | Form,
14 | } from "react-bootstrap";
15 | const ProductDetails = ({ history, match }) => {
16 | const [qty, setQty] = useState(1);
17 | const dispatch = useDispatch();
18 | const productDetails = useSelector((state) => state.productDetails);
19 | const { loading, error, product } = productDetails;
20 |
21 | useEffect(() => {
22 | dispatch(listProductDetails(match.params.id));
23 | }, [dispatch, match]);
24 |
25 | const addToCartHandler = () => {
26 | history.push(`/cart/${match.params.id}?qty=${qty}`);
27 | };
28 | return (
29 | <>
30 |
31 |
32 | GO BACK
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {product.name}
43 |
44 |
45 |
49 |
50 | Price : ${product.price}
51 | {product.description}
52 |
53 |
54 |
55 |
56 |
57 | Status :
58 |
59 | {product.countInStock > 0 ? "In Stock " : "out of stock"}
60 |
61 |
62 |
63 | {product.countInStock > 0 && (
64 |
65 |
66 | Qty
67 | setQty(e.target.value)}
71 | >
72 | {[...Array(product.countInStock).keys()].map((x) => (
73 |
74 | {x + 1}
75 |
76 | ))}
77 |
78 |
79 |
80 | )}
81 |
82 |
87 | Add to cart
88 |
89 |
90 |
91 |
92 | >
93 | );
94 | };
95 |
96 | export default ProductDetails;
97 |
--------------------------------------------------------------------------------
/backend/data/books.js:
--------------------------------------------------------------------------------
1 | const books = [
2 | {
3 | name: "Inspiring Thoughts : Apj Abdul Kalam",
4 | image: "/images/book.jpg",
5 | description:"The book is filled with influential statements, some which explain Kalams belief about the need to nurture children with utmost focus. He adds that while following in the footsteps of others might be important, exploring new roads would get new variants of success. Although the book is addressed to a large audience, the language of the book is in the form of personal sayings.",
6 | publisher: "Rajpal & Sons",
7 | category: "Inspiring",
8 | price: 20.99,
9 | countInStock: 10,
10 | rating: 4.5,
11 | numReviews: 12,
12 | },
13 | {
14 | name: "Node.js for Embedded Systems Using Web Technologies to Build Connected Devices",
15 | image: "/images/book2.jpg",
16 | description:"This practical guide shows hardware and software engineers, makers, and web developers how to talk in JavaScript with a variety of hardware platforms. Authors Patrick Mulder and Kelsey Breseman also delve into the basics of microcontrollers, single-board computers, and other hardware components.",
17 | publisher: "Shroff/OReilly",
18 | category: "Programming",
19 | price: 50.99,
20 | countInStock: 10,
21 | rating: 4.5,
22 | numReviews: 12,
23 | },
24 | {
25 | name: "General Knowledge",
26 | image: "/images/book3.jpg",
27 | description:"No description available for this product.",
28 | publisher: "V&S Publishers",
29 | category: "general knowledge ",
30 | price: 30.99,
31 | countInStock: 10,
32 | rating: 4.5,
33 | numReviews: 12,
34 | },
35 | {
36 | name: "Perspectives- From The Creator Of Dog Man",
37 | image: "/images/book4.jpg",
38 | description:"About the Book: Cat Kid Comic Club #2: Perspectives- From the Creator of Dog Man Flippy, Molly, Lil Petey, and twenty-one baby frogs each have something to say. Naomi and Melvin dont see eye to eye and Poppy perceives the world differently than her siblings. Will the baby frogs figure out how to work together and appreciate one anothers point of view -- both inside and outside the classroom?",
39 | publisher: "Scholastic Inc",
40 | category: "general knowledge ",
41 | price: 10.99,
42 | countInStock: 10,
43 | rating: 4.5,
44 | numReviews: 12,
45 | },
46 | {
47 | name: "All Time Great Classics : The Three Musketeers",
48 | image: "/images/book5.jpg",
49 | description:"Three Musketteers : All Time Great Classics",
50 | publisher: "Manoj Publications",
51 | category: "adventure",
52 | price: 10.99,
53 | countInStock: 10,
54 | rating: 4.5,
55 | numReviews: 12,
56 | },
57 | {
58 | name: "Llb Self Study Guide",
59 | image: "/images/book6.jpg",
60 | description:"About the Book: Llb Self Study Guide Entrance Examination 2018 Bachelor of laws or LLB is a professional under-graduate degree in law that enables one to become lawyer.",
61 | publisher: "Arihant Publications India Pvt Ltd",
62 | category: "acadmic",
63 | price: 70.99,
64 | countInStock: 10,
65 | rating: 4.5,
66 | numReviews: 12,
67 | },
68 | ];
69 | module.exports = books;
70 |
--------------------------------------------------------------------------------
/frontend/src/reducers/orderReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ORDER_CREATE_REQUEST,
3 | ORDER_CREATE_SUCCESS,
4 | ORDER_CREATE_FAIL,
5 | ORDER_DETAILS_REQUEST,
6 | ORDER_DETAILS_SUCCESS,
7 | ORDER_DETAILS_FAIL,
8 | ORDER_PAY_REQUEST,
9 | ORDER_PAY_SUCCESS,
10 | ORDER_PAY_FAIL,
11 | ORDER_PAY_RESET,
12 | ORDER_LIST_MY_FAIL,
13 | ORDER_LIST_MY_SUCCESS,
14 | ORDER_LIST_MY_REQUEST,
15 | ORDER_LIST_MY_RESET,
16 | } from "../constants/orderConstant";
17 |
18 | export const orderCreateReducer = (state = {}, action) => {
19 | switch (action.type) {
20 | case ORDER_CREATE_REQUEST:
21 | return { loading: true };
22 | case ORDER_CREATE_SUCCESS:
23 | return { loading: false, success: true, order: action.payload };
24 | case ORDER_CREATE_FAIL:
25 | return { loading: false, error: action.payload };
26 | default:
27 | return state;
28 | }
29 | };
30 |
31 | // export const orderDetailsReducer = (
32 | // state = { loading: true, orderItems: [], shippingAddress: {} },
33 | // action
34 | // ) => {
35 | // switch (action.type) {
36 | // case ORDER_DETAILS_REQUEST:
37 | // return { ...state, loading: true };
38 | // case ORDER_DETAILS_SUCCESS:
39 | // return { loading: false, order: action.payload };
40 | // case ORDER_DETAILS_FAIL:
41 | // return { loading: false, error: action.payload };
42 | // default:
43 | // return state;
44 | // }
45 | // };
46 | export const orderDetailsReducer = (
47 | state = { loading: true, orderItems: [], shippingAddress: {} },
48 | action
49 | ) => {
50 | switch (action.type) {
51 | case ORDER_DETAILS_REQUEST:
52 | return {
53 | ...state,
54 | loading: true,
55 | };
56 | case ORDER_DETAILS_SUCCESS:
57 | return {
58 | loading: false,
59 | order: action.payload,
60 | };
61 | case ORDER_DETAILS_FAIL:
62 | return {
63 | loading: false,
64 | error: action.payload,
65 | };
66 | default:
67 | return state;
68 | }
69 | };
70 |
71 | export const orderPayReducer = (state = {}, action) => {
72 | switch (action.type) {
73 | case ORDER_PAY_REQUEST:
74 | return {
75 | loading: true,
76 | };
77 | case ORDER_PAY_SUCCESS:
78 | return {
79 | loading: false,
80 | success: true,
81 | };
82 | case ORDER_PAY_FAIL:
83 | return {
84 | loading: false,
85 | error: action.payload,
86 | };
87 | case ORDER_PAY_RESET:
88 | return {};
89 | default:
90 | return state;
91 | }
92 | };
93 |
94 | export const orderListMyReducer = (state = { orders: [] }, action) => {
95 | switch (action.type) {
96 | case ORDER_LIST_MY_REQUEST:
97 | return {
98 | loading: true,
99 | };
100 | case ORDER_LIST_MY_SUCCESS:
101 | return {
102 | loading: false,
103 | orders: action.payload,
104 | };
105 | case ORDER_LIST_MY_FAIL:
106 | return {
107 | loading: false,
108 | error: action.payload,
109 | };
110 | case ORDER_LIST_MY_RESET:
111 | return { orders: [] };
112 | default:
113 | return state;
114 | }
115 | };
116 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `yarn build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/src/screens/RegisterScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link } from "react-router-dom";
3 | import { Form, Button, Row, Col } from "react-bootstrap";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import Message from "../components/shared/Message";
6 | import Loader from "../components/shared/Loader";
7 | import { register } from "../actions/userAction";
8 | import FormContainer from "../components/shared/FromContainer";
9 |
10 | const RegisterScreen = ({ location, history }) => {
11 | const [name, setName] = useState("");
12 | const [email, setEmail] = useState("");
13 | const [password, setPassword] = useState("");
14 | const [confirmPassword, setConfirmPassword] = useState("");
15 | const [message, setMessage] = useState("");
16 |
17 | const redirect = location.search ? location.search.split("=")[1] : "/";
18 |
19 | const dispatch = useDispatch();
20 | const userRegister = useSelector((state) => state.userRegister);
21 | const { loading, error, userInfo } = userRegister;
22 |
23 | useEffect(() => {
24 | if (userInfo) {
25 | history.push(redirect);
26 | }
27 | }, [history, userInfo, redirect]);
28 |
29 | const submitHandler = (e) => {
30 | e.preventDefault();
31 | //dispatch
32 | if (password !== confirmPassword) {
33 | setMessage("Password do not macth");
34 | } else {
35 | dispatch(register(name, email, password));
36 | }
37 | };
38 |
39 | return (
40 | <>
41 |
42 | Register
43 | {error && {error} }
44 | {loading && }
45 | {message && {message} }
46 |
48 | Name
49 | setName(e.target.value)}
54 | >
55 |
56 |
57 | Email Address
58 | setEmail(e.target.value)}
63 | >
64 |
65 |
66 | Password
67 | setPassword(e.target.value)}
72 | >
73 |
74 |
75 | COnfirm Password
76 | setConfirmPassword(e.target.value)}
81 | >
82 |
83 |
84 | SING IN
85 |
86 |
87 |
88 |
89 | Have an account !
90 |
91 | Login
92 |
93 |
94 |
95 |
96 | >
97 | );
98 | };
99 |
100 | export default RegisterScreen;
101 |
--------------------------------------------------------------------------------
/frontend/src/actions/orderAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | ORDER_CREATE_REQUEST,
4 | ORDER_CREATE_SUCCESS,
5 | ORDER_CREATE_FAIL,
6 | ORDER_DETAILS_REQUEST,
7 | ORDER_DETAILS_SUCCESS,
8 | ORDER_DETAILS_FAIL,
9 | ORDER_PAY_REQUEST,
10 | ORDER_PAY_SUCCESS,
11 | ORDER_PAY_FAIL,
12 | ORDER_LIST_MY_REQUEST,
13 | ORDER_LIST_MY_SUCCESS,
14 | ORDER_LIST_MY_FAIL,
15 | } from "../constants/orderConstant";
16 |
17 | export const createOrder = (order) => async (dispatch, getState) => {
18 | try {
19 | dispatch({
20 | type: ORDER_CREATE_REQUEST,
21 | });
22 | const {
23 | userLogin: { userInfo },
24 | } = getState();
25 | const config = {
26 | headers: {
27 | "Contnet-Type": "application/json",
28 | Authorization: `Bearer ${userInfo.token}`,
29 | },
30 | };
31 | const { data } = await axios.post("/api/orders", order, config);
32 | dispatch({ type: ORDER_CREATE_SUCCESS, payload: data });
33 | } catch (error) {
34 | dispatch({
35 | type: ORDER_CREATE_FAIL,
36 | payload:
37 | error.response && error.response.data.message
38 | ? error.response.data.message
39 | : error.message,
40 | });
41 | }
42 | };
43 |
44 | export const getOrderDetails = (id) => async (dispatch, getState) => {
45 | try {
46 | dispatch({
47 | type: ORDER_DETAILS_REQUEST,
48 | });
49 | const {
50 | userLogin: { userInfo },
51 | } = getState();
52 | const config = {
53 | headers: {
54 | "Content-Type": "application/json",
55 | Authorization: `Bearer ${userInfo.token}`,
56 | },
57 | };
58 | const { data } = await axios.get(`/api/orders/${id}`, config);
59 | dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data });
60 | } catch (error) {
61 | dispatch({
62 | type: ORDER_DETAILS_FAIL,
63 | payload:
64 | error.response && error.response.data.message
65 | ? error.response.data.message
66 | : error.message,
67 | });
68 | }
69 | };
70 |
71 | export const payOrder =
72 | (orderId, paymentResult) => async (dispatch, getState) => {
73 | try {
74 | dispatch({
75 | type: ORDER_PAY_REQUEST,
76 | });
77 | const {
78 | userLogin: { userInfo },
79 | } = getState();
80 | const config = {
81 | headers: {
82 | "Content-Type": "application/json",
83 | Authorization: `Bearer ${userInfo.token}`,
84 | },
85 | };
86 | const { data } = await axios.put(
87 | `/api/orders/${orderId}/pay`,
88 | paymentResult,
89 | config
90 | );
91 | dispatch({ type: ORDER_PAY_SUCCESS, payload: data });
92 | } catch (error) {
93 | dispatch({
94 | type: ORDER_PAY_FAIL,
95 | payload:
96 | error.response && error.response.data.message
97 | ? error.response.data.message
98 | : error.message,
99 | });
100 | }
101 | };
102 |
103 | export const listMyOrders = () => async (dispatch, getState) => {
104 | try {
105 | dispatch({
106 | type: ORDER_LIST_MY_REQUEST,
107 | });
108 | const {
109 | userLogin: { userInfo },
110 | } = getState();
111 | const config = {
112 | headers: {
113 | Authorization: `Bearer ${userInfo.token}`,
114 | },
115 | };
116 | const { data } = await axios.get("/api/orders/myorders", config);
117 | dispatch({ type: ORDER_LIST_MY_SUCCESS, payload: data });
118 | } catch (error) {
119 | dispatch({
120 | type: ORDER_LIST_MY_FAIL,
121 | payload:
122 | error.response && error.response.data.message
123 | ? error.response.data.message
124 | : error.message,
125 | });
126 | }
127 | };
128 |
--------------------------------------------------------------------------------
/frontend/src/actions/userAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { ORDER_LIST_MY_RESET } from "../constants/orderConstant";
3 | import {
4 | USER_DETAILS_FAIL,
5 | USER_DETAILS_REQUEST,
6 | USER_DETAILS_RESET,
7 | USER_DETAILS_SUCCESS,
8 | USER_LOGIN_FAIL,
9 | USER_LOGIN_REQUEST,
10 | USER_LOGIN_SUCCESS,
11 | USER_LOGOUT,
12 | USER_REGISTER_FAIL,
13 | USER_REGISTER_REQUEST,
14 | USER_REGISTER_SUCCESS,
15 | USER_UPDATE_PROFILE_FAIL,
16 | USER_UPDATE_PROFILE_REQUEST,
17 | USER_UPDATE_PROFILE_SUCCESS,
18 | } from "../constants/userContants";
19 |
20 | export const logout = () => (dispatch) => {
21 | localStorage.removeItem("userInfo");
22 | dispatch({ type: ORDER_LIST_MY_RESET });
23 | dispatch({ type: USER_DETAILS_RESET });
24 | dispatch({ type: USER_LOGOUT });
25 | };
26 |
27 | export const login = (email, password) => async (dispatch) => {
28 | try {
29 | dispatch({ type: USER_LOGIN_REQUEST });
30 | const config = { headers: { "Contnet-Type": "application/json" } };
31 | const { data } = await axios.post(
32 | "/api/users/login",
33 | { email, password },
34 | config
35 | );
36 | dispatch({
37 | type: USER_LOGIN_SUCCESS,
38 | payload: data,
39 | });
40 | localStorage.setItem("userInfo", JSON.stringify(data));
41 | } catch (error) {
42 | dispatch({
43 | type: USER_LOGIN_FAIL,
44 | payload:
45 | error.response && error.response.data.message
46 | ? error.response.data.message
47 | : error.message,
48 | });
49 | }
50 | };
51 |
52 | export const register = (name, email, password) => async (dispatch) => {
53 | try {
54 | dispatch({ type: USER_REGISTER_REQUEST });
55 | const config = { headers: { "Contnet-Type": "application/json" } };
56 | const { data } = await axios.post(
57 | "/api/users",
58 | { name, email, password },
59 | config
60 | );
61 | dispatch({
62 | type: USER_REGISTER_SUCCESS,
63 | payload: data,
64 | });
65 | dispatch({
66 | type: USER_LOGIN_SUCCESS,
67 | payload: data,
68 | });
69 | localStorage.setItem("userInfo", JSON.stringify(data));
70 | } catch (error) {
71 | dispatch({
72 | type: USER_REGISTER_FAIL,
73 | payload:
74 | error.response && error.response.data.message
75 | ? error.response.data.message
76 | : error.message,
77 | });
78 | }
79 | };
80 |
81 | export const getUserDetails = (id) => async (dispatch, getState) => {
82 | try {
83 | dispatch({
84 | type: USER_DETAILS_REQUEST,
85 | });
86 | const {
87 | userLogin: { userInfo },
88 | } = getState();
89 | const config = {
90 | headers: {
91 | "Contnet-Type": "application/json",
92 | Authorization: `Bearer ${userInfo.token}`,
93 | },
94 | };
95 | const { data } = await axios.get(`/api/users/${id}`, config);
96 |
97 | dispatch({
98 | type: USER_DETAILS_SUCCESS,
99 | payload: data,
100 | });
101 | } catch (error) {
102 | dispatch({
103 | type: USER_DETAILS_FAIL,
104 | payload:
105 | error.response && error.response.data.message
106 | ? error.response.data.message
107 | : error.message,
108 | });
109 | }
110 | };
111 |
112 | export const updateUserProfile = (user) => async (dispatch, getState) => {
113 | try {
114 | dispatch({
115 | type: USER_UPDATE_PROFILE_REQUEST,
116 | });
117 | const {
118 | userLogin: { userInfo },
119 | } = getState();
120 | const config = {
121 | headers: {
122 | "Content-Type": "application/json",
123 | Authorization: `Bearer ${userInfo.token}`,
124 | },
125 | };
126 | const { data } = await axios.put("api/users/profile", user, config);
127 | dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: data });
128 | } catch (error) {
129 | dispatch({
130 | type: USER_UPDATE_PROFILE_FAIL,
131 | payload:
132 | error.response && error.response.data.message
133 | ? error.response.data.message
134 | : error.message,
135 | });
136 | }
137 | };
138 |
--------------------------------------------------------------------------------
/frontend/src/screens/CartScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Message from "../components/shared/Message";
3 | import { Link } from "react-router-dom";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import {
6 | Row,
7 | Col,
8 | Form,
9 | Button,
10 | Card,
11 | Image,
12 | ListGroup,
13 | ListGroupItem,
14 | } from "react-bootstrap";
15 | import { addToCart, removeFromCart } from "../actions/cartAction";
16 |
17 | const CartScreen = ({ match, location, history }) => {
18 | const productId = match.params.id;
19 | const qty = location.search ? Number(location.search.split("=")[1]) : 1;
20 | const dispatch = useDispatch();
21 |
22 | useEffect(() => {
23 | if (productId) {
24 | dispatch(addToCart(productId, qty));
25 | }
26 | }, [dispatch, productId, qty]);
27 |
28 | const cart = useSelector((state) => state.cart);
29 | const { cartItems } = cart;
30 | console.log(cartItems);
31 |
32 | const removeFromCartHandler = (id) => {
33 | dispatch(removeFromCart(id));
34 | };
35 |
36 | const checkout = () => {
37 | history.push("/login?redirect=shipping");
38 | };
39 | return (
40 | <>
41 |
42 |
43 | Shopping Cart
44 | {cartItems.length === 0 ? (
45 |
46 | Your Cart is Empty ! Go Back
47 |
48 | ) : (
49 |
50 | {cartItems.map((item) => (
51 |
52 |
53 |
54 |
55 |
56 |
57 | {item.name}
58 |
59 | ${item.price}
60 |
61 |
65 | dispatch(
66 | addToCart(item.product, Number(e.target.value))
67 | )
68 | }
69 | >
70 | {[...Array(item.countInStock).keys()].map((x) => (
71 |
72 | {x + 1}
73 |
74 | ))}
75 |
76 | removeFromCartHandler(item.product)}
80 | >
81 |
85 |
86 |
87 |
88 |
89 | ))}
90 |
91 | )}
92 |
93 |
94 |
95 |
96 |
97 |
98 | subtotal ({cartItems.reduce((acc, item) => acc + item.qty, 0)}
99 | ) items
100 |
101 | $
102 | {cartItems
103 | .reduce((acc, item) => acc + item.qty * item.price, 0)
104 | .toFixed(2)}
105 |
106 |
112 | Proceed to checkOut
113 |
114 |
115 |
116 |
117 |
118 | >
119 | );
120 | };
121 |
122 | export default CartScreen;
123 |
--------------------------------------------------------------------------------
/frontend/src/screens/PlaceOrderScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Button, Row, Col, ListGroup, Image, Card } from "react-bootstrap";
3 | import { Link } from "react-router-dom";
4 | import { createOrder } from "../actions/orderAction";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import Message from "../components/shared/Message";
7 | import CheckOutStep from "../components/shared/CheckoutStep";
8 |
9 | const PlaceOrderScreen = ({ history }) => {
10 | const cart = useSelector((state) => state.cart);
11 | const dispatch = useDispatch();
12 | const orderCreate = useSelector((state) => state.orderCreate);
13 | const { order, success, error } = orderCreate;
14 |
15 | //fun for decimal
16 | const addDecimal = (num) => {
17 | return (Math.round(num * 100) / 100).toFixed(2);
18 | };
19 | cart.itemsPrice = addDecimal(
20 | cart.cartItems.reduce((acc, item) => acc + item.price * item.qty, 0)
21 | );
22 | cart.shippingPrice = addDecimal(cart.cartItems > 500 ? 0 : 50);
23 | cart.taxPrice = addDecimal(Number((0.15 * cart.itemsPrice).toFixed(2)));
24 | cart.totalPrice = (
25 | Number(cart.itemsPrice) +
26 | Number(cart.shippingPrice) +
27 | Number(cart.taxPrice)
28 | ).toFixed(2);
29 |
30 | const placeOrderHandler = () => {
31 | dispatch(
32 | createOrder({
33 | orderItems: cart.cartItems,
34 | shippingAddress: cart.shippingAddress,
35 | paymentMethod: cart.paymentMethod,
36 | itemsPrice: cart.itemsPrice,
37 | shippingPrice: cart.shippingPrice,
38 | taxPrice: cart.taxPrice,
39 | totalPrice: cart.totalPrice,
40 | })
41 | );
42 | };
43 |
44 | useEffect(() => {
45 | if (success) {
46 | history.push(`/order/${order._id}`);
47 | }
48 | //eslint-disable-next-line
49 | }, [history, success]);
50 | return (
51 | <>
52 |
53 |
54 |
55 |
56 |
57 | Shipping
58 |
59 | Address :
60 | {cart.shippingAddress.address}
61 | {cart.shippingAddress.city}
62 | {cart.shippingAddress.postalcode}
63 | {cart.shippingAddress.country}
64 |
65 |
66 |
67 | Payment Method
68 |
69 | {cart.paymentMethod}
70 |
71 |
72 |
73 | Order Items
74 | {cart.cartItems.length === 0 ? (
75 | Your Cart is Empty
76 | ) : (
77 |
78 | {cart.cartItems.map((item, index) => (
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {item.name}
87 |
88 |
89 |
90 | {item.qty} X ${item.price} = ${item.price}
91 |
92 |
93 |
94 | ))}
95 |
96 | )}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Order Summary
105 |
106 |
107 |
108 | Items
109 | ${cart.itemsPrice}
110 |
111 |
112 | Shipping
113 | ${cart.shippingPrice}
114 |
115 |
116 | Tax
117 | ${cart.taxPrice}
118 |
119 |
120 | Total
121 | ${cart.totalPrice}
122 |
123 |
124 |
125 | {error && {error} }
126 |
127 |
133 | Place Order
134 |
135 |
136 |
137 |
138 |
139 | >
140 | );
141 | };
142 |
143 | export default PlaceOrderScreen;
144 |
--------------------------------------------------------------------------------
/frontend/src/screens/ProfileScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link } from "react-router-dom";
3 | import { Form, Button, Row, Col, Table } from "react-bootstrap";
4 | import { LinkContainer } from "react-router-bootstrap";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import Message from "../components/shared/Message";
7 | import Loader from "../components/shared/Loader";
8 | import { getUserDetails, updateUserProfile } from "../actions/userAction";
9 | import { listMyOrders } from "../actions/orderAction";
10 | import { ORDER_CREATE_REQUEST } from "../constants/orderConstant";
11 |
12 | const ProfileScreen = ({ location, history }) => {
13 | const [name, setName] = useState("");
14 | const [email, setEmail] = useState("");
15 | const [password, setPassword] = useState("");
16 | const [confirmPassword, setConfirmPassword] = useState("");
17 | const [message, setMessage] = useState("");
18 |
19 | const dispatch = useDispatch();
20 | const userDetails = useSelector((state) => state.userDetails);
21 | const { loading, error, user } = userDetails;
22 | const userLogin = useSelector((state) => state.userLogin);
23 | const { userInfo } = userLogin;
24 |
25 | const userUpdateProfile = useSelector((state) => state.userUpdateProfile);
26 | const { success } = userUpdateProfile;
27 |
28 | const orderListMy = useSelector((state) => state.orderListMy);
29 | const { loading: loadingOrders, orders, error: errorOrders } = orderListMy;
30 |
31 | useEffect(() => {
32 | if (!userInfo) {
33 | history.push("/login");
34 | } else {
35 | if (!user.name) {
36 | dispatch(getUserDetails("profile"));
37 | dispatch(listMyOrders());
38 | } else {
39 | setName(user.name);
40 | setEmail(user.email);
41 | }
42 | }
43 | }, [history, userInfo, user, dispatch]);
44 |
45 | const submitHandler = (e) => {
46 | e.preventDefault();
47 | //dispatch
48 | dispatch(updateUserProfile({ id: user._id, name, email, password }));
49 | };
50 |
51 | return (
52 | <>
53 |
54 |
55 | Update Information
56 | {error && {error} }
57 | {success && Profile Updated }
58 | {loading && }
59 | {message && {message} }
60 |
62 | Name
63 | setName(e.target.value)}
68 | >
69 |
70 |
71 | Email Address
72 | setEmail(e.target.value)}
77 | >
78 |
79 |
80 | Password
81 | setPassword(e.target.value)}
86 | >
87 |
88 |
89 | COnfirm Password
90 | setConfirmPassword(e.target.value)}
95 | >
96 |
97 |
98 | Update
99 |
100 |
101 |
102 |
103 | My Orders
104 | {loadingOrders ? (
105 |
106 | ) : errorOrders ? (
107 | {errorOrders}
108 | ) : (
109 |
110 |
111 |
112 | ID
113 | DATE
114 | TOTAL
115 | PAID
116 | DELIVERD
117 |
118 |
119 |
120 |
121 | {orders.map((order) => (
122 |
123 | {order._id}
124 | {order.createdAt.substring(0, 10)}
125 | {order.totalPrice}
126 |
127 | {order.isPaid ? (
128 | order.paidAt.substring(0, 10)
129 | ) : (
130 |
134 | )}
135 |
136 |
137 | {order.isDeleverd ? (
138 | order.deleverdAt.substring(0, 10)
139 | ) : (
140 |
144 | )}
145 |
146 |
147 |
148 | Details
149 |
150 |
151 |
152 | ))}
153 |
154 |
155 | )}
156 |
157 |
158 | >
159 | );
160 | };
161 |
162 | export default ProfileScreen;
163 |
--------------------------------------------------------------------------------
/frontend/src/screens/OrderScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { PayPalButton } from "react-paypal-button-v2";
3 | import axios from "axios";
4 | import { ORDER_PAY_RESET } from "../constants/orderConstant";
5 | import { Button, Row, Col, ListGroup, Image, Card } from "react-bootstrap";
6 | import { Link } from "react-router-dom";
7 | import { getOrderDetails, payOrder } from "../actions/orderAction";
8 | import { useDispatch, useSelector } from "react-redux";
9 | import Message from "../components/shared/Message";
10 | import Loader from "../components/shared/Loader";
11 |
12 | const OrderScreen = ({ match }) => {
13 | const orderId = match.params.id;
14 | const [sdkReady, setSdkReady] = useState(false);
15 | const dispatch = useDispatch();
16 |
17 | const orderDetails = useSelector((state) => state.orderDetails);
18 | const { order, loading, error } = orderDetails;
19 |
20 | const orderPay = useSelector((state) => state.orderPay);
21 | const { loading: loadingPay, success: successpay } = orderPay;
22 | if (!loading) {
23 | // Calculate prices
24 | const addDecimals = (num) => {
25 | return (Math.round(num * 100) / 100).toFixed(2);
26 | };
27 |
28 | order.itemsPrice = addDecimals(
29 | order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0)
30 | );
31 | }
32 |
33 | const successPaymentHandler = (paymentResult) => {
34 | console.log(paymentResult);
35 | dispatch(payOrder(orderId, paymentResult));
36 | };
37 |
38 | useEffect(() => {
39 | const addPaypalScript = async () => {
40 | const { data: clientId } = await axios.get("/api/config/paypal");
41 | const script = document.createElement("script");
42 | script.type = "text/javascript";
43 | script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`;
44 | script.async = true;
45 | script.onload = () => {
46 | setSdkReady(true);
47 | };
48 | document.body.appendChild(script);
49 | };
50 | if (!order || successpay) {
51 | dispatch({ type: ORDER_PAY_RESET });
52 | dispatch(getOrderDetails(orderId));
53 | } else if (!order.isPaid) {
54 | if (!window.paypal) {
55 | addPaypalScript();
56 | } else {
57 | setSdkReady(true);
58 | }
59 | }
60 | }, [dispatch, orderId, order, successpay]);
61 |
62 | return loading ? (
63 |
64 | ) : error ? (
65 | {error}
66 | ) : (
67 | <>
68 | Order {order._id}
69 |
70 |
71 |
72 | Shipping
73 |
74 | Name :
75 | {order.user.name}
76 |
77 |
78 | Email :
79 | {order.user.email}
80 |
81 |
82 | Address :
83 | {order.shippingAddress.address}
84 | {order.shippingAddress.city}
85 | {order.shippingAddress.postalcode}
86 | {order.shippingAddress.country}
87 |
88 | {order.isDeliverd ? (
89 | Paid On {order.isDeliverd}
90 | ) : (
91 | Not Deliverd
92 | )}
93 |
94 |
95 | Payment Method
96 |
97 | Method :
98 | {order.paymentMethod}
99 |
100 | {order.isPaid ? (
101 | Paid On {order.paidAt}
102 | ) : (
103 | Not Paid
104 | )}
105 |
106 |
107 | Order Items
108 | {order.orderItems.length === 0 ? (
109 | Your Cart is Empty
110 | ) : (
111 |
112 | {order.orderItems.map((item, index) => (
113 |
114 |
115 |
116 |
117 |
118 |
119 | {item.name}
120 |
121 |
122 | {item.qty} X ${item.price} = ${item.price}
123 |
124 |
125 |
126 | ))}
127 |
128 | )}
129 |
130 |
131 |
132 |
133 |
134 |
135 | Order Summary
136 |
137 |
138 |
139 | Items
140 | ${order.itemsPrice}
141 |
142 |
143 | Shipping
144 | ${order.shippingPrice}
145 |
146 |
147 | Tax
148 | ${order.taxPrice}
149 |
150 |
151 | Total
152 | ${order.totalPrice}
153 |
154 |
155 |
156 | {error && {error} }
157 |
158 |
159 |
160 | {!order.isPaid && (
161 |
162 | {loadingPay && }
163 | {!sdkReady ? (
164 |
165 | ) : (
166 |
170 | )}
171 |
172 | )}
173 |
174 |
175 | >
176 | );
177 | };
178 |
179 | export default OrderScreen;
180 |
--------------------------------------------------------------------------------
/frontend/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\index.js":"1","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reportWebVitals.js":"2","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\App.js":"3","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\footer.jsx":"4","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\Header.jsx":"5","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\HomeScreen.jsx":"6","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\products.js":"7","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProductScreen.jsx":"8","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\Rating.jsx":"9","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProductDetails.jsx":"10","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\store.js":"11","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\productReducer.js":"12","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\productConstant.js":"13","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\productActions.js":"14","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\Loader.jsx":"15","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\Message.jsx":"16","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\cartScreen.jsx":"17","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\cartReducer.js":"18","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\cartConstant.js":"19","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\cartAction.js":"20","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\CartScreen.jsx":"21","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\userReducers.js":"22","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\userContants.js":"23","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\LoginScreen.jsx":"24","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\FromContainer.jsx":"25","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\userAction.js":"26","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\RegisterScreen.jsx":"27","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProfileScreen.jsx":"28","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ShippingScreen.jsx":"29","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\CheckoutStep.jsx":"30","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\PaymentScreen.jsx":"31","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\orderReducer.js":"32","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\orderConstant.js":"33","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\PlaceOrderScreen.jsx":"34","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\orderAction.js":"35","E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\OrderScreen.jsx":"36"},{"size":597,"mtime":1618340622390,"results":"37","hashOfConfig":"38"},{"size":362,"mtime":1606112611288,"results":"39","hashOfConfig":"38"},{"size":1569,"mtime":1621939478600,"results":"40","hashOfConfig":"38"},{"size":404,"mtime":1608664606332,"results":"41","hashOfConfig":"38"},{"size":1887,"mtime":1620262725192,"results":"42","hashOfConfig":"38"},{"size":1018,"mtime":1618600009666,"results":"43","hashOfConfig":"38"},{"size":2519,"mtime":1608559933897,"results":"44","hashOfConfig":"45"},{"size":917,"mtime":1610052936715,"results":"46","hashOfConfig":"38"},{"size":1579,"mtime":1608670911899,"results":"47","hashOfConfig":"38"},{"size":2845,"mtime":1619031077832,"results":"48","hashOfConfig":"38"},{"size":1821,"mtime":1622442813111,"results":"49","hashOfConfig":"38"},{"size":1043,"mtime":1618688330098,"results":"50","hashOfConfig":"38"},{"size":378,"mtime":1618687874138,"results":"51","hashOfConfig":"38"},{"size":1198,"mtime":1618688812661,"results":"52","hashOfConfig":"38"},{"size":410,"mtime":1618599722179,"results":"53","hashOfConfig":"38"},{"size":204,"mtime":1618599915778,"results":"54","hashOfConfig":"38"},{"size":704,"mtime":1619033534136,"results":"55","hashOfConfig":"45"},{"size":1126,"mtime":1621594607862,"results":"56","hashOfConfig":"38"},{"size":242,"mtime":1621588990324,"results":"57","hashOfConfig":"38"},{"size":1249,"mtime":1621590685925,"results":"58","hashOfConfig":"38"},{"size":3817,"mtime":1619207257439,"results":"59","hashOfConfig":"38"},{"size":2047,"mtime":1622446146681,"results":"60","hashOfConfig":"38"},{"size":921,"mtime":1622445929184,"results":"61","hashOfConfig":"38"},{"size":2348,"mtime":1620178011439,"results":"62","hashOfConfig":"38"},{"size":353,"mtime":1621520414176,"results":"63","hashOfConfig":"38"},{"size":3646,"mtime":1622446276934,"results":"64","hashOfConfig":"38"},{"size":3372,"mtime":1620434593879,"results":"65","hashOfConfig":"38"},{"size":5842,"mtime":1622445366706,"results":"66","hashOfConfig":"38"},{"size":2775,"mtime":1621520499938,"results":"67","hashOfConfig":"38"},{"size":1383,"mtime":1621522476100,"results":"68","hashOfConfig":"38"},{"size":1450,"mtime":1621599106924,"results":"69","hashOfConfig":"38"},{"size":2768,"mtime":1622446697782,"results":"70","hashOfConfig":"38"},{"size":818,"mtime":1622445845960,"results":"71","hashOfConfig":"38"},{"size":4788,"mtime":1621598321206,"results":"72","hashOfConfig":"38"},{"size":3322,"mtime":1622443076002,"results":"73","hashOfConfig":"38"},{"size":5985,"mtime":1621950272013,"results":"74","hashOfConfig":"38"},{"filePath":"75","messages":"76","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"ktug1",{"filePath":"77","messages":"78","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"79","messages":"80","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"81","messages":"82","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"83","messages":"84","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"85","messages":"86","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"87","messages":"88","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"89"},"13ch5wa",{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"92","messages":"93","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"94","messages":"95","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"96","messages":"97","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"98","messages":"99","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"102","messages":"103","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"104","messages":"105","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"106","messages":"107","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"108","messages":"109","errorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"110","usedDeprecatedRules":"111"},{"filePath":"112","messages":"113","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"114","messages":"115","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"116","messages":"117","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"118","messages":"119","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"120","messages":"121","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"122","messages":"123","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"124","messages":"125","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"126","messages":"127","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"128","messages":"129","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"130","messages":"131","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"132","messages":"133","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"134","messages":"135","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"136","messages":"137","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"138","messages":"139","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"140","messages":"141","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"142","messages":"143","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"144","messages":"145","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"146","messages":"147","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"148","messages":"149","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\index.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reportWebVitals.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\App.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\footer.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\Header.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\HomeScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\products.js",[],["150","151"],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProductScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\Rating.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProductDetails.jsx",["152","153"],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\store.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\productReducer.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\productConstant.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\productActions.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\Loader.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\Message.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\cartScreen.jsx",["154","155","156","157","158","159","160","161"],"import React, { useEffect } from \"react\";\r\nimport { useDispatch, useSelector } from \"react-redux\";\r\nimport {\r\n Row,\r\n Col,\r\n Form,\r\n Button,\r\n Card,\r\n Image,\r\n ListGroup,\r\n} from \"react-bootstrap\";\r\nimport { addToCart } from \"../actions/cartAction\";\r\n\r\nconst CartScreen = ({ match, location, history }) => {\r\n const productId = match.params.id;\r\n const qty = location.search ? Number(location.search.split(\"=\")[1]) : 1;\r\n const dispatch = useDispatch();\r\n\r\n useEffect(() => {\r\n if (productId) {\r\n dispatch(addToCart(productId, qty));\r\n }\r\n }, [dispatch, productId, qty]);\r\n\r\n return (\r\n \r\n
CArt Component \r\n \r\n );\r\n};\r\n\r\nexport default CartScreen;\r\n",["162","163"],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\cartReducer.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\cartConstant.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\cartAction.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\CartScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\userReducers.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\userContants.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\LoginScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\FromContainer.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\userAction.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\RegisterScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ProfileScreen.jsx",["164","165","166"],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\ShippingScreen.jsx",["167"],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\components\\shared\\CheckoutStep.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\PaymentScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\reducers\\orderReducer.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\constants\\orderConstant.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\PlaceOrderScreen.jsx",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\actions\\orderAction.js",[],"E:\\a_yt_prac\\youtube\\shopping-app\\frontend\\src\\screens\\OrderScreen.jsx",["168"],{"ruleId":"169","replacedBy":"170"},{"ruleId":"171","replacedBy":"172"},{"ruleId":"173","severity":1,"message":"174","line":19,"column":11,"nodeType":"175","messageId":"176","endLine":19,"endColumn":18},{"ruleId":"173","severity":1,"message":"177","line":19,"column":20,"nodeType":"175","messageId":"176","endLine":19,"endColumn":25},{"ruleId":"173","severity":1,"message":"178","line":2,"column":23,"nodeType":"175","messageId":"176","endLine":2,"endColumn":34},{"ruleId":"173","severity":1,"message":"179","line":4,"column":3,"nodeType":"175","messageId":"176","endLine":4,"endColumn":6},{"ruleId":"173","severity":1,"message":"180","line":5,"column":3,"nodeType":"175","messageId":"176","endLine":5,"endColumn":6},{"ruleId":"173","severity":1,"message":"181","line":6,"column":3,"nodeType":"175","messageId":"176","endLine":6,"endColumn":7},{"ruleId":"173","severity":1,"message":"182","line":7,"column":3,"nodeType":"175","messageId":"176","endLine":7,"endColumn":9},{"ruleId":"173","severity":1,"message":"183","line":8,"column":3,"nodeType":"175","messageId":"176","endLine":8,"endColumn":7},{"ruleId":"173","severity":1,"message":"184","line":9,"column":3,"nodeType":"175","messageId":"176","endLine":9,"endColumn":8},{"ruleId":"173","severity":1,"message":"185","line":10,"column":3,"nodeType":"175","messageId":"176","endLine":10,"endColumn":12},{"ruleId":"169","replacedBy":"186"},{"ruleId":"171","replacedBy":"187"},{"ruleId":"173","severity":1,"message":"188","line":2,"column":10,"nodeType":"175","messageId":"176","endLine":2,"endColumn":14},{"ruleId":"173","severity":1,"message":"189","line":10,"column":10,"nodeType":"175","messageId":"176","endLine":10,"endColumn":30},{"ruleId":"173","severity":1,"message":"190","line":17,"column":19,"nodeType":"175","messageId":"176","endLine":17,"endColumn":29},{"ruleId":"173","severity":1,"message":"191","line":6,"column":8,"nodeType":"175","messageId":"176","endLine":6,"endColumn":20},{"ruleId":"173","severity":1,"message":"182","line":5,"column":10,"nodeType":"175","messageId":"176","endLine":5,"endColumn":16},"no-native-reassign",["192"],"no-negated-in-lhs",["193"],"no-unused-vars","'loading' is assigned a value but never used.","Identifier","unusedVar","'error' is assigned a value but never used.","'useSelector' is defined but never used.","'Row' is defined but never used.","'Col' is defined but never used.","'Form' is defined but never used.","'Button' is defined but never used.","'Card' is defined but never used.","'Image' is defined but never used.","'ListGroup' is defined but never used.",["192"],["193"],"'Link' is defined but never used.","'ORDER_CREATE_REQUEST' is defined but never used.","'setMessage' is assigned a value but never used.","'ChekcoutStep' is defined but never used.","no-global-assign","no-unsafe-negation"]
--------------------------------------------------------------------------------