├── .babelrc
├── .gitignore
├── README.md
├── Screenshots
├── add-product.png
├── admin-products.png
├── cart.png
├── main.png
├── main2.png
├── payment.png
├── placeorder.png
├── product-details.png
├── register.png
├── shipping.png
└── signin.png
├── backend
├── config.js
├── data.js
├── models
│ ├── productModel.js
│ └── usermodel.js
├── routes
│ ├── productRoute.js
│ └── userRoute.js
├── server.js
└── util.js
├── frontend
├── .gitignore
├── README.md
├── debug.log
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── w1.jpg
│ │ ├── w2.jpg
│ │ ├── w3.jpg
│ │ ├── w4.jpg
│ │ ├── wf1.jpg
│ │ ├── wf2.jpg
│ │ ├── wf3.jpg
│ │ └── wf4.jpg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── Images
│ └── logo.png
│ ├── Screens
│ ├── CartScreen.js
│ ├── HomeScreen.js
│ ├── PaymentScreen.js
│ ├── PlaceOrderScreen.js
│ ├── ProductScreen.js
│ ├── ProductsScreen.js
│ ├── RegisterScreen.js
│ ├── ShippingScreen.js
│ └── SigninScreen.js
│ ├── actions
│ ├── CartAction.js
│ ├── productActions.js
│ └── userAction.js
│ ├── components
│ ├── CheckoutSteps.js
│ └── Corousel.js
│ ├── constants
│ ├── cartConstants.js
│ ├── productConstants.js
│ └── userConstants.js
│ ├── data.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reducers
│ ├── cartReducers.js
│ ├── productReducers.js
│ └── userReducer.js
│ ├── serviceWorker.js
│ ├── setupTests.js
│ └── store.js
├── package-lock.json
├── package.json
└── template
├── index.html
└── style.css
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":[
3 | [
4 | "@babel/preset-env"
5 | ]
6 | ]
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # E-Commerce Website using MERN Stack.
2 | A E-Commerce website developed using ReactJS for the frontend, NodeJs for the backend, MongoDB as database.
3 |
4 |
5 | ## Table of contents
6 | * [Prerequisites](#prerequisites)
7 | * [Requirements](#requirements)
8 | * [Technologies](#technologies)
9 | * [Features](#features)
10 | * [Screenshots](#screenshots)
11 | * [Contact](#contact)
12 |
13 |
14 | ## Prerequisites
15 | - Text Editor ([VS Code](https://code.visualstudio.com/download) , [Atom](https://atom.io/), [Brackets](http://brackets.io/), etc.)
16 | - Node.js and npm - [install here](https://www.npmjs.com/get-npm)
17 | - MongoDB - [install here](https://docs.mongodb.com/manual/installation/)
18 |
19 |
20 | ## Requirements
21 | To run this project, install it locally using npm:
22 |
23 | - git clone git@github.com:suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS.git
24 | ```
25 | $ cd E-Commerce-Website-using-ReactJS-NodeJS
26 | ```
27 | - To Run Backend
28 | - open terminal
29 | ```
30 | $ npm install
31 | $ npm start
32 | ```
33 | - To Run Frontend
34 | - open new terminal
35 | ```
36 | $ cd frontend
37 | $ npm install
38 | $ npm start
39 | ```
40 |
41 | ## Technologies
42 | Project is created with:
43 | * HTML5 and CSS3: Semantic Elements, CSS Grid, Flexbox
44 | * React: Components, Props, Events, Hooks, Router, Axios, React-Bootstrap
45 | * Redux: Store, Reducers, Actions
46 | * Node & Express: Web API, Body Parser, File Upload, JWT
47 | * MongoDB: Mongoose
48 | * Development: ESLint, Babel, Git, Github,
49 | * Deployment:
50 |
51 | ## Features
52 | List of features ready and TODOs for future development
53 | * User Login, Signup, User Authentication.
54 | * Admin Login.
55 | * Add, Edit, Remove Products. (Only Admin)
56 | * Add to Cart.
57 |
58 | To-do list:
59 | * Sorting
60 | * Search
61 |
62 | ## Screenshots
63 |
64 | ### Home Page
65 | This is the home page of e-commerce. It shows a list of products. It also uses React-Bootstrap Corousel for corousel.
66 |
67 | 
68 |
69 | ________________________________________________________
70 |
71 | ### Product Details Page
72 | When the user clicks on a product it takes you to the product details page.
73 |
74 | 
75 | ________________________________________________________
76 |
77 | ### Cart
78 | Shopping Cart is the heart of any e-commerce website. I have created a user-friendly shopping cart using React and Redux.
79 |
80 | 
81 | ________________________________________________________
82 |
83 | ### Register
84 | I have created forms for getting user info and save them in the database.
85 |
86 | 
87 | ________________________________________________________
88 |
89 | ### Sign-In
90 | Sign in page for user to sign in.
91 |
92 | 
93 | ________________________________________________________
94 |
95 | ## Ordering Products
96 |
97 | Sign in page for user to sign in.
98 |
99 | ### Shipping Screen
100 | 
101 | ________________________________________________________
102 | ### Payment Screen
103 | 
104 | ________________________________________________________
105 | ### Place Order Screen
106 | 
107 | ________________________________________________________
108 |
109 | ### Admin
110 | Admin should be able to define products and update the count in stock whenever they like. This page is about managing ECommerce products.
111 |
112 | ## View Products (admin)
113 |
114 | 
115 | ________________________________________________________
116 |
117 | ### Add Products (admin)
118 |
119 | 
120 | ________________________________________________________
121 |
122 |
123 | ## Contact
124 | [Suhas Suhas](https://www.suhassalian.netlify.com/) - feel free to contact me!
125 |
--------------------------------------------------------------------------------
/Screenshots/add-product.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/add-product.png
--------------------------------------------------------------------------------
/Screenshots/admin-products.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/admin-products.png
--------------------------------------------------------------------------------
/Screenshots/cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/cart.png
--------------------------------------------------------------------------------
/Screenshots/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/main.png
--------------------------------------------------------------------------------
/Screenshots/main2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/main2.png
--------------------------------------------------------------------------------
/Screenshots/payment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/payment.png
--------------------------------------------------------------------------------
/Screenshots/placeorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/placeorder.png
--------------------------------------------------------------------------------
/Screenshots/product-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/product-details.png
--------------------------------------------------------------------------------
/Screenshots/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/register.png
--------------------------------------------------------------------------------
/Screenshots/shipping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/shipping.png
--------------------------------------------------------------------------------
/Screenshots/signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/signin.png
--------------------------------------------------------------------------------
/backend/config.js:
--------------------------------------------------------------------------------
1 | import dotenv from "dotenv";
2 |
3 | dotenv.config();
4 |
5 | export default {
6 | MONGODB_URL: process.env.MONGODB_URL || "mongodb://localhost/amazon",
7 | JWT_SECRET: process.env.JWT_SECRET || "somethingsecret"
8 | };
9 |
--------------------------------------------------------------------------------
/backend/data.js:
--------------------------------------------------------------------------------
1 | // export default {
2 | // products: [
3 | // {
4 | // _id: "1",
5 | // name: "Nike Men's Regular fit T-Shirt",
6 | // category: "Shirt",
7 | // image: "/images/d1.jpg",
8 | // price: 35,
9 | // brand: " Nike",
10 | // rating: 3.0,
11 | // numReviews: 13,
12 | // countInStock: 6
13 | // },
14 | // {
15 | // _id: "2",
16 | // name: "Nike Men's Track Pants",
17 | // category: "Pants",
18 | // image: "/images/d2.jpg",
19 | // price: 50,
20 | // brand: " Nike",
21 | // rating: 4.2,
22 | // numReviews: 66,
23 | // countInStock: 8
24 | // },
25 | // {
26 | // _id: "3",
27 | // name: "Nike Men's Track Pants Gray",
28 | // category: "Pants",
29 | // image: "/images/d3.jpg",
30 | // price: 70,
31 | // brand: " Nike",
32 | // rating: 4,
33 | // numReviews: 8,
34 | // countInStock: 8
35 | // },
36 | // {
37 | // _id: "4",
38 | // name: "Nike Girl's Plain Regular fit T-Shirt",
39 | // category: "Pants",
40 | // image: "/images/d4.jpg",
41 | // price: 40,
42 | // brand: " Nike",
43 | // rating: 3.5,
44 | // numReviews: 18,
45 | // countInStock: 9
46 | // }
47 | // ]
48 | // };
49 |
--------------------------------------------------------------------------------
/backend/models/productModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const productSchema = new mongoose.Schema({
4 | name: { type: String, required: true },
5 | image: { type: String, required: true },
6 | brand: { type: String, required: true },
7 | price: { type: Number, default: 0, required: true },
8 | category: { type: String, required: true },
9 | countInStock: { type: Number, default: 0, required: true },
10 | description: { type: String, required: true },
11 | rating: { type: Number, default: 0, required: true },
12 | numReviews: { type: Number, default: 0, required: true }
13 | });
14 |
15 | const productModel = mongoose.model("Product", productSchema);
16 |
17 | export default productModel;
18 |
--------------------------------------------------------------------------------
/backend/models/usermodel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema({
4 | name: { type: String, required: true },
5 | email: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | index: true,
10 | dropDups: true
11 | },
12 | password: { type: String, required: true },
13 | isAdmin: { type: Boolean, required: true, default: false }
14 | });
15 |
16 | const userModel = mongoose.model("User", userSchema);
17 |
18 | export default userModel;
19 |
--------------------------------------------------------------------------------
/backend/routes/productRoute.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import Product from "../models/productModel";
3 | import { isAuth, isAdmin } from "../util";
4 |
5 | const router = express.Router();
6 |
7 | router.get("/", async (req, res) => {
8 | const products = await Product.find({});
9 | res.send(products);
10 | });
11 | router.get("/:id", async (req, res) => {
12 | const product = await Product.findOne({ _id: req.params.id });
13 | if (product) {
14 | res.send(product);
15 | } else {
16 | res.status(404).send({ message: "404 product not found" });
17 | }
18 | res.send(product);
19 | });
20 |
21 | router.post("/", async (req, res) => {
22 | const product = new Product({
23 | name: req.body.name,
24 | price: req.body.price,
25 | image: req.body.image,
26 | brand: req.body.brand,
27 | category: req.body.category,
28 | countInStock: req.body.countInStock,
29 | description: req.body.description,
30 | rating: req.body.rating,
31 | numReviews: req.body.numReviews
32 | });
33 | const newProduct = await product.save();
34 | if (newProduct) {
35 | return res
36 | .status(201)
37 | .send({ message: "New Product Created", data: newProduct });
38 | }
39 | return res.status(500).send({ message: "Error in Creating Product." });
40 | });
41 |
42 | router.put("/:id", async (req, res) => {
43 | const productId = req.params.id;
44 | const product = await Product.findById({ _id: productId });
45 | if (product) {
46 | product.name = req.body.name;
47 | product.price = req.body.price;
48 | product.image = req.body.image;
49 | product.brand = req.body.brand;
50 | product.category = req.body.category;
51 | product.countInStock = req.body.countInStock;
52 | product.description = req.body.description;
53 | const updatedProduct = await product.save();
54 | if (updatedProduct) {
55 | return res
56 | .status(200)
57 | .send({ message: "Product Updated", data: updatedProduct });
58 | }
59 | }
60 | return res.status(500).send({ message: "Error in Updating Product." });
61 | });
62 | router.delete("/:id", isAuth, isAdmin, async (req, res) => {
63 | const deletedProduct = await Product.findById(req.params.id);
64 | if (deletedProduct) {
65 | await deletedProduct.remove();
66 | res.send({ message: "Product Deleted" });
67 | } else {
68 | res.send("Error in deleting Product.");
69 | }
70 | });
71 |
72 | export default router;
73 |
--------------------------------------------------------------------------------
/backend/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import User from "../models/userModel";
3 | import { getToken } from "../util";
4 |
5 | const router = express.Router();
6 |
7 | router.post("/signin", async (req, res) => {
8 | const signinUser = await User.findOne({
9 | email: req.body.email,
10 | password: req.body.password
11 | });
12 | if (signinUser) {
13 | res.send({
14 | _id: signinUser.id,
15 | name: signinUser.name,
16 | email: signinUser.email,
17 | isAdmin: signinUser.isAdmin,
18 | token: getToken(signinUser)
19 | });
20 | } else {
21 | res.status(401).send({ message: "Invalid Email or Password." });
22 | }
23 | });
24 | router.post("/register", async (req, res) => {
25 | const user = new User({
26 | name: req.body.name,
27 | email: req.body.email,
28 | password: req.body.password
29 | });
30 | const newUser = await user.save();
31 | if (newUser) {
32 | res.send({
33 | _id: newUser.id,
34 | name: newUser.name,
35 | email: newUser.email,
36 | isAdmin: newUser.isAdmin,
37 | token: getToken(newUser)
38 | });
39 | } else {
40 | res.status(401).send({ message: "Invalid Userdata." });
41 | }
42 | });
43 |
44 | router.get("/createadmin", async (req, res) => {
45 | try {
46 | const user = new User({
47 | name: "suhas",
48 | email: "suhas@gmail.com",
49 | password: "1234",
50 | isAdmin: true
51 | });
52 | const newUser = await user.save();
53 | res.send(newUser);
54 | } catch (error) {
55 | res.send({ message: error.message });
56 | }
57 | });
58 |
59 | export default router;
60 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import config from "./config";
3 | import dotenv from "dotenv";
4 | import mongoose from "mongoose";
5 | import bodyParser from "body-parser";
6 | import userRoute from "./routes/userRoute";
7 | import productRoute from "./routes/productRoute";
8 |
9 | const path = require("path");
10 |
11 | dotenv.config();
12 |
13 | const PORT = process.env.PORT || 5000;
14 | const mongodbUrl = config.MONGODB_URL;
15 |
16 | mongoose
17 | .connect(mongodbUrl, {
18 | useNewUrlParser: true,
19 | useUnifiedTopology: true,
20 | useCreateIndex: true
21 | })
22 | .catch(error => console.log(error.reason));
23 |
24 | const app = express();
25 |
26 | app.use(bodyParser.json());
27 | app.use("/api/users", userRoute);
28 | app.use("/api/products", productRoute);
29 |
30 | // Serve static assests if in production
31 |
32 | if (process.env.NODE_ENV === "production") {
33 | app.use(express.static("client/build"));
34 |
35 | app.get("*", (req, res) => {
36 | res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
37 | });
38 | }
39 |
40 | app.listen(PORT, () =>
41 | console.log(
42 | "************************************************** \n The Server has started at : http://localhost:5000"
43 | )
44 | );
45 |
--------------------------------------------------------------------------------
/backend/util.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import config from "./config";
3 | const getToken = user => {
4 | return jwt.sign(
5 | {
6 | _id: user._id,
7 | name: user.name,
8 | email: user.email,
9 | isAdmin: user.isAdmin
10 | },
11 | config.JWT_SECRET,
12 | {
13 | expiresIn: "48h"
14 | }
15 | );
16 | };
17 |
18 | const isAuth = (req, res, next) => {
19 | const token = req.headers.authorization;
20 | if (token) {
21 | const onlyToken = token.slice(7, token.length);
22 | jwt.verify(onlyToken, config.JWT_SECRET, (err, decode) => {
23 | if (err) {
24 | return res.status(401).send({ msg: "Invalid Token" });
25 | }
26 | req.user = decode;
27 | next();
28 | return;
29 | });
30 | 0;
31 | } else {
32 | return res.status(401).send({ msg: "Token is not supplied." });
33 | }
34 | };
35 | const isAdmin = (req, res, next) => {
36 | if (req.user && req.user.isAdmin) {
37 | return next();
38 | }
39 | return res.status(401).send({ msg: "Admin Token is not valid" });
40 | };
41 |
42 | export { getToken, isAuth, isAdmin };
43 |
--------------------------------------------------------------------------------
/frontend/.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 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | 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.
35 |
36 | 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.
37 |
38 | 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.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/frontend/debug.log:
--------------------------------------------------------------------------------
1 | [0710/104803.603:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5)
2 | [0710/104803.604:ERROR:process_info.cc(556)] ReadMemoryInfo failed
3 | [0710/104803.604:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a)
4 | [0710/105742.914:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5)
5 | [0710/105742.915:ERROR:process_info.cc(556)] ReadMemoryInfo failed
6 | [0710/105742.915:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a)
7 | [0710/111326.919:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5)
8 | [0710/111326.920:ERROR:process_info.cc(556)] ReadMemoryInfo failed
9 | [0710/111326.948:ERROR:process_memory_win.cc(39)] Failed to initialize ProcessInfo.
10 | [0710/111326.948:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a)
11 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "proxy": "http://localhost:5000",
4 | "secure": false,
5 | "version": "0.1.0",
6 | "private": true,
7 | "dependencies": {
8 | "@testing-library/jest-dom": "^4.2.4",
9 | "@testing-library/react": "^9.5.0",
10 | "@testing-library/user-event": "^7.2.1",
11 | "@types/js-cookie": "^2.2.6",
12 | "axios": "^0.19.2",
13 | "js-cookie": "^2.2.1",
14 | "react": "^16.13.1",
15 | "react-dom": "^16.13.1",
16 | "react-redux": "^7.2.0",
17 | "react-router-dom": "^5.2.0",
18 | "react-scripts": "3.4.1",
19 | "redux": "^4.0.5",
20 | "redux-thunk": "^2.3.0"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": "react-app"
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/images/w1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w1.jpg
--------------------------------------------------------------------------------
/frontend/public/images/w2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w2.jpg
--------------------------------------------------------------------------------
/frontend/public/images/w3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w3.jpg
--------------------------------------------------------------------------------
/frontend/public/images/w4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w4.jpg
--------------------------------------------------------------------------------
/frontend/public/images/wf1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf1.jpg
--------------------------------------------------------------------------------
/frontend/public/images/wf2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf2.jpg
--------------------------------------------------------------------------------
/frontend/public/images/wf3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf3.jpg
--------------------------------------------------------------------------------
/frontend/public/images/wf4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf4.jpg
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Origami
34 |
35 |
36 |
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/logo512.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 |
4 | import { BrowserRouter, Route, Link } from "react-router-dom";
5 | import { useSelector } from "react-redux";
6 |
7 | import HomeScreen from "./screens/HomeScreen";
8 | import ProductScreen from "./screens/ProductScreen";
9 | import CartScreen from "./screens/CartScreen";
10 | import SigninScreen from "./screens/SigninScreen";
11 | import RegisterScreen from "./screens/RegisterScreen";
12 | import ProductsScreen from "./screens/ProductsScreen";
13 | import ShipppingScreen from "./screens/ShippingScreen";
14 | import PaymentScreen from "./screens/PaymentScreen";
15 | import PlaceOrderScreen from "./screens/PlaceOrderScreen";
16 |
17 | import logo from "./Images/logo.png";
18 |
19 | function App() {
20 | const userSignin = useSelector(state => state.userSignin);
21 | const { userInfo } = userSignin;
22 | const openmenu = () => {
23 | document.querySelector(".sidebar").classList.add("open");
24 | };
25 | const closemenu = () => {
26 | document.querySelector(".sidebar").classList.remove("open");
27 | };
28 | return (
29 |
30 |
31 |
58 |
81 |
82 |
83 |
84 |
85 |
86 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | );
101 | }
102 |
103 | export default App;
104 |
--------------------------------------------------------------------------------
/frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/frontend/src/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/src/Images/logo.png
--------------------------------------------------------------------------------
/frontend/src/Screens/CartScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { addToCart, removeFromCart } from "../actions/cartAction";
4 | import { Link } from "react-router-dom";
5 |
6 | function CartScreen(props) {
7 | const cart = useSelector(state => state.cart);
8 | const { cartItems } = cart;
9 | const productId = props.match.params.id;
10 | const qty = props.location.search
11 | ? Number(props.location.search.split("=")[1])
12 | : 1;
13 | const dispatch = useDispatch();
14 | const removeFromCartHandler = productId => {
15 | dispatch(removeFromCart(productId));
16 | };
17 | useEffect(() => {
18 | window.scrollTo(0, 0);
19 | if (productId) {
20 | dispatch(addToCart(productId, qty));
21 | }
22 | }, []);
23 | const checkoutHandler = () => {
24 | props.history.push("/signin?redirect=shipping");
25 | };
26 |
27 | return (
28 |
29 |
30 |
31 | -
32 |
Your Cart
33 | Price
34 |
35 | {cartItems.length === 0 ? (
36 |
37 |
Cart is Empty.
38 |
39 | ) : (
40 | cartItems.map(item => (
41 | -
42 |
43 |

44 |
45 |
46 |
47 |
48 | {item.name}
49 |
50 |
51 |
52 | Quantity :
53 |
68 |
79 |
80 |
81 | ${item.price}
82 |
83 | ))
84 | )}
85 |
86 |
87 |
88 |
89 | Subtotal ({cartItems.reduce((a, c) => a + c.qty, 0)} items)
90 | : ${cartItems.reduce((a, c) => a + c.price * c.qty, 0)}
91 |
92 |
99 |
100 |
101 | );
102 | }
103 |
104 | export default CartScreen;
105 |
--------------------------------------------------------------------------------
/frontend/src/Screens/HomeScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { listProducts } from "../actions/productActions";
5 |
6 | import Corousel from "../components/Corousel";
7 |
8 | function HomeScreen(props) {
9 | const productList = useSelector(state => state.productList);
10 | const { products, loading, error } = productList;
11 | const dispatch = useDispatch();
12 |
13 | useEffect(() => {
14 | window.scrollTo(0, 0);
15 | dispatch(listProducts());
16 | return () => {
17 | };
18 | }, []);
19 |
20 | return (
21 |
22 |
23 | {loading ? (
24 |
loading...
25 | ) : error ? (
26 |
{error}
27 | ) : (
28 |
29 | {products.map(product => (
30 | -
31 |
32 |
33 |

38 |
39 |
40 |
41 | {product.name}
42 |
43 |
44 |
45 | {product.brand}
46 |
47 |
48 | ${product.price}
49 |
50 |
51 | {/* {product.rating} Stars ({product.numReviews} Reviews) */}
52 |
53 |
54 |
55 | ))}
56 |
57 | )}
58 |
59 | );
60 | }
61 |
62 | export default HomeScreen;
63 |
--------------------------------------------------------------------------------
/frontend/src/Screens/PaymentScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { savePayment } from "../actions/cartAction";
5 | import CheckoutSteps from "../components/CheckoutSteps";
6 |
7 | function PaymentScreen(props) {
8 | const [paymentMethod, setPaymentMethod] = useState("");
9 |
10 | const dispatch = useDispatch();
11 |
12 | const submitHandler = e => {
13 | e.preventDefault();
14 | dispatch(savePayment(paymentMethod));
15 | props.history.push("placeorder");
16 | };
17 | return (
18 |
45 | );
46 | }
47 | export default PaymentScreen;
48 |
--------------------------------------------------------------------------------
/frontend/src/Screens/PlaceOrderScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { addToCart, removeFromCart } from "../actions/cartAction";
4 | import { Link } from "react-router-dom";
5 | import CheckoutSteps from "../components/CheckoutSteps";
6 |
7 | function PlaceOrderScreen(props) {
8 | const cart = useSelector(state => state.cart);
9 | const { cartItems, shipping, payment } = cart;
10 | if (!shipping.address) {
11 | props.history.push("/shipping");
12 | } else if (!payment) {
13 | props.history.push("/payment");
14 | }
15 | const itemsPrice = cartItems.reduce((a, c) => a + c.price * c.qty, 0);
16 | const shippingPrice = itemsPrice > 100 ? 0 : 10;
17 | const taxPrice = 0.15 * itemsPrice;
18 | const totalPrice = itemsPrice + shippingPrice + taxPrice;
19 |
20 | const dispatch = useDispatch();
21 |
22 | useEffect(() => {
23 | window.scrollTo(0, 0);
24 | }, []);
25 | const placeOrderHandler = () => {
26 | props.history.push("/");
27 | alert("Order placed successfully.");
28 | };
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
Shipping
38 |
39 | {cart.shipping.address},{cart.shipping.city},
40 | {cart.shipping.postalCode},{cart.shipping.country},
41 |
42 |
43 |
44 |
Payment
45 |
Payment Method : {cart.payment.paymentMethod}
46 |
47 |
48 |
49 | -
50 |
Shopping Cart
51 | Price
52 |
53 | {cartItems.length === 0 ? (
54 | Cart is Empty.
55 | ) : (
56 | cartItems.map(item => (
57 | -
58 |
59 |

63 |
64 |
65 |
66 |
72 | {item.name}
73 |
74 |
75 |
Quantity : {item.qty}
76 |
77 |
78 | ${item.price}
79 |
80 |
81 | ))
82 | )}
83 |
84 |
85 |
86 |
87 |
88 | -
89 |
95 |
96 | -
97 |
Order Summary
98 |
99 | -
100 |
Items
101 | ${itemsPrice}
102 |
103 | -
104 |
Shipping
105 | ${shippingPrice}
106 |
107 | -
108 |
Tax
109 | ${taxPrice}
110 |
111 | -
112 |
Order Total
113 | ${totalPrice}
114 |
115 |
116 |
117 |
118 |
119 | );
120 | }
121 |
122 | export default PlaceOrderScreen;
123 |
--------------------------------------------------------------------------------
/frontend/src/Screens/ProductScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { detailsProduct } from "../actions/productActions";
5 |
6 | function ProductScreen(props) {
7 | const [qty, setQty] = useState(1);
8 | const productDetails = useSelector(state => state.productDetails);
9 | const { product, loading, error } = productDetails;
10 | const dispatch = useDispatch();
11 |
12 | useEffect(() => {
13 | window.scrollTo(0, 0)
14 | dispatch(detailsProduct(props.match.params.id));
15 | return () => {
16 | //
17 | };
18 | }, []);
19 |
20 | const handleAddToCart = () => {
21 | props.history.push("/cart/" + props.match.params.id + "?qty=" + qty);
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | arrow_back
29 |
30 |
31 | {loading ? (
32 |
Loading...
33 | ) : error ? (
34 |
{error}
35 | ) : (
36 |
37 |
38 |

39 |
40 |
41 |
42 | -
43 |
{product.brand}
44 |
45 | -
46 |
{product.name}
47 |
48 | -
49 | {product.rating} Stars ({product.numReviews}{" "}
50 | Reviews)
51 |
52 | -
53 | ${product.price}
54 |
55 | -
56 |
{product.description}
57 |
58 | -
59 | Net Qty :
60 |
75 |
76 | -
77 | {product.countInStock > 0
78 | ? "In Stock"
79 | : "Unavailble"}
80 |
81 | -
82 | {product.countInStock > 0 && (
83 |
89 | )}
90 | {console.log(product.category)}
91 |
92 |
93 |
94 |
95 | )}
96 |
97 | );
98 | }
99 | export default ProductScreen;
100 |
--------------------------------------------------------------------------------
/frontend/src/Screens/ProductsScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import {
4 | saveProduct,
5 | listProducts,
6 | deleteProduct
7 | } from "../actions/productActions";
8 |
9 | function ProductsScreen(props) {
10 | const [modalVisible, setModalVisible] = useState(false);
11 | const [id, setId] = useState("");
12 | const [name, setName] = useState("");
13 | const [price, setPrice] = useState("");
14 | const [image, setImage] = useState("");
15 | const [brand, setBrand] = useState("");
16 | const [category, setCategory] = useState("");
17 | const [countInStock, setCountInStock] = useState("");
18 | const [description, setDescription] = useState("");
19 | const productList = useSelector(state => state.productList);
20 | const { loading, products, error } = productList;
21 |
22 | const productSave = useSelector(state => state.productSave);
23 | const {
24 | loading: loadingSave,
25 | success: successSave,
26 | error: errorSave
27 | } = productSave;
28 |
29 | const productDelete = useSelector(state => state.productDelete);
30 | const {
31 | loading: loadingDelete,
32 | success: successDelete,
33 | error: errorDelete
34 | } = productDelete;
35 |
36 | const dispatch = useDispatch();
37 |
38 | useEffect(() => {
39 | if (successSave) {
40 | setModalVisible(false);
41 | }
42 | dispatch(listProducts());
43 | return () => {
44 | //
45 | };
46 | }, [successSave, successDelete]);
47 |
48 | const openModal = product => {
49 | setModalVisible(true);
50 | setId(product._id);
51 | setName(product.name);
52 | setPrice(product.price);
53 | setDescription(product.description);
54 | setImage(product.image);
55 | setBrand(product.brand);
56 | setCategory(product.category);
57 | setCountInStock(product.countInStock);
58 | };
59 |
60 | const submitHandler = e => {
61 | e.preventDefault();
62 | dispatch(
63 | saveProduct({
64 | _id: id,
65 | name,
66 | price,
67 | image,
68 | brand,
69 | category,
70 | countInStock,
71 | description
72 | })
73 | );
74 | };
75 |
76 | const deleteHandler = product => {
77 | dispatch(deleteProduct(product._id));
78 | };
79 |
80 | return (
81 |
82 |
83 |
Products
84 |
90 |
91 | {modalVisible && (
92 |
198 | )}
199 |
200 |
201 |
202 |
203 |
204 | ID |
205 | Name |
206 | Price |
207 | Category |
208 | Brand |
209 | Count In Stock |
210 | Action |
211 |
212 |
213 |
214 | {products.map(product => (
215 |
216 | {product._id} |
217 | {product.name} |
218 | {product.price} |
219 | {product.category} |
220 | {product.brand} |
221 | {product.countInStock} |
222 |
223 |
229 | {" "}
230 |
236 | |
237 |
238 | ))}
239 |
240 |
241 |
242 |
243 | );
244 | }
245 | export default ProductsScreen;
246 |
--------------------------------------------------------------------------------
/frontend/src/Screens/RegisterScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { register } from "../actions/userAction";
5 |
6 | function RegisterScreen(props) {
7 | const [name, setName] = useState("");
8 | const [email, setEmail] = useState("");
9 | const [password, setPassword] = useState("");
10 | const [rePassword, setRePassword] = useState("");
11 | const userRegister = useSelector(state => state.userRegister);
12 | const { loading, userInfo, error } = userRegister;
13 | const dispatch = useDispatch();
14 | const redirect = props.location.search
15 | ? props.location.search.split("=")[1]
16 | : "/";
17 |
18 | useEffect(() => {
19 | window.scrollTo(0, 0);
20 | if (userInfo) {
21 | props.history.push(redirect);
22 | }
23 | return () => {
24 | //
25 | };
26 | }, [userInfo]);
27 |
28 | const submitHandler = e => {
29 | e.preventDefault();
30 | dispatch(register(name, email, password));
31 | };
32 | return (
33 |
100 | );
101 | }
102 | export default RegisterScreen;
103 |
--------------------------------------------------------------------------------
/frontend/src/Screens/ShippingScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { saveShipping } from "../actions/cartAction";
5 | import CheckoutSteps from "../components/CheckoutSteps";
6 |
7 | function ShippingScreen(props) {
8 | const [address, setAddress] = useState("");
9 | const [city, setCity] = useState("");
10 | const [postalCode, setPostalCode] = useState("");
11 | const [country, setCountry] = useState("");
12 |
13 | const dispatch = useDispatch();
14 |
15 | const submitHandler = e => {
16 | e.preventDefault();
17 | dispatch(saveShipping({ address, city, postalCode, country }));
18 | props.history.push("payment");
19 | };
20 | return (
21 |
75 | );
76 | }
77 | export default ShippingScreen;
78 |
--------------------------------------------------------------------------------
/frontend/src/Screens/SigninScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { signin } from "../actions/userAction";
5 |
6 | function SigninScreen(props) {
7 | const [email, setEmail] = useState("");
8 | const [password, setPassword] = useState("");
9 | const userSignin = useSelector(state => state.userSignin);
10 | const { loading, userInfo, error } = userSignin;
11 | const dispatch = useDispatch();
12 | const redirect = props.location.search
13 | ? props.location.search.split("=")[1]
14 | : "/";
15 |
16 | useEffect(() => {
17 | window.scrollTo(0, 0);
18 | if (userInfo) {
19 | props.history.push(redirect);
20 | }
21 | return () => {
22 | };
23 | }, [userInfo]);
24 |
25 | const submitHandler = e => {
26 | e.preventDefault();
27 | dispatch(signin(email, password));
28 | };
29 | return (
30 |
79 | );
80 | }
81 | export default SigninScreen;
82 |
--------------------------------------------------------------------------------
/frontend/src/actions/CartAction.js:
--------------------------------------------------------------------------------
1 | import Axios from "axios";
2 | import Cookie from "js-cookie";
3 | import {
4 | CART_ADD_ITEM,
5 | CART_REMOVE_ITEM,
6 | CART_SAVE_SHIPPING,
7 | CART_SAVE_PAYMENT
8 | } from "../constants/cartConstants";
9 |
10 | const addToCart = (productId, qty) => async (dispatch, getState) => {
11 | try {
12 | const { data } = await Axios.get("/api/products/" + productId);
13 | dispatch({
14 | type: CART_ADD_ITEM,
15 | payload: {
16 | product: data._id,
17 | name: data.name,
18 | image: data.image,
19 | price: data.price,
20 | countInStock: data.countInStock,
21 | qty
22 | }
23 | });
24 | const {
25 | cart: { cartItems }
26 | } = getState();
27 | Cookie.set("cartItems", JSON.stringify(cartItems));
28 | } catch (error) {}
29 | };
30 |
31 | const removeFromCart = productId => (dispatch, getState) => {
32 | dispatch({
33 | type: CART_REMOVE_ITEM,
34 | payload: productId
35 | });
36 | const {
37 | cart: { cartItems }
38 | } = getState();
39 | Cookie.set("cartItems", JSON.stringify(cartItems));
40 | };
41 |
42 | const saveShipping = data => dispatch => {
43 | dispatch({ type: CART_SAVE_SHIPPING, payload: data });
44 | };
45 | const savePayment = data => dispatch => {
46 | dispatch({ type: CART_SAVE_PAYMENT, payload: data });
47 | };
48 |
49 | export { addToCart, removeFromCart, saveShipping, savePayment };
50 |
--------------------------------------------------------------------------------
/frontend/src/actions/productActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | PRODUCT_LIST_REQUEST,
3 | PRODUCT_LIST_SUCCESS,
4 | PRODUCT_LIST_FAIL,
5 | PRODUCT_DETAILS_REQUEST,
6 | PRODUCT_DETAILS_SUCCESS,
7 | PRODUCT_DETAILS_FAIL,
8 | PRODUCT_SAVE_REQUEST,
9 | PRODUCT_SAVE_SUCCESS,
10 | PRODUCT_SAVE_FAIL,
11 | PRODUCT_DELETE_SUCCESS,
12 | PRODUCT_DELETE_FAIL,
13 | PRODUCT_DELETE_REQUEST
14 | } from "../constants/productConstants";
15 | import axios from "axios";
16 |
17 | const listProducts = () => async dispatch => {
18 | try {
19 | dispatch({ type: PRODUCT_LIST_REQUEST });
20 | const { data } = await axios.get("/api/products");
21 | dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
22 | } catch (error) {
23 | dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
24 | }
25 | };
26 |
27 | const saveProduct = product => async (dispatch, getState) => {
28 | try {
29 | dispatch({ type: PRODUCT_SAVE_REQUEST, payload: product });
30 | const {
31 | userSignin: { userInfo }
32 | } = getState();
33 | if (!product._id) {
34 | const { data } = await axios.post("/api/products", product, {
35 | headers: {
36 | Authorization: "Bearer" + userInfo.token
37 | }
38 | });
39 |
40 | dispatch({ type: PRODUCT_SAVE_SUCCESS, payload: data });
41 | } else {
42 | const { data } = await axios.put(
43 | "/api/products/" + product._id,
44 | product,
45 | {
46 | headers: {
47 | Authorization: "Bearer" + userInfo.token
48 | }
49 | }
50 | );
51 | dispatch({ type: PRODUCT_SAVE_SUCCESS, payload: data });
52 | }
53 | } catch (error) {
54 | dispatch({ type: PRODUCT_SAVE_FAIL, payload: error.message });
55 | }
56 | };
57 |
58 | const deleteProduct = productId => async (dispatch, getState) => {
59 | try {
60 | const {
61 | userSignin: { userInfo }
62 | } = getState();
63 | dispatch({ type: PRODUCT_DELETE_REQUEST, payload: productId });
64 | const { data } = await axios.delete("/api/products/" + productId, {
65 | Authorization: "Bearer " + userInfo.token
66 | });
67 | dispatch({
68 | type: PRODUCT_DELETE_SUCCESS,
69 | payload: data,
70 | success: true
71 | });
72 | } catch (error) {
73 | dispatch({ type: PRODUCT_DELETE_FAIL, payload: error.message });
74 | }
75 | };
76 | const detailsProduct = productId => async dispatch => {
77 | try {
78 | dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
79 | const { data } = await axios.get("/api/products/" + productId);
80 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
81 | } catch (error) {
82 | dispatch({ type: PRODUCT_DETAILS_FAIL, payload: error.message });
83 | }
84 | };
85 |
86 | export { listProducts, detailsProduct, saveProduct, deleteProduct };
87 |
--------------------------------------------------------------------------------
/frontend/src/actions/userAction.js:
--------------------------------------------------------------------------------
1 | import Axios from "axios";
2 | import Cookie from "js-cookie";
3 | import {
4 | USER_SIGNIN_REQUEST,
5 | USER_SIGNIN_SUCCESS,
6 | USER_SIGNIN_FAIL,
7 | USER_REGISTER_FAIL,
8 | USER_REGISTER_SUCCESS,
9 | USER_REGISTER_REQUEST
10 | } from "../constants/userConstants";
11 |
12 | const signin = (email, password) => async dispatch => {
13 | dispatch({ type: USER_SIGNIN_REQUEST, payload: { email, password } });
14 | try {
15 | const { data } = await Axios.post("/api/users/signin", {
16 | email,
17 | password
18 | });
19 | dispatch({ type: USER_SIGNIN_SUCCESS, payload: data });
20 | Cookie.set("userInfo", JSON.stringify(data));
21 | } catch (error) {
22 | dispatch({ type: USER_SIGNIN_FAIL, payload: error.message });
23 | }
24 | };
25 | const register = (name, email, password) => async dispatch => {
26 | dispatch({
27 | type: USER_REGISTER_REQUEST,
28 | payload: { name, email, password }
29 | });
30 | try {
31 | const { data } = await Axios.post("/api/users/register", {
32 | name,
33 | email,
34 | password
35 | });
36 | dispatch({ type: USER_REGISTER_SUCCESS, payload: data });
37 | Cookie.set("userInfo", JSON.stringify(data));
38 | } catch (error) {
39 | dispatch({ type: USER_REGISTER_FAIL, payload: error.message });
40 | }
41 | };
42 |
43 | export { signin, register };
44 |
--------------------------------------------------------------------------------
/frontend/src/components/CheckoutSteps.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function CheckoutSteps(props) {
4 | return (
5 |
6 |
Signin
7 |
Shipping
8 |
Payment
9 |
Place Order
10 |
11 | );
12 | }
13 | export default CheckoutSteps;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/Corousel.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Carousel } from "react-bootstrap";
3 |
4 | import logo from "../Images/logo.png";
5 |
6 | function Coursel() {
7 | return (
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 | Discover your Passion
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 | Show your Style
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 | Live your Dream
47 |
48 |
49 |
50 |
51 |
56 |
57 |
58 | Because time flies,
59 | But the memories last forever.
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | export default Coursel;
67 |
--------------------------------------------------------------------------------
/frontend/src/constants/cartConstants.js:
--------------------------------------------------------------------------------
1 | export const CART_ADD_ITEM = "CART_ADD_ITEM";
2 | export const CART_REMOVE_ITEM = "CART_REMOVE_ITEM";
3 |
4 | export const CART_SAVE_SHIPPING = "CART_SAVE_SHIPPING";
5 |
6 | export const CART_SAVE_PAYMENT = "CART_SAVE_PAYMENT";
7 |
--------------------------------------------------------------------------------
/frontend/src/constants/productConstants.js:
--------------------------------------------------------------------------------
1 | export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST";
2 | export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS";
3 | export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL";
4 |
5 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST";
6 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS";
7 | export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL";
8 |
9 | export const PRODUCT_SAVE_REQUEST = "PRODUCT_SAVE_REQUEST";
10 | export const PRODUCT_SAVE_SUCCESS = "PRODUCT_SAVE_SUCCESS";
11 | export const PRODUCT_SAVE_FAIL = "PRODUCT_SAVE_FAIL";
12 |
13 | export const PRODUCT_DELETE_REQUEST = "PRODUCT_DELETE_REQUEST";
14 | export const PRODUCT_DELETE_SUCCESS = "PRODUCT_DELETE_SUCCESS";
15 | export const PRODUCT_DELETE_FAIL = "PRODUCT_DELETE_FAIL";
16 |
--------------------------------------------------------------------------------
/frontend/src/constants/userConstants.js:
--------------------------------------------------------------------------------
1 | export const USER_SIGNIN_REQUEST = "USER_SIGNIN_REQUEST";
2 | export const USER_SIGNIN_SUCCESS = "USER_SIGNIN_SUCCESS";
3 | export const USER_SIGNIN_FAIL = "USER_SIGNIN_FAIL";
4 |
5 | export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST";
6 | export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS";
7 | export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL";
8 |
--------------------------------------------------------------------------------
/frontend/src/data.js:
--------------------------------------------------------------------------------
1 | // export default {
2 | // products: [
3 | // {
4 | // _id: "1",
5 | // name: "Nike Men's Regular fit T-Shirt",
6 | // category: "Shirt",
7 | // image: "/images/d1.jpg",
8 | // price: 35,
9 | // brand: " Nike",
10 | // rating: 3.0,
11 | // numReviews: 13
12 | // },
13 | // {
14 | // _id: "2",
15 | // name: "Nike Men's Track Pants",
16 | // category: "Pants",
17 | // image: "/images/d2.jpg",
18 | // price: 50,
19 | // brand: " Nike",
20 | // rating: 4.2,
21 | // numReviews: 66
22 | // },
23 | // {
24 | // _id: "3",
25 | // name: "Nike Men's Track Pants Gray",
26 | // category: "Pants",
27 | // image: "/images/d3.jpg",
28 | // price: 70,
29 | // brand: " Nike",
30 | // rating: 4,
31 | // numReviews: 8
32 | // },
33 | // {
34 | // _id: "4",
35 | // name: "Nike Girl's Plain Regular fit T-Shirt",
36 | // category: "Pants",
37 | // image: "/images/d4.jpg",
38 | // price: 40,
39 | // brand: " Nike",
40 | // rating: 3.5,
41 | // numReviews: 18
42 | // }
43 | // ]
44 | // };
45 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Pacifico&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Yeseva+One&display=swap");
3 | @import url("https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,900;1,400;1,500;1,900&family=Vollkorn+SC:wght@400;600;700;900&family=Vollkorn:ital,wght@0,500;1,400;1,500&display=swap");
4 |
5 | /* fonts */
6 | /* font-family: Pacifico;
7 | font-family: Yeseva One;
8 | font-family: Playfair;
9 | font-family: Vollkorn;
10 | font-family: Vollkorn VC; */
11 |
12 | html {
13 | font-size: 62.5%;
14 | box-sizing: border-box;
15 | }
16 | body {
17 | font: 1.6rem Helvetica;
18 | height: 100%;
19 | margin: 0%;
20 | }
21 | button {
22 | font-size: 1.6rem;
23 | }
24 | .full-width {
25 | width: 100%;
26 | }
27 | #root {
28 | height: 100%;
29 | }
30 | ul {
31 | padding: 0;
32 | }
33 | a,
34 | link {
35 | text-decoration: none !important;
36 | }
37 | .grid-container {
38 | display: grid;
39 | grid-template-areas:
40 | "header"
41 | "main"
42 | "footer";
43 | grid-template-columns: 1fr;
44 | grid-template-rows: 5rem 1fr 5rem;
45 | height: 100%;
46 | }
47 | .header {
48 | grid-area: header;
49 | background-color: rgb(255, 255, 255);
50 | color: rgb(0, 0, 0);
51 | display: flex;
52 | justify-content: space-between;
53 | align-items: center;
54 | padding: 0.5rem;
55 | position: -webkit-sticky; /* Safari */
56 | position: sticky;
57 | top: 0;
58 | z-index: 9;
59 | box-shadow: 5px 0.1px 10px rgb(212, 175, 55);
60 | }
61 | .logo {
62 | height: 50px;
63 | }
64 | .brand a {
65 | color: black;
66 | font-size: 2.5rem;
67 | font-weight: bold;
68 | text-decoration: none;
69 | font-family: "Pacifico", cursive;
70 | }
71 | .header-links span {
72 | margin-right: 2rem;
73 | }
74 | .header-links a span {
75 | font-size: 3.5rem;
76 | color: rgb(0, 0, 0);
77 | text-decoration: none;
78 | transition: 0.3s;
79 | margin: 0;
80 | padding: 0;
81 | }
82 | .header-links a span:hover {
83 | color: rgb(212, 175, 55);
84 | }
85 | .main {
86 | grid-area: main;
87 | }
88 | .footer {
89 | grid-area: footer;
90 | background-color: rgb(255, 255, 255);
91 | color: rgb(0, 0, 0);
92 | display: flex;
93 | justify-content: center;
94 | align-items: center;
95 | box-shadow: 5px 0.1px 10px rgb(212, 175, 55);
96 | }
97 | a {
98 | text-decoration: none;
99 | transition: 0.3s;
100 | font-size: 1.7rem;
101 | }
102 | a:hover {
103 | color: rgb(212, 175, 55);
104 | }
105 | /* Home Screen */
106 | .products {
107 | display: flex;
108 | justify-content: center;
109 | align-items: center;
110 | flex-wrap: wrap;
111 | z-index: 0;
112 | background-color: #f8f8f8;
113 | }
114 | .products li {
115 | list-style: none;
116 | padding: 0;
117 | flex: 0 1 30rem;
118 | margin: 1rem;
119 | height: 40rem;
120 | /* border-bottom: 0.1rem #c0c0c0 solid; */
121 | background-color: #ffffff;
122 | }
123 | .product {
124 | display: flex;
125 | flex-direction: column;
126 | justify-content: space-between;
127 | height: 100%;
128 | margin: 0.5rem;
129 | align-items: center;
130 | transition: 0.3s;
131 | }
132 | .product:hover {
133 | /* border: .1px solid rgb(200, 200, 200); */
134 | box-shadow: 1px 1px 10px rgb(212, 175, 55);
135 | }
136 | .product-name {
137 | font-size: 2rem;
138 | font-weight: bold;
139 | color: black;
140 | text-align: center;
141 | transition: 0.3s;
142 | }
143 | .product-name:hover {
144 | color: rgb(212, 175, 55);
145 | }
146 | .product-brand {
147 | font-size: 1.2rem;
148 | color: #808080;
149 | }
150 | .product-price {
151 | font-size: 2.5rem;
152 | font-weight: bold;
153 | }
154 | .product-image {
155 | max-width: 24rem;
156 | max-height: 24rem;
157 | transition: 0.5s;
158 | }
159 | .product-image:hover {
160 | max-width: 25rem;
161 | max-height: 25rem;
162 | }
163 | .product-rating {
164 | margin-bottom: 1rem;
165 | }
166 | /* SideBar */
167 | .brand button {
168 | font-size: 3rem;
169 | padding: 0.5rem;
170 | background: none;
171 | border: none;
172 | color: #000000;
173 | cursor: pointer;
174 | }
175 | .sidebar {
176 | position: fixed;
177 | transform: translate(-30rem);
178 | width: 30rem;
179 | background-color: rgba(0, 0, 0, 0.95);
180 | height: 100%;
181 | transition: all 0.5s;
182 | z-index: 10;
183 | }
184 | .open {
185 | transform: translate(0);
186 | }
187 | .sidebar-close-button {
188 | border: 0.1rem rgba(0, 0, 0, 0);
189 | background-color: rgba(255, 255, 255, 0);
190 | /* color: rgb(255, 254, 254); */
191 | width: 3rem;
192 | height: 3rem;
193 | padding: 0.5rem;
194 | font-size: 4.5rem;
195 | padding-top: 0;
196 | cursor: pointer;
197 | position: absolute;
198 | right: 2rem;
199 | top: 0.1rem;
200 | /* outline: none; */
201 | }
202 | .sidebar-close-button span {
203 | font-size: 3rem;
204 | color: rgb(255, 255, 255);
205 | }
206 | .sidebar-close-button span:hover {
207 | color: rgb(212, 175, 55);
208 | }
209 | .sidebar-close-button::focus {
210 | outline: none;
211 | }
212 |
213 | .sidebar h3 {
214 | color: white;
215 | padding: 1.1rem;
216 | font-size: 30px;
217 | justify-content: center;
218 | border-bottom: 0.1px solid rgba(255, 255, 255, 0.397);
219 | }
220 |
221 | .sidebar ul {
222 | list-style: none;
223 | margin: 1rem;
224 | padding: 1rem 0 0 1rem;
225 | }
226 | .sidebar ul li {
227 | list-style: none;
228 | margin: 1rem;
229 | padding-bottom: 0.1rem;
230 | color: white;
231 | }
232 | .sidebar ul li a {
233 | color: white;
234 | font-weight: 400;
235 | }
236 | .sidebar ul li a:hover {
237 | color: rgb(212, 175, 55);
238 | }
239 |
240 | /* Product details */
241 | /*
242 | font-family: Pacifico;
243 | font-family: Yeseva One;
244 | font-family: Playfair;
245 | font-family: Vollkorn;
246 | font-family: Vollkorn VC;
247 | */
248 |
249 | .details {
250 | display: flex;
251 | align-items: flex-start;
252 | flex-wrap: wrap;
253 | padding: 1rem;
254 | font-family: Vollkorn VC;
255 | font-size: 2rem;
256 | position: relative;
257 | min-height: 100vh;
258 | }
259 | .details-image {
260 | flex: 1 1 30rem;
261 | display: flex;
262 | justify-content: center;
263 | align-items: center;
264 | align-content: center;
265 | }
266 | .details-image img {
267 | max-width: 35rem;
268 | width: 80%;
269 | transition: 0.5s;
270 | }
271 | .details-image img:hover {
272 | max-width: 38rem;
273 | width: 82%;
274 | transition: 0.3s;
275 | }
276 |
277 | .details-info {
278 | flex: 1 1 30rem;
279 | height: 100%;
280 | padding-left: 10rem;
281 | padding-right: 10rem;
282 | }
283 | .details-info ul,
284 | .details-action ul {
285 | list-style-type: none;
286 | padding: none;
287 | }
288 | .details-info li,
289 | .details-action li {
290 | margin: 1rem;
291 | }
292 | .back-to-result a span {
293 | font-size: 3.5rem;
294 | padding: 0.5rem 0 0 1rem;
295 | color: black;
296 | transition: 0.3s;
297 | }
298 | .back-to-result a span:hover {
299 | color: rgb(212, 175, 55);
300 | }
301 |
302 | .details-info ul li h1 {
303 | font-family: Vollkorn VC;
304 | font-size: 3rem;
305 | }
306 | .details-info ul li:nth-child(4) {
307 | font-size: 2rem;
308 | margin-top: 5rem;
309 | }
310 | .details-info ul li:nth-child(5) {
311 | font-size: 1.5rem;
312 | color: rgb(97, 97, 97);
313 | margin-bottom: 4rem;
314 | }
315 | .details-info ul li:nth-child(7) {
316 | margin-top: 2rem;
317 | font-size: 1.4rem;
318 | color: rgb(97, 97, 97);
319 | }
320 | .details-info ul li:last-child {
321 | display: flex;
322 | flex-direction: column;
323 | }
324 | .button {
325 | padding: 1rem;
326 | border: 0.2rem #000000 solid;
327 | cursor: pointer;
328 | border-radius: 0.5rem;
329 | background-color: rgb(212, 175, 55);
330 | transition: 0.3s;
331 | }
332 | .button:hover {
333 | /* border: 0.2rem #000000 solid; */
334 | background-color: rgb(255, 255, 255);
335 | }
336 | .button:hover {
337 | border: 0.1rem #404040 solid;
338 | }
339 | .button.primary {
340 | background-color: #f0c040;
341 | }
342 | .button.secondary {
343 | background-color: #f0f0f0;
344 | }
345 | .text-center {
346 | text-align: center;
347 | }
348 |
349 | /* Cart Screen */
350 |
351 | .cart {
352 | display: flex;
353 | flex-wrap: wrap;
354 | margin: 1rem;
355 | align-items: flex-start;
356 | position: relative;
357 | min-height: 100vh;
358 | }
359 | .cart-list {
360 | flex: 3 1 60rem;
361 | }
362 | .cart-action {
363 | flex: 1 1 20rem;
364 | background-color: #f8f8f8;
365 | border-radius: 0.5rem;
366 | padding: 1rem;
367 | }
368 | .cart-list-container {
369 | padding: 0;
370 | list-style-type: none;
371 | padding: 1rem;
372 | }
373 |
374 | .cart-list-container li {
375 | display: flex;
376 | justify-content: space-between;
377 | align-items: flex-end;
378 | padding-bottom: 1rem;
379 | margin-bottom: 1rem;
380 | border-bottom: 0.1rem #808080 solid;
381 | }
382 | .cart-list-container li img {
383 | max-width: 10rem;
384 | max-height: 10rem;
385 | }
386 | .cart-list-container li:first-child {
387 | display: flex;
388 | justify-content: space-between;
389 | align-items: flex;
390 | }
391 |
392 | .cart-image {
393 | flex: 1 1;
394 | }
395 | .cart-name {
396 | flex: 8 1;
397 | }
398 | .cart-price {
399 | flex: 1 1;
400 | font-size: 2.5rem;
401 | text-align: right;
402 | }
403 | .cart-button{
404 | width: 10rem;
405 | margin: .1rem;
406 | border: 0.2rem #000000 solid;
407 | cursor: pointer;
408 | border-radius: 0.5rem;
409 | background-color: rgb(212, 175, 55);
410 | transition: 0.3s;
411 | float: right;
412 | margin-right: 5rem;
413 | }
414 | .cart-button:hover{
415 | border: 0.2rem #000000 solid;
416 | background-color: rgb(255, 255, 255);
417 | }
418 |
419 | /* Signin */
420 |
421 | .form {
422 | display: flex;
423 | justify-content: center;
424 | align-items: center;
425 | height: 100%;
426 | position: relative;
427 | min-height: 90vh;
428 | }
429 | .form-container {
430 | display: flex;
431 | flex-direction: column;
432 | width: 32rem;
433 | padding: 2rem;
434 | border: 0.2rem #f0f0f0 solid;
435 | border-radius: 0.5rem;
436 | list-style: none;
437 | }
438 |
439 | .form-container li {
440 | display: flex;
441 | flex-direction: column;
442 | margin-bottom: 1rem;
443 | margin-top: 1rem;
444 | }
445 | input {
446 | padding: 1rem;
447 | border: 0.1rem #c0c0c0 solid;
448 | border-radius: 0.5rem;
449 | }
450 |
451 | /* Products */
452 | .content{
453 | position: relative;
454 | min-height: 100vh;
455 | }
456 | .product-header {
457 | display: flex;
458 | justify-content: space-between;
459 | align-items: flex-start;
460 | }
461 | .content-margined {
462 | margin: 1rem;
463 | }
464 | .table {
465 | width: 100%;
466 | }
467 | th {
468 | text-align: left;
469 | }
470 | tbody > tr:nth-child(odd) {
471 | background-color: #f0f0f0;
472 | }
473 | .product-button{
474 | width: 5.6rem;
475 | margin: .1rem;
476 | border: 0.2rem #000000 solid;
477 | cursor: pointer;
478 | border-radius: 0.5rem;
479 | background-color: rgb(212, 175, 55);
480 | transition: 0.3s;
481 | }
482 | .product-button:hover{
483 | border: 0.2rem #000000 solid;
484 | background-color: rgb(255, 255, 255);
485 | }
486 |
487 |
488 | /* Checkout steps */
489 |
490 | .checkout-steps {
491 | display: flex;
492 | justify-content: space-around;
493 | width: 40rem;
494 | margin: 1rem auto;
495 | }
496 | .checkout-steps > div {
497 | border-top: 0.3rem #c0c0c0 solid;
498 | color: #c0c0c0;
499 | flex: 1 1;
500 | padding-top: 1rem;
501 | }
502 | .checkout-steps > div.active {
503 | border-top: 0.3rem #f08000 solid;
504 | color: #f08000;
505 | }
506 |
507 | /* place order */
508 |
509 | .placeorder {
510 | display: flex;
511 | flex-wrap: wrap;
512 | padding: 1rem;
513 | justify-content: space-between;
514 | }
515 |
516 | .placeorder-info {
517 | flex: 3 1 60rem;
518 | }
519 | .placeorder-action {
520 | flex: 3 1 20rem;
521 | border: 0.1rem #c0c0c0 solid;
522 | border-radius: 0.5rem;
523 | background-color: #fcfcfc;
524 | padding: 1rem;
525 | }
526 | .placeorder-info > div {
527 | border: 0.1rem #c0c0c0 solid;
528 | border-radius: 0.5rem;
529 | background-color: #fcfcfc;
530 | padding: 1rem;
531 | margin: 1rem;
532 | }
533 | .placeorder-info > div:first-child {
534 | margin-top: 0;
535 | }
536 | .placeorder-action > ul {
537 | padding: 0;
538 | list-style-type: none;
539 | }
540 | .placeorder-action > ul > li {
541 | display: flex;
542 | justify-content: space-between;
543 | margin-bottom: 1rem;
544 | }
545 | .placeorder-action > ul > li:last-child {
546 | font-size: 2rem;
547 | font-weight: bold;
548 | color: #f08000;
549 | border-top: 0.1px #c0c0c0 solid;
550 | padding-top: 0.5rem;
551 | }
552 |
553 | /* Courasel */
554 | .corousel {
555 | /* font-family: 'Yeseva One', cursive; */
556 | font-family: "Playfair Display", serif;
557 | /* font-style: italic; */
558 | font-weight: 400;
559 | }
560 | .corousel .item {
561 | height: 500px;
562 | }
563 | .corousel .item .corousel-img {
564 | position: absolute;
565 | top: -9999px;
566 | left: -9999px;
567 | right: -9999px;
568 | bottom: -9999px;
569 | margin: auto;
570 | }
571 | .corousel .item .caption h1 {
572 | font-size: 7.5rem;
573 | text-shadow: 1px 1px 2px black;
574 | }
575 | .corousel .item .caption p {
576 | padding: 0;
577 | margin: 0;
578 | font-size: 45px;
579 | text-shadow: 2px 2px 2px black;
580 | }
581 | span {
582 | color: rgb(212, 175, 55);
583 | }
584 | .caption img {
585 | height: 35rem;
586 | opacity: 0.8;
587 | padding: 0;
588 | margin: 0;
589 | transition: 0.5s;
590 | }
591 | .caption img:hover {
592 | /* height: 38rem; */
593 | opacity: 1;
594 | padding: 0;
595 | margin: 0;
596 | -webkit-filter: drop-shadow(5px 5px 5px #222);
597 | filter: drop-shadow(3px 3px 3px rgb(0, 0, 0));
598 | }
599 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Provider } from "react-redux";
3 | import ReactDOM from "react-dom";
4 | import "./index.css";
5 | import App from "./App";
6 | import * as serviceWorker from "./serviceWorker";
7 | import store from "./store";
8 | // import "../../node_modules/bootstrap/dist/css/bootstrap.css"
9 |
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById("root")
16 | );
17 |
18 | serviceWorker.unregister();
19 |
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/frontend/src/reducers/cartReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | CART_ADD_ITEM,
3 | CART_REMOVE_ITEM,
4 | CART_SAVE_SHIPPING,
5 | CART_SAVE_PAYMENT
6 | } from "../constants/cartConstants";
7 |
8 | function cartReducer(
9 | state = { cartItems: [], shipping: {}, payment: {} },
10 | action
11 | ) {
12 | switch (action.type) {
13 | case CART_ADD_ITEM:
14 | const item = action.payload;
15 | const product = state.cartItems.find(
16 | x => x.product === item.product
17 | );
18 | if (product) {
19 | return {
20 | cartItems: state.cartItems.map(x =>
21 | x.product === product.product ? item : x
22 | )
23 | };
24 | }
25 | return { cartItems: [...state.cartItems, item] };
26 | case CART_REMOVE_ITEM:
27 | return {
28 | cartItems: state.cartItems.filter(
29 | x => x.product !== action.payload
30 | )
31 | };
32 | case CART_SAVE_SHIPPING:
33 | return { ...state, shipping: action.payload };
34 | case CART_SAVE_PAYMENT:
35 | return { ...state, payment: action.payload };
36 | default:
37 | return state;
38 | }
39 | }
40 |
41 | export { cartReducer };
42 |
--------------------------------------------------------------------------------
/frontend/src/reducers/productReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | PRODUCT_LIST_REQUEST,
3 | PRODUCT_LIST_SUCCESS,
4 | PRODUCT_LIST_FAIL,
5 | PRODUCT_DETAILS_REQUEST,
6 | PRODUCT_DETAILS_SUCCESS,
7 | PRODUCT_DETAILS_FAIL,
8 | PRODUCT_SAVE_REQUEST,
9 | PRODUCT_SAVE_SUCCESS,
10 | PRODUCT_SAVE_FAIL,
11 | PRODUCT_DELETE_REQUEST,
12 | PRODUCT_DELETE_SUCCESS,
13 | PRODUCT_DELETE_FAIL
14 | } from "../constants/productConstants";
15 |
16 | function productListReducer(state = { products: [] }, action) {
17 | switch (action.type) {
18 | case PRODUCT_LIST_REQUEST:
19 | return { loading: true, products: [] };
20 | case PRODUCT_LIST_SUCCESS:
21 | return { loading: false, products: action.payload };
22 | case PRODUCT_LIST_FAIL:
23 | return { loading: false, error: action.payload };
24 | default:
25 | return state;
26 | }
27 | }
28 | function productSaveReducer(state = { product: { reviews: [] } }, action) {
29 | switch (action.type) {
30 | case PRODUCT_SAVE_REQUEST:
31 | return { loading: true };
32 | case PRODUCT_SAVE_SUCCESS:
33 | return { loading: false, success: true, product: action.payload };
34 | case PRODUCT_SAVE_FAIL:
35 | return { loading: false, error: action.payload };
36 | default:
37 | return state;
38 | }
39 | }
40 | function productDeleteReducer(state = { product: { reviews: [] } }, action) {
41 | switch (action.type) {
42 | case PRODUCT_DELETE_REQUEST:
43 | return { loading: true };
44 | case PRODUCT_DELETE_SUCCESS:
45 | return { loading: false, product: action.payload, success: true };
46 | case PRODUCT_DELETE_FAIL:
47 | return { loading: false, error: action.payload };
48 | default:
49 | return state;
50 | }
51 | }
52 | function productDetailsReducer(state = { product: { reviews: [] } }, action) {
53 | switch (action.type) {
54 | case PRODUCT_DETAILS_REQUEST:
55 | return { loading: true };
56 | case PRODUCT_DETAILS_SUCCESS:
57 | return { loading: false, product: action.payload };
58 | case PRODUCT_DETAILS_FAIL:
59 | return { loading: false, error: action.payload };
60 | default:
61 | return state;
62 | }
63 | }
64 | export {
65 | productListReducer,
66 | productDetailsReducer,
67 | productSaveReducer,
68 | productDeleteReducer
69 | };
70 |
--------------------------------------------------------------------------------
/frontend/src/reducers/userReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | USER_SIGNIN_REQUEST,
3 | USER_SIGNIN_SUCCESS,
4 | USER_SIGNIN_FAIL,
5 | USER_REGISTER_REQUEST,
6 | USER_REGISTER_SUCCESS,
7 | USER_REGISTER_FAIL
8 | } from "../constants/userConstants";
9 |
10 | function userSigninReducer(state = {}, action) {
11 | switch (action.type) {
12 | case USER_SIGNIN_REQUEST:
13 | return { loading: true };
14 | case USER_SIGNIN_SUCCESS:
15 | return { loading: false, userInfo: action.payload };
16 | case USER_SIGNIN_FAIL:
17 | return { loading: false, error: action.payload };
18 | default:
19 | return state;
20 | }
21 | }
22 | function userRegisterReducer(state = {}, action) {
23 | switch (action.type) {
24 | case USER_REGISTER_REQUEST:
25 | return { loading: true };
26 | case USER_REGISTER_SUCCESS:
27 | return { loading: false, userInfo: action.payload };
28 | case USER_REGISTER_FAIL:
29 | return { loading: false, error: action.payload };
30 | default:
31 | return state;
32 | }
33 | }
34 | export { userSigninReducer, userRegisterReducer };
35 |
--------------------------------------------------------------------------------
/frontend/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/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/extend-expect';
6 |
--------------------------------------------------------------------------------
/frontend/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, combineReducers, compose } from "redux";
2 | import {
3 | productListReducer,
4 | productDetailsReducer,
5 | productSaveReducer,
6 | productDeleteReducer
7 | } from "./reducers/productReducers";
8 | import { userSigninReducer, userRegisterReducer } from "./reducers/userReducer";
9 | import { cartReducer } from "./reducers/cartReducers";
10 | import thunk from "redux-thunk";
11 | import * as Cookie from "js-cookie";
12 |
13 | const cartItems = Cookie.getJSON("cartItems") || [];
14 | const userInfo = Cookie.getJSON("userInfo") || null;
15 |
16 | const initialState = { cart: { cartItems, shipping:{}, payment:{} }, userSignin: { userInfo } };
17 | const reducer = combineReducers({
18 | cart: cartReducer,
19 | userSignin: userSigninReducer,
20 | userRegister: userRegisterReducer,
21 | productList: productListReducer,
22 | productDetails: productDetailsReducer,
23 | productSave: productSaveReducer,
24 | productDelete: productDeleteReducer
25 | });
26 |
27 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
28 | const store = createStore(
29 | reducer,
30 | initialState,
31 | composeEnhancers(applyMiddleware(thunk))
32 | );
33 | export default store;
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "engines": {
7 | "node": "12.x",
8 | "npm": "6.x"
9 | },
10 | "scripts": {
11 | "start": "nodemon --watch backend --exec babel-node backend/server.js",
12 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install cd client && npm run build cd client"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS.git"
17 | },
18 | "author": "Suhas Salian",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/issues"
22 | },
23 | "homepage": "https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS#readme",
24 | "dependencies": {
25 | "@babel/cli": "^7.10.4",
26 | "@babel/core": "^7.10.4",
27 | "@babel/node": "^7.10.4",
28 | "@babel/preset-env": "^7.10.4",
29 | "body-parser": "^1.19.0",
30 | "bootstrap": "^4.5.0",
31 | "dotenv": "^8.2.0",
32 | "express": "^4.17.1",
33 | "jsonwebtoken": "^8.5.1",
34 | "mongodb": "^3.5.9",
35 | "mongoose": "^5.9.22",
36 | "nodemon": "^2.0.4",
37 | "react-bootstrap": "^1.2.2"
38 | }
39 | }
--------------------------------------------------------------------------------
/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
24 |
38 |
39 |
40 |
41 | -
42 |
43 |

48 |
53 |
Nike
54 |
$60
55 |
56 | 4.5 Stars (10 Reviews)
57 |
58 |
59 |
60 | -
61 |
62 |

67 |
72 |
Nike
73 |
$60
74 |
75 | 4.5 Stars (10 Reviews)
76 |
77 |
78 |
79 | -
80 |
81 |

86 |
91 |
Nike
92 |
$60
93 |
94 | 4.5 Stars (10 Reviews)
95 |
96 |
97 |
98 | -
99 |
100 |

105 |
110 |
Nike
111 |
$60
112 |
113 | 4.5 Stars (10 Reviews)
114 |
115 |
116 |
117 | -
118 |
119 |

124 |
129 |
Nike
130 |
$60
131 |
132 | 4.5 Stars (10 Reviews)
133 |
134 |
135 |
136 | -
137 |
138 |

143 |
148 |
Nike
149 |
$60
150 |
151 | 4.5 Stars (10 Reviews)
152 |
153 |
154 |
155 |
156 |
157 |
158 |
161 |
162 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/template/style.css:
--------------------------------------------------------------------------------
1 | html{
2 | font-size: 62.5%;
3 | box-sizing: border-box;
4 | }
5 | body{
6 | font: 1.6rem Helvetica;
7 | height: 100vh;
8 | margin: 0%;
9 | }
10 | .grid-container{
11 | display: grid;
12 | grid-template-areas:
13 | "header"
14 | "main"
15 | "footer";
16 | grid-template-columns: 1fr;
17 | grid-template-rows: 5rem 1fr 5rem;
18 | height: 100%;
19 | }
20 | .header{
21 | grid-area: header;
22 | background-color: rgb(29, 42, 54);
23 | color: white;
24 | display: flex;
25 | justify-content: space-between;
26 | align-items: center;
27 | padding: .5rem;
28 | }
29 | .brand a{
30 | color: white;
31 | font-size: 2.5rem;
32 | font-weight: bold;
33 | text-decoration: none;
34 | }
35 | .header-links a{
36 | color: white;
37 | text-decoration: none;
38 | }
39 | .header-links a:hover{
40 | color: rgb(255, 136, 0);
41 | }
42 | .main{
43 | grid-area: main;
44 | }
45 | .footer{
46 | grid-area: footer;
47 | background-color: rgb(29, 42, 54);
48 | color: white;
49 | display: flex;
50 | justify-content: center;
51 | align-items: center;
52 | }
53 | a{
54 | text-decoration: none;
55 | }
56 | a:hover{
57 | color: rgb(255, 136, 0);
58 | }
59 | /* Home Screen */
60 | .products{
61 | display: flex;
62 | justify-content: center;
63 | align-items: center;
64 | flex-wrap: wrap;
65 | }
66 | .products li{
67 | list-style: none;
68 | padding: 0;
69 | flex: 0 1 34rem;
70 | margin: 1 rem;
71 | height: 50rem;
72 | border-bottom: .1rem #c0c0c0 solid;
73 | }
74 | .product{
75 | display: flex;
76 | flex-direction: column;
77 | justify-content: space-between;
78 | height: 100%;
79 | margin: .5rem;
80 | }
81 | .product-name{
82 | font-size: 2rem;
83 | font-weight: bold;
84 |
85 | }
86 | .product-brand{
87 | font-size:1.2rem;
88 | color: #808080;
89 | }
90 | .product-price{
91 | font-size: 2.5rem;
92 | font-weight: bold;
93 | }
94 | .product-image{
95 | max-width: 34rem;
96 | max-height: 34rem;
97 | }
98 | .product-rating{
99 | margin-bottom: 1rem;
100 | }
101 | /* SideBar */
102 | .brand button{
103 | font-size: 3rem;
104 | padding: .5rem;
105 | background: none;
106 | border:none;
107 | color: #ffffff;
108 | cursor:pointer;
109 | }
110 | .sidebar{
111 | position: fixed;
112 | transform: translate(-30rem);
113 | width: 30rem;
114 | background-color: white;
115 | height: 100%;
116 | }
117 | .open{
118 | transform: translate(0);
119 | }
120 | .sidebar-close-button{
121 | border-radius: 50%;
122 | border: .1 rem black;
123 | width: 3rem;
124 | height: 3rem;
125 | padding: .5rem;
126 | font-size: 2rem;
127 | padding-top: 0;
128 | cursor: pointer;
129 | position: absolute;
130 | right: 1rem;
131 | top: 1.5rem;
132 | }
133 | .sidebar ul{
134 | list-style: none;
135 | }
--------------------------------------------------------------------------------