├── Procfile
├── frontend
├── public
│ ├── robots.txt
│ ├── Profile.png
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── images
│ │ ├── logo.png
│ │ ├── cover.jfif
│ │ ├── cursor.png
│ │ ├── Appstore.png
│ │ ├── Profile.png
│ │ └── playstore.png
│ ├── constants
│ │ ├── cartConstants.js
│ │ ├── orderConstants.js
│ │ ├── productConstants.js
│ │ └── userConstants.js
│ ├── component
│ │ ├── layout
│ │ │ ├── Header
│ │ │ │ ├── Header.css
│ │ │ │ ├── Header.js
│ │ │ │ └── UserOptions.js
│ │ │ ├── Loader
│ │ │ │ ├── Loader.js
│ │ │ │ └── Loader.css
│ │ │ ├── MetaData.js
│ │ │ ├── Contact
│ │ │ │ ├── Contact.js
│ │ │ │ └── Contact.css
│ │ │ ├── Not Found
│ │ │ │ ├── NotFound.js
│ │ │ │ └── NotFound.css
│ │ │ ├── Footer
│ │ │ │ ├── Footer.js
│ │ │ │ └── Footer.css
│ │ │ └── About
│ │ │ │ ├── About.js
│ │ │ │ └── aboutSection.css
│ │ ├── Cart
│ │ │ ├── CheckoutSteps.css
│ │ │ ├── OrderSuccess.js
│ │ │ ├── CartItemCard.js
│ │ │ ├── orderSuccess.css
│ │ │ ├── CartItemCard.css
│ │ │ ├── CheckoutSteps.js
│ │ │ ├── payment.css
│ │ │ ├── Shipping.css
│ │ │ ├── Cart.js
│ │ │ ├── Cart.css
│ │ │ ├── ConfirmOrder.js
│ │ │ ├── ConfirmOrder.css
│ │ │ ├── Payment.js
│ │ │ └── Shipping.js
│ │ ├── Product
│ │ │ ├── ReviewCard.js
│ │ │ ├── Search.js
│ │ │ ├── Search.css
│ │ │ ├── Products.css
│ │ │ ├── Products.js
│ │ │ └── ProductDetails.css
│ │ ├── Home
│ │ │ ├── ProductCard.js
│ │ │ ├── Home.js
│ │ │ └── Home.css
│ │ ├── Admin
│ │ │ ├── sidebar.css
│ │ │ ├── processOrder.css
│ │ │ ├── productList.css
│ │ │ ├── productReviews.css
│ │ │ ├── Sidebar.js
│ │ │ ├── dashboard.css
│ │ │ ├── Dashboard.js
│ │ │ ├── newProduct.css
│ │ │ ├── ProductList.js
│ │ │ ├── UsersList.js
│ │ │ ├── OrderList.js
│ │ │ ├── UpdateUser.js
│ │ │ ├── ProductReviews.js
│ │ │ └── NewProduct.js
│ │ ├── Route
│ │ │ └── ProtectedRoute.js
│ │ ├── Order
│ │ │ ├── myOrders.css
│ │ │ ├── orderDetails.css
│ │ │ ├── MyOrders.js
│ │ │ └── OrderDetails.js
│ │ └── User
│ │ │ ├── Profile.js
│ │ │ ├── ForgotPassword.js
│ │ │ ├── ResetPassword.css
│ │ │ ├── ForgotPassword.css
│ │ │ ├── UpdatePassword.css
│ │ │ ├── ResetPassword.js
│ │ │ ├── Profile.css
│ │ │ ├── UpdateProfile.css
│ │ │ ├── UpdatePassword.js
│ │ │ ├── UpdateProfile.js
│ │ │ └── LoginSignUp.css
│ ├── App.css
│ ├── index.js
│ ├── reducers
│ │ ├── cartReducer.js
│ │ └── orderReducer.js
│ ├── actions
│ │ ├── cartAction.js
│ │ └── orderAction.js
│ └── store.js
├── .gitignore
├── package.json
└── README.md
├── backend
├── middleware
│ ├── catchAsyncErrors.js
│ ├── auth.js
│ └── error.js
├── utils
│ ├── errorhander.js
│ ├── jwtToken.js
│ ├── sendEmail.js
│ └── apifeatures.js
├── config
│ └── database.js
├── routes
│ ├── paymentRoute.js
│ ├── orderRoute.js
│ ├── productRoute.js
│ └── userRoute.js
├── controllers
│ ├── paymentController.js
│ └── orderController.js
├── server.js
├── app.js
└── models
│ ├── productModel.js
│ ├── orderModel.js
│ └── userModel.js
├── .gitIgnore
├── README.md
└── package.json
/Procfile:
--------------------------------------------------------------------------------
1 | web: node backend/server.js
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/public/Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/public/Profile.png
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/logo.png
--------------------------------------------------------------------------------
/frontend/src/images/cover.jfif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/cover.jfif
--------------------------------------------------------------------------------
/frontend/src/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/cursor.png
--------------------------------------------------------------------------------
/frontend/src/images/Appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/Appstore.png
--------------------------------------------------------------------------------
/frontend/src/images/Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/Profile.png
--------------------------------------------------------------------------------
/frontend/src/images/playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nakshatra05/E-Commerce-App/HEAD/frontend/src/images/playstore.png
--------------------------------------------------------------------------------
/backend/middleware/catchAsyncErrors.js:
--------------------------------------------------------------------------------
1 | module.exports = (theFunc) => (req, res, next) => {
2 | Promise.resolve(theFunc(req, res, next)).catch(next);
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/src/constants/cartConstants.js:
--------------------------------------------------------------------------------
1 | export const ADD_TO_CART = "ADD_TO_CART";
2 |
3 | export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM";
4 |
5 | export const SAVE_SHIPPING_INFO = "SAVE_SHIPPING_INFO";
6 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Header/Header.css:
--------------------------------------------------------------------------------
1 | .speedDial {
2 | position: fixed;
3 | right: 3vmax;
4 | top: 3vmax;
5 | }
6 |
7 | .speedDialIcon {
8 | width: 56px;
9 | height: 56px;
10 | border-radius: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Loader/Loader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Loader.css";
3 |
4 | const Loader = () => {
5 | return (
6 |
9 | );
10 | };
11 |
12 | export default Loader;
13 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/MetaData.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Helmet from "react-helmet";
3 |
4 | const MetaData = ({ title }) => {
5 | return (
6 |
7 | {title}
8 |
9 | );
10 | };
11 |
12 | export default MetaData;
13 |
--------------------------------------------------------------------------------
/backend/utils/errorhander.js:
--------------------------------------------------------------------------------
1 | class ErrorHandler extends Error{
2 | constructor(message,statusCode){
3 | super(message);
4 | this.statusCode = statusCode
5 |
6 | Error.captureStackTrace(this,this.constructor);
7 |
8 | }
9 |
10 | }
11 |
12 | module.exports = ErrorHandler
13 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/CheckoutSteps.css:
--------------------------------------------------------------------------------
1 | .MuiStepConnector-line {
2 | display: none !important;
3 | }
4 |
5 | .MuiStepConnector-root {
6 | height: 1px;
7 | background-color: rgba(0, 0, 0, 0.349);
8 | }
9 |
10 | .MuiStepConnector-active,
11 | .MuiStepConnector-completed {
12 | background-color: tomato;
13 | }
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/backend/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const connectDatabase = () => {
4 | mongoose
5 | .connect(process.env.DB_URI, {
6 | useNewUrlParser: true,
7 | useUnifiedTopology: true,
8 | useCreateIndex: true,
9 | })
10 | .then((data) => {
11 | console.log(`Mongodb connected with server: ${data.connection.host}`);
12 | });
13 | };
14 |
15 | module.exports = connectDatabase;
16 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Contact/Contact.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Contact.css";
3 | import { Button } from "@material-ui/core";
4 |
5 | const Contact = () => {
6 | return (
7 |
12 | );
13 | };
14 |
15 | export default Contact;
16 |
--------------------------------------------------------------------------------
/.gitIgnore:
--------------------------------------------------------------------------------
1 |
2 | # dependencies
3 | /node_modules
4 | /frontend/node_modules
5 | /frontend/.pnp
6 | /frontend/.pnp.js
7 |
8 | # testing
9 | /frontend/coverage
10 |
11 | # production
12 | /frontend/build
13 |
14 | # misc
15 | /backend/config/config.env
16 | /frontend/.DS_Store
17 | /frontend/.env.local
18 | /frontend/.env.development.local
19 | /frontend/.env.test.local
20 | /frontend/.env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/backend/routes/paymentRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | processPayment,
4 | sendStripeApiKey,
5 | } = require("../controllers/paymentController");
6 | const router = express.Router();
7 | const { isAuthenticatedUser } = require("../middleware/auth");
8 |
9 | router.route("/payment/process").post(isAuthenticatedUser, processPayment);
10 |
11 | router.route("/stripeapikey").get(isAuthenticatedUser, sendStripeApiKey);
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Loader/Loader.css:
--------------------------------------------------------------------------------
1 | .loading {
2 | width: 100vw;
3 | height: 100vh;
4 | background-color: white;
5 | display: grid;
6 | place-items: center;
7 | max-width: 100%;
8 | }
9 |
10 | .loading > div {
11 | width: 10vmax;
12 | height: 10vmax;
13 | border-bottom: 5px solid rgba(0, 0, 0, 0.719);
14 |
15 | border-radius: 50%;
16 |
17 | animation: loadingRotate 800ms linear infinite;
18 | }
19 |
20 | @keyframes loadingRotate {
21 | to {
22 | transform: rotateZ(-360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Not Found/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ErrorIcon from "@material-ui/icons/Error";
3 | import "./NotFound.css";
4 | import { Typography } from "@material-ui/core";
5 | import { Link } from "react-router-dom";
6 |
7 | const NotFound = () => {
8 | return (
9 |
10 |
11 |
12 | Page Not Found
13 | Home
14 |
15 | );
16 | };
17 |
18 | export default NotFound;
19 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | scroll-behavior: smooth;
4 | }
5 |
6 | body {
7 | cursor: url("./images/cursor.png"), auto;
8 | }
9 |
10 | /* Chrome, Safari, Edge, Opera */
11 | input::-webkit-outer-spin-button,
12 | input::-webkit-inner-spin-button {
13 | -webkit-appearance: none;
14 | margin: 0;
15 | }
16 |
17 | /* Firefox */
18 | input[type="number"] {
19 | -moz-appearance: textfield;
20 | }
21 |
22 | .greenColor {
23 | color: green !important;
24 | }
25 | .redColor {
26 | color: red !important;
27 | }
28 |
--------------------------------------------------------------------------------
/backend/utils/jwtToken.js:
--------------------------------------------------------------------------------
1 | // Create Token and saving in cookie
2 |
3 | const sendToken = (user, statusCode, res) => {
4 | const token = user.getJWTToken();
5 |
6 | // options for cookie
7 | const options = {
8 | expires: new Date(
9 | Date.now() + process.env.COOKIE_EXPIRE * 24 * 60 * 60 * 1000
10 | ),
11 | httpOnly: true,
12 | };
13 |
14 | res.status(statusCode).cookie("token", token, options).json({
15 | success: true,
16 | user,
17 | token,
18 | });
19 | };
20 |
21 | module.exports = sendToken;
22 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/OrderSuccess.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CheckCircleIcon from "@material-ui/icons/CheckCircle";
3 | import "./orderSuccess.css";
4 | import { Typography } from "@material-ui/core";
5 | import { Link } from "react-router-dom";
6 |
7 | const OrderSuccess = () => {
8 | return (
9 |
10 |
11 |
12 | Your Order has been Placed successfully
13 | View Orders
14 |
15 | );
16 | };
17 |
18 | export default OrderSuccess;
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Used **Node**, **React**, **Express**, **MongoDB** and **RestAPI**
2 |
3 | # Install Dependencies
4 |
5 | **For Backend** - `npm i`
6 |
7 | **For Frontend** - `cd frontend` ` npm i`
8 |
9 | ## Env Variables
10 |
11 | **Essential Variables**
12 | PORT=
13 | DB_URI =
14 | STRIPE_API_KEY=
15 | STRIPE_SECRET_KEY=
16 | JWT_SECRET=
17 | JWT_EXPIRE=
18 | COOKIE_EXPIRE=
19 | SMPT_SERVICE =
20 | SMPT_MAIL=
21 | SMPT_PASSWORD=
22 | SMPT_HOST=
23 | SMPT_PORT=
24 | CLOUDINARY_NAME
25 | CLOUDINARY_API_KEY
26 | CLOUDINARY_API_SECRET
27 |
28 | Made with help of Youtube Tutorials of Abhi Singh
29 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/CartItemCard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./CartItemCard.css";
3 | import { Link } from "react-router-dom";
4 |
5 | const CartItemCard = ({ item, deleteCartItems }) => {
6 | return (
7 |
8 |
9 |
10 |
{item.name}
11 |
{`Price: ₹${item.price}`}
12 |
deleteCartItems(item.product)}>Remove
13 |
14 |
15 | );
16 | };
17 |
18 | export default CartItemCard;
19 |
--------------------------------------------------------------------------------
/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/src/component/Product/ReviewCard.js:
--------------------------------------------------------------------------------
1 | import { Rating } from "@material-ui/lab";
2 | import React from "react";
3 | import profilePng from "../../images/Profile.png";
4 |
5 | const ReviewCard = ({ review }) => {
6 | const options = {
7 | value: review.rating,
8 | readOnly: true,
9 | precision: 0.5,
10 | };
11 |
12 | return (
13 |
14 |
15 |
{review.name}
16 |
17 |
{review.comment}
18 |
19 | );
20 | };
21 |
22 | export default ReviewCard;
23 |
--------------------------------------------------------------------------------
/backend/utils/sendEmail.js:
--------------------------------------------------------------------------------
1 | const nodeMailer = require("nodemailer");
2 |
3 | const sendEmail = async (options) => {
4 | const transporter = nodeMailer.createTransport({
5 | host: process.env.SMPT_HOST,
6 | port: process.env.SMPT_PORT,
7 | service: process.env.SMPT_SERVICE,
8 | auth: {
9 | user: process.env.SMPT_MAIL,
10 | pass: process.env.SMPT_PASSWORD,
11 | },
12 | });
13 |
14 | const mailOptions = {
15 | from: process.env.SMPT_MAIL,
16 | to: options.email,
17 | subject: options.subject,
18 | text: options.message,
19 | };
20 |
21 | await transporter.sendMail(mailOptions);
22 | };
23 |
24 | module.exports = sendEmail;
25 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Contact/Contact.css:
--------------------------------------------------------------------------------
1 | .contactContainer {
2 | height: 100vh;
3 | width: 100vw;
4 | max-width: 100%;
5 | display: grid;
6 | place-items: center;
7 | background-color: white;
8 | position: fixed;
9 | }
10 |
11 | .mailBtn {
12 | text-decoration: none;
13 | transform: translateX(-100vw);
14 | animation: mailBtnAnimation 2s forwards;
15 | }
16 |
17 | .mailBtn > button {
18 | text-decoration: none;
19 | font: 200 2vmax "Roboto";
20 | cursor: url("https://img.icons8.com/color/48/000000/edit--v2.png"), pointer;
21 | padding: 2vmax;
22 | }
23 |
24 | @keyframes mailBtnAnimation {
25 | to {
26 | transform: translateX(0);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import { Provider } from "react-redux";
5 | import store from "./store";
6 |
7 | import { positions, transitions, Provider as AlertProvider } from "react-alert";
8 | import AlertTemplate from "react-alert-template-basic";
9 |
10 | const options = {
11 | timeout: 5000,
12 | position: positions.BOTTOM_CENTER,
13 | transition: transitions.SCALE,
14 | };
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 |
21 | ,
22 | document.getElementById("root")
23 | );
24 |
--------------------------------------------------------------------------------
/backend/controllers/paymentController.js:
--------------------------------------------------------------------------------
1 | const catchAsyncErrors = require("../middleware/catchAsyncErrors");
2 |
3 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
4 |
5 | exports.processPayment = catchAsyncErrors(async (req, res, next) => {
6 | const myPayment = await stripe.paymentIntents.create({
7 | amount: req.body.amount,
8 | currency: "inr",
9 | metadata: {
10 | company: "Ecommerce",
11 | },
12 | });
13 |
14 | res
15 | .status(200)
16 | .json({ success: true, client_secret: myPayment.client_secret });
17 | });
18 |
19 | exports.sendStripeApiKey = catchAsyncErrors(async (req, res, next) => {
20 | res.status(200).json({ stripeApiKey: process.env.STRIPE_API_KEY });
21 | });
22 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 | ECOMMERCE
17 |
18 |
19 | You need to enable JavaScript to run this app.
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/frontend/src/component/Home/ProductCard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import { Rating } from "@material-ui/lab";
4 |
5 | const ProductCard = ({ product }) => {
6 | const options = {
7 | value: product.ratings,
8 | readOnly: true,
9 | precision: 0.5,
10 | };
11 | return (
12 |
13 |
14 | {product.name}
15 |
16 | {" "}
17 |
18 | {" "}
19 | ({product.numOfReviews} Reviews)
20 |
21 |
22 | {`₹${product.price}`}
23 |
24 | );
25 | };
26 |
27 | export default ProductCard;
28 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/sidebar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | background-color: rgb(255, 255, 255);
3 | display: flex;
4 | flex-direction: column;
5 | padding: 4rem 0;
6 | }
7 |
8 | .sidebar > a:first-child {
9 | padding: 0;
10 | }
11 | .sidebar > a > img {
12 | width: 100%;
13 | transition: all 0.5s;
14 | }
15 |
16 | .sidebar > a > img:hover {
17 | filter: drop-shadow(0 0 10px tomato);
18 | }
19 | .sidebar a {
20 | text-decoration: none;
21 | color: rgba(0, 0, 0, 0.493);
22 | font: 200 1rem "Roboto";
23 | padding: 2rem;
24 | transition: all 0.5s;
25 | }
26 | .sidebar a:hover {
27 | color: tomato;
28 | transform: scale(1.1);
29 | }
30 |
31 | .sidebar a > P {
32 | display: flex;
33 | align-items: center;
34 | }
35 | .sidebar a > p > svg {
36 | margin-right: 0.5rem;
37 | }
38 |
39 | .MuiTypography-root {
40 | background-color: #fff !important;
41 | }
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "backend/server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node backend/server.js",
9 | "dev": "nodemon backend/server.js",
10 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false && npm install --prefix frontend && npm run build --prefix frontend"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcryptjs": "^2.4.3",
16 | "body-parser": "^1.19.0",
17 | "cloudinary": "^1.26.3",
18 | "cookie-parser": "^1.4.5",
19 | "dotenv": "^10.0.0",
20 | "express": "^4.17.1",
21 | "express-fileupload": "^1.2.1",
22 | "jsonwebtoken": "^8.5.1",
23 | "mongoose": "^5.13.5",
24 | "nodemailer": "^6.6.3",
25 | "stripe": "^8.174.0",
26 | "validator": "^13.6.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/routes/orderRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | newOrder,
4 | getSingleOrder,
5 | myOrders,
6 | getAllOrders,
7 | updateOrder,
8 | deleteOrder,
9 | } = require("../controllers/orderController");
10 | const router = express.Router();
11 |
12 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
13 |
14 | router.route("/order/new").post(isAuthenticatedUser, newOrder);
15 |
16 | router.route("/order/:id").get(isAuthenticatedUser, getSingleOrder);
17 |
18 | router.route("/orders/me").get(isAuthenticatedUser, myOrders);
19 |
20 | router
21 | .route("/admin/orders")
22 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAllOrders);
23 |
24 | router
25 | .route("/admin/order/:id")
26 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateOrder)
27 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteOrder);
28 |
29 | module.exports = router;
30 |
--------------------------------------------------------------------------------
/frontend/src/component/Route/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { useSelector } from "react-redux";
3 | import { Redirect, Route } from "react-router-dom";
4 |
5 | const ProtectedRoute = ({ isAdmin, component: Component, ...rest }) => {
6 | const { loading, isAuthenticated, user } = useSelector((state) => state.user);
7 |
8 | return (
9 |
10 | {loading === false && (
11 | {
14 | if (isAuthenticated === false) {
15 | return ;
16 | }
17 |
18 | if (isAdmin === true && user.role !== "admin") {
19 | return ;
20 | }
21 |
22 | return ;
23 | }}
24 | />
25 | )}
26 |
27 | );
28 | };
29 |
30 | export default ProtectedRoute;
31 |
--------------------------------------------------------------------------------
/frontend/src/component/Product/Search.js:
--------------------------------------------------------------------------------
1 | import React, { useState, Fragment } from "react";
2 | import MetaData from "../layout/MetaData";
3 | import "./Search.css";
4 |
5 | const Search = ({ history }) => {
6 | const [keyword, setKeyword] = useState("");
7 |
8 | const searchSubmitHandler = (e) => {
9 | e.preventDefault();
10 | if (keyword.trim()) {
11 | history.push(`/products/${keyword}`);
12 | } else {
13 | history.push("/products");
14 | }
15 | };
16 |
17 | return (
18 |
19 |
20 |
28 |
29 | );
30 | };
31 |
32 | export default Search;
33 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/orderSuccess.css:
--------------------------------------------------------------------------------
1 | .orderSuccess {
2 | margin: auto;
3 | text-align: center;
4 | padding: 10vmax;
5 | height: 50vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | .orderSuccess > svg {
12 | font-size: 7vmax;
13 | color: tomato;
14 | }
15 | .orderSuccess > p {
16 | font-size: 2vmax;
17 | }
18 | .orderSuccess > a {
19 | background-color: rgb(51, 51, 51);
20 | color: white;
21 | border: none;
22 | padding: 1vmax 3vmax;
23 | cursor: pointer;
24 | font: 400 1vmax "Roboto";
25 | text-decoration: none;
26 | margin: 2vmax;
27 | }
28 |
29 | @media screen and (max-width: 600px) {
30 | .orderSuccess > a {
31 | padding: 3vw 6vw;
32 | font: 400 4vw "Roboto";
33 | margin: 2vmax;
34 | }
35 |
36 | .orderSuccess > svg {
37 | font-size: 20vw;
38 | }
39 | .orderSuccess > p {
40 | margin: 2vmax;
41 | font-size: 5vw;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Not Found/NotFound.css:
--------------------------------------------------------------------------------
1 | .PageNotFound {
2 | margin: auto;
3 | text-align: center;
4 | padding: 10vmax;
5 | height: 50vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | .PageNotFound > svg {
12 | font-size: 7vmax;
13 | color: tomato;
14 | }
15 | .PageNotFound > p {
16 | font-size: 2vmax;
17 | }
18 | .PageNotFound > a {
19 | background-color: rgb(51, 51, 51);
20 | color: white;
21 | border: none;
22 | padding: 1vmax 3vmax;
23 | cursor: pointer;
24 | font: 400 1vmax "Roboto";
25 | text-decoration: none;
26 | margin: 2vmax;
27 | }
28 |
29 | @media screen and (max-width: 600px) {
30 | .PageNotFound > a {
31 | padding: 3vw 6vw;
32 | font: 400 4vw "Roboto";
33 | margin: 2vmax;
34 | }
35 |
36 | .PageNotFound > svg {
37 | font-size: 20vw;
38 | }
39 | .PageNotFound > p {
40 | margin: 2vmax;
41 | font-size: 5vw;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/backend/middleware/auth.js:
--------------------------------------------------------------------------------
1 | const ErrorHander = require("../utils/errorhander");
2 | const catchAsyncErrors = require("./catchAsyncErrors");
3 | const jwt = require("jsonwebtoken");
4 | const User = require("../models/userModel");
5 |
6 | exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
7 | const { token } = req.cookies;
8 |
9 | if (!token) {
10 | return next(new ErrorHander("Please Login to access this resource", 401));
11 | }
12 |
13 | const decodedData = jwt.verify(token, process.env.JWT_SECRET);
14 |
15 | req.user = await User.findById(decodedData.id);
16 |
17 | next();
18 | });
19 |
20 | exports.authorizeRoles = (...roles) => {
21 | return (req, res, next) => {
22 | if (!roles.includes(req.user.role)) {
23 | return next(
24 | new ErrorHander(
25 | `Role: ${req.user.role} is not allowed to access this resouce `,
26 | 403
27 | )
28 | );
29 | }
30 |
31 | next();
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/processOrder.css:
--------------------------------------------------------------------------------
1 | .updateOrderForm {
2 | margin: 5vmax 0;
3 | padding: 3vmax;
4 | background-color: white;
5 | }
6 |
7 | .updateOrderForm > div {
8 | display: flex;
9 | width: 100%;
10 | align-items: center;
11 | }
12 | .updateOrderForm > div > select {
13 | padding: 1vmax 4vmax;
14 | margin: 2rem 0;
15 | width: 100%;
16 | box-sizing: border-box;
17 | border: 1px solid rgba(0, 0, 0, 0.267);
18 | border-radius: 4px;
19 | font: 300 0.9vmax cursive;
20 | outline: none;
21 | }
22 |
23 | .updateOrderForm > div > svg {
24 | position: absolute;
25 | transform: translateX(1vmax);
26 | font-size: 1.6vmax;
27 | color: rgba(0, 0, 0, 0.623);
28 | }
29 |
30 | @media screen and (max-width: 600px) {
31 | .updateOrderForm {
32 | padding: 5vmax;
33 | }
34 |
35 | .updateOrderForm > div > select {
36 | padding: 2.5vmax 5vmax;
37 | font: 300 1.7vmax cursive;
38 | }
39 |
40 | .updateOrderForm > div > svg {
41 | font-size: 2.8vmax;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import playStore from "../../../images/playstore.png";
3 | import appStore from "../../../images/Appstore.png";
4 | import "./Footer.css";
5 |
6 | const Footer = () => {
7 | return (
8 |
30 | );
31 | };
32 |
33 | export default Footer;
34 |
--------------------------------------------------------------------------------
/backend/middleware/error.js:
--------------------------------------------------------------------------------
1 | const ErrorHandler = require("../utils/errorhander");
2 |
3 | module.exports = (err, req, res, next) => {
4 | err.statusCode = err.statusCode || 500;
5 | err.message = err.message || "Internal Server Error";
6 |
7 | // Wrong Mongodb Id error
8 | if (err.name === "CastError") {
9 | const message = `Resource not found. Invalid: ${err.path}`;
10 | err = new ErrorHandler(message, 400);
11 | }
12 |
13 | // Mongoose duplicate key error
14 | if (err.code === 11000) {
15 | const message = `Duplicate ${Object.keys(err.keyValue)} Entered`;
16 | err = new ErrorHandler(message, 400);
17 | }
18 |
19 | // Wrong JWT error
20 | if (err.name === "JsonWebTokenError") {
21 | const message = `Json Web Token is invalid, Try again `;
22 | err = new ErrorHandler(message, 400);
23 | }
24 |
25 | // JWT EXPIRE error
26 | if (err.name === "TokenExpiredError") {
27 | const message = `Json Web Token is Expired, Try again `;
28 | err = new ErrorHandler(message, 400);
29 | }
30 |
31 | res.status(err.statusCode).json({
32 | success: false,
33 | message: err.message,
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const app = require("./app");
2 | const cloudinary = require("cloudinary");
3 | const connectDatabase = require("./config/database");
4 |
5 | // Handling Uncaught Exception
6 | process.on("uncaughtException", (err) => {
7 | console.log(`Error: ${err.message}`);
8 | console.log(`Shutting down the server due to Uncaught Exception`);
9 | process.exit(1);
10 | });
11 |
12 | // Config
13 | if (process.env.NODE_ENV !== "PRODUCTION") {
14 | require("dotenv").config({ path: "backend/config/config.env" });
15 | }
16 |
17 | // Connecting to database
18 | connectDatabase();
19 |
20 | cloudinary.config({
21 | cloud_name: process.env.CLOUDINARY_NAME,
22 | api_key: process.env.CLOUDINARY_API_KEY,
23 | api_secret: process.env.CLOUDINARY_API_SECRET,
24 | });
25 |
26 | const server = app.listen(process.env.PORT, () => {
27 | console.log(`Server is working on http://localhost:${process.env.PORT}`);
28 | });
29 |
30 | // Unhandled Promise Rejection
31 | process.on("unhandledRejection", (err) => {
32 | console.log(`Error: ${err.message}`);
33 | console.log(`Shutting down the server due to Unhandled Promise Rejection`);
34 |
35 | server.close(() => {
36 | process.exit(1);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/frontend/src/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TO_CART,
3 | REMOVE_CART_ITEM,
4 | SAVE_SHIPPING_INFO,
5 | } from "../constants/cartConstants";
6 |
7 | export const cartReducer = (
8 | state = { cartItems: [], shippingInfo: {} },
9 | action
10 | ) => {
11 | switch (action.type) {
12 | case ADD_TO_CART:
13 | const item = action.payload;
14 |
15 | const isItemExist = state.cartItems.find(
16 | (i) => i.product === item.product
17 | );
18 |
19 | if (isItemExist) {
20 | return {
21 | ...state,
22 | cartItems: state.cartItems.map((i) =>
23 | i.product === isItemExist.product ? item : i
24 | ),
25 | };
26 | } else {
27 | return {
28 | ...state,
29 | cartItems: [...state.cartItems, item],
30 | };
31 | }
32 |
33 | case REMOVE_CART_ITEM:
34 | return {
35 | ...state,
36 | cartItems: state.cartItems.filter((i) => i.product !== action.payload),
37 | };
38 |
39 | case SAVE_SHIPPING_INFO:
40 | return {
41 | ...state,
42 | shippingInfo: action.payload,
43 | };
44 |
45 | default:
46 | return state;
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/productList.css:
--------------------------------------------------------------------------------
1 | .productListContainer {
2 | width: 100%;
3 | box-sizing: border-box;
4 | background-color: rgb(255, 255, 255);
5 | border-left: 1px solid rgba(0, 0, 0, 0.158);
6 | display: flex;
7 | flex-direction: column;
8 | height: 100vh;
9 | }
10 |
11 | #productListHeading {
12 | font: 400 2rem "Roboto";
13 | padding: 0.5vmax;
14 | box-sizing: border-box;
15 | color: rgba(0, 0, 0, 0.637);
16 | transition: all 0.5s;
17 | margin: 2rem;
18 | text-align: center;
19 | }
20 |
21 | .productListTable {
22 | background-color: white;
23 | border: none !important;
24 | }
25 |
26 | .productListTable div {
27 | font: 300 1vmax "Roboto";
28 | color: rgba(0, 0, 0, 0.678);
29 | border: none !important;
30 | }
31 |
32 | .productListTable a,
33 | .productListTable button {
34 | color: rgba(0, 0, 0, 0.527);
35 | transition: all 0.5s;
36 | }
37 |
38 | .productListTable a:hover {
39 | color: tomato;
40 | }
41 |
42 | .productListTable button:hover {
43 | color: rgb(236, 30, 30);
44 | }
45 |
46 | .MuiDataGrid-columnHeader div {
47 | color: rgb(255, 255, 255);
48 | }
49 |
50 | @media screen and (max-width: 600px) {
51 | .productListTable div {
52 | font: 300 4vw "Roboto";
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/CartItemCard.css:
--------------------------------------------------------------------------------
1 | .CartItemCard {
2 | display: flex;
3 | padding: 1vmax;
4 | height: 8vmax;
5 | align-items: flex-start;
6 | box-sizing: border-box;
7 | }
8 | .CartItemCard > img {
9 | width: 5vmax;
10 | }
11 |
12 | .CartItemCard > div {
13 | display: flex;
14 | margin: 0.3vmax 1vmax;
15 | flex-direction: column;
16 | }
17 |
18 | .CartItemCard > div > a {
19 | font: 300 0.9vmax cursive;
20 | color: rgba(24, 24, 24, 0.815);
21 | text-decoration: none;
22 | }
23 |
24 | .CartItemCard > div > span {
25 | font: 300 0.9vmax "Roboto";
26 | color: rgba(24, 24, 24, 0.815);
27 | }
28 |
29 | .CartItemCard > div > p {
30 | color: tomato;
31 | font: 100 0.8vmax "Roboto";
32 | cursor: pointer;
33 | }
34 |
35 | @media screen and (max-width: 600px) {
36 | .CartItemCard {
37 | padding: 3vmax;
38 | height: 25vmax;
39 | }
40 | .CartItemCard > img {
41 | width: 10vmax;
42 | }
43 |
44 | .CartItemCard > div {
45 | margin: 1vmax 2vmax;
46 | }
47 |
48 | .CartItemCard > div > a {
49 | font: 300 2vmax cursive;
50 | }
51 |
52 | .CartItemCard > div > span {
53 | font: 300 1.9vmax "Roboto";
54 | }
55 |
56 | .CartItemCard > div > p {
57 | font: 100 1.8vmax "Roboto";
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/backend/routes/productRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | getAllProducts,
4 | createProduct,
5 | updateProduct,
6 | deleteProduct,
7 | getProductDetails,
8 | createProductReview,
9 | getProductReviews,
10 | deleteReview,
11 | getAdminProducts,
12 | } = require("../controllers/productController");
13 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
14 |
15 | const router = express.Router();
16 |
17 | router.route("/products").get(getAllProducts);
18 |
19 | router
20 | .route("/admin/products")
21 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAdminProducts);
22 |
23 | router
24 | .route("/admin/product/new")
25 | .post(isAuthenticatedUser, authorizeRoles("admin"), createProduct);
26 |
27 | router
28 | .route("/admin/product/:id")
29 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateProduct)
30 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProduct);
31 |
32 | router.route("/product/:id").get(getProductDetails);
33 |
34 | router.route("/review").put(isAuthenticatedUser, createProductReview);
35 |
36 | router
37 | .route("/reviews")
38 | .get(getProductReviews)
39 | .delete(isAuthenticatedUser, deleteReview);
40 |
41 | module.exports = router;
42 |
--------------------------------------------------------------------------------
/backend/app.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 | const cookieParser = require("cookie-parser");
4 | const bodyParser = require("body-parser");
5 | const fileUpload = require("express-fileupload");
6 | const path = require("path");
7 |
8 | const errorMiddleware = require("./middleware/error");
9 |
10 | // Config
11 | if (process.env.NODE_ENV !== "PRODUCTION") {
12 | require("dotenv").config({ path: "backend/config/config.env" });
13 | }
14 |
15 | app.use(express.json());
16 | app.use(cookieParser());
17 | app.use(bodyParser.urlencoded({ extended: true }));
18 | app.use(fileUpload());
19 |
20 | // Route Imports
21 | const product = require("./routes/productRoute");
22 | const user = require("./routes/userRoute");
23 | const order = require("./routes/orderRoute");
24 | const payment = require("./routes/paymentRoute");
25 |
26 | app.use("/api/v1", product);
27 | app.use("/api/v1", user);
28 | app.use("/api/v1", order);
29 | app.use("/api/v1", payment);
30 |
31 | app.use(express.static(path.join(__dirname, "../frontend/build")));
32 |
33 | app.get("*", (req, res) => {
34 | res.sendFile(path.resolve(__dirname, "../frontend/build/index.html"));
35 | });
36 |
37 | // Middleware for Errors
38 | app.use(errorMiddleware);
39 |
40 | module.exports = app;
41 |
--------------------------------------------------------------------------------
/frontend/src/actions/cartAction.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TO_CART,
3 | REMOVE_CART_ITEM,
4 | SAVE_SHIPPING_INFO,
5 | } from "../constants/cartConstants";
6 | import axios from "axios";
7 |
8 | // Add to Cart
9 | export const addItemsToCart = (id, quantity) => async (dispatch, getState) => {
10 | const { data } = await axios.get(`/api/v1/product/${id}`);
11 |
12 | dispatch({
13 | type: ADD_TO_CART,
14 | payload: {
15 | product: data.product._id,
16 | name: data.product.name,
17 | price: data.product.price,
18 | image: data.product.images[0].url,
19 | stock: data.product.Stock,
20 | quantity,
21 | },
22 | });
23 |
24 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
25 | };
26 |
27 | // REMOVE FROM CART
28 | export const removeItemsFromCart = (id) => async (dispatch, getState) => {
29 | dispatch({
30 | type: REMOVE_CART_ITEM,
31 | payload: id,
32 | });
33 |
34 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
35 | };
36 |
37 | // SAVE SHIPPING INFO
38 | export const saveShippingInfo = (data) => async (dispatch) => {
39 | dispatch({
40 | type: SAVE_SHIPPING_INFO,
41 | payload: data,
42 | });
43 |
44 | localStorage.setItem("shippingInfo", JSON.stringify(data));
45 | };
46 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ReactNavbar } from "overlay-navbar";
3 | import logo from "../../../images/logo.png";
4 |
5 | const options = {
6 | burgerColorHover: "#eb4034",
7 | logo,
8 | logoWidth: "20vmax",
9 | navColor1: "white",
10 | logoHoverSize: "10px",
11 | logoHoverColor: "#eb4034",
12 | link1Text: "Home",
13 | link2Text: "Products",
14 | link3Text: "Contact",
15 | link4Text: "About",
16 | link1Url: "/",
17 | link2Url: "/products",
18 | link3Url: "/contact",
19 | link4Url: "/about",
20 | link1Size: "1.3vmax",
21 | link1Color: "rgba(35, 35, 35,0.8)",
22 | nav1justifyContent: "flex-end",
23 | nav2justifyContent: "flex-end",
24 | nav3justifyContent: "flex-start",
25 | nav4justifyContent: "flex-start",
26 | link1ColorHover: "#eb4034",
27 | link1Margin: "1vmax",
28 | profileIconUrl: "/login",
29 | profileIconColor: "rgba(35, 35, 35,0.8)",
30 | searchIconColor: "rgba(35, 35, 35,0.8)",
31 | cartIconColor: "rgba(35, 35, 35,0.8)",
32 | profileIconColorHover: "#eb4034",
33 | searchIconColorHover: "#eb4034",
34 | cartIconColorHover: "#eb4034",
35 | cartIconMargin: "1vmax",
36 | };
37 |
38 | const Header = () => {
39 | return ;
40 | };
41 |
42 | export default Header;
43 |
--------------------------------------------------------------------------------
/backend/utils/apifeatures.js:
--------------------------------------------------------------------------------
1 | class ApiFeatures {
2 | constructor(query, queryStr) {
3 | this.query = query;
4 | this.queryStr = queryStr;
5 | }
6 |
7 | search() {
8 | const keyword = this.queryStr.keyword
9 | ? {
10 | name: {
11 | $regex: this.queryStr.keyword,
12 | $options: "i",
13 | },
14 | }
15 | : {};
16 |
17 | this.query = this.query.find({ ...keyword });
18 | return this;
19 | }
20 |
21 | filter() {
22 | const queryCopy = { ...this.queryStr };
23 | // Removing some fields for category
24 | const removeFields = ["keyword", "page", "limit"];
25 |
26 | removeFields.forEach((key) => delete queryCopy[key]);
27 |
28 | // Filter For Price and Rating
29 |
30 | let queryStr = JSON.stringify(queryCopy);
31 | queryStr = queryStr.replace(/\b(gt|gte|lt|lte)\b/g, (key) => `$${key}`);
32 |
33 | this.query = this.query.find(JSON.parse(queryStr));
34 |
35 | return this;
36 | }
37 |
38 | pagination(resultPerPage) {
39 | const currentPage = Number(this.queryStr.page) || 1;
40 |
41 | const skip = resultPerPage * (currentPage - 1);
42 |
43 | this.query = this.query.limit(resultPerPage).skip(skip);
44 |
45 | return this;
46 | }
47 | }
48 |
49 | module.exports = ApiFeatures;
50 |
--------------------------------------------------------------------------------
/frontend/src/constants/orderConstants.js:
--------------------------------------------------------------------------------
1 | export const CREATE_ORDER_REQUEST = "CREATE_ORDER_REQUEST";
2 | export const CREATE_ORDER_SUCCESS = "CREATE_ORDER_SUCCESS";
3 | export const CREATE_ORDER_FAIL = "CREATE_ORDER_FAIL";
4 |
5 | export const MY_ORDERS_REQUEST = "MY_ORDERS_REQUEST";
6 | export const MY_ORDERS_SUCCESS = "MY_ORDERS_SUCCESS";
7 | export const MY_ORDERS_FAIL = "MY_ORDERS_FAIL";
8 |
9 | export const ALL_ORDERS_REQUEST = "ALL_ORDERS_REQUEST";
10 | export const ALL_ORDERS_SUCCESS = "ALL_ORDERS_SUCCESS";
11 | export const ALL_ORDERS_FAIL = "ALL_ORDERS_FAIL";
12 |
13 | export const UPDATE_ORDER_REQUEST = "UPDATE_ORDER_REQUEST";
14 | export const UPDATE_ORDER_SUCCESS = "UPDATE_ORDER_SUCCESS";
15 | export const UPDATE_ORDER_RESET = "UPDATE_ORDER_RESET";
16 | export const UPDATE_ORDER_FAIL = "UPDATE_ORDER_FAIL";
17 |
18 | export const DELETE_ORDER_REQUEST = "DELETE_ORDER_REQUEST";
19 | export const DELETE_ORDER_SUCCESS = "DELETE_ORDER_SUCCESS";
20 | export const DELETE_ORDER_RESET = "DELETE_ORDER_RESET";
21 | export const DELETE_ORDER_FAIL = "DELETE_ORDER_FAIL";
22 |
23 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST";
24 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS";
25 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL";
26 |
27 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
28 |
--------------------------------------------------------------------------------
/frontend/src/component/Product/Search.css:
--------------------------------------------------------------------------------
1 | .searchBox {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .searchBox > input[type="text"] {
15 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.274);
16 | background-color: white;
17 | border: none;
18 | color: rgba(0, 0, 0, 0.637);
19 | padding: 1vmax 2vmax;
20 | width: 50%;
21 | outline: none;
22 | border-radius: 0%;
23 | font: 300 1.1vmax cursive;
24 | box-sizing: border-box;
25 | height: 8%;
26 | }
27 |
28 | .searchBox > input[type="submit"] {
29 | height: 8%;
30 | border-radius: 0%;
31 | background-color: tomato;
32 | border: none;
33 | padding: 1vmax;
34 | width: 10%;
35 | font: 300 1.1vmax "Roboto";
36 | cursor: pointer;
37 | color: white;
38 | transition: all 0.5s;
39 | }
40 |
41 | .searchBox > input[type="submit"]:hover {
42 | background-color: rgb(55, 97, 214);
43 | }
44 |
45 | @media screen and (max-width: 600px) {
46 | .searchBox > input[type="text"] {
47 | width: 100%;
48 | font: 300 4vw cursive;
49 | height: 10%;
50 | }
51 |
52 | .searchBox > input[type="submit"] {
53 | height: 10%;
54 | width: 30%;
55 | font: 300 4vw "Roboto";
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/backend/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | registerUser,
4 | loginUser,
5 | logout,
6 | forgotPassword,
7 | resetPassword,
8 | getUserDetails,
9 | updatePassword,
10 | updateProfile,
11 | getAllUser,
12 | getSingleUser,
13 | updateUserRole,
14 | deleteUser,
15 | } = require("../controllers/userController");
16 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
17 |
18 | const router = express.Router();
19 |
20 | router.route("/register").post(registerUser);
21 |
22 | router.route("/login").post(loginUser);
23 |
24 | router.route("/password/forgot").post(forgotPassword);
25 |
26 | router.route("/password/reset/:token").put(resetPassword);
27 |
28 | router.route("/logout").get(logout);
29 |
30 | router.route("/me").get(isAuthenticatedUser, getUserDetails);
31 |
32 | router.route("/password/update").put(isAuthenticatedUser, updatePassword);
33 |
34 | router.route("/me/update").put(isAuthenticatedUser, updateProfile);
35 |
36 | router
37 | .route("/admin/users")
38 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAllUser);
39 |
40 | router
41 | .route("/admin/user/:id")
42 | .get(isAuthenticatedUser, authorizeRoles("admin"), getSingleUser)
43 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateUserRole)
44 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteUser);
45 |
46 | module.exports = router;
47 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/productReviews.css:
--------------------------------------------------------------------------------
1 | .productReviewsContainer {
2 | width: 100%;
3 | box-sizing: border-box;
4 | background-color: rgb(255, 255, 255);
5 | border-left: 1px solid rgba(0, 0, 0, 0.158);
6 | height: 100vh;
7 | }
8 |
9 | .productReviewsForm {
10 | width: 20rem;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | margin: auto;
15 | padding: 3vmax;
16 | background-color: white;
17 | }
18 |
19 | .productReviewsFormHeading {
20 | color: rgba(0, 0, 0, 0.733);
21 | font: 300 2rem "Roboto";
22 | text-align: center;
23 | }
24 |
25 | .productReviewsForm > div {
26 | display: flex;
27 | width: 100%;
28 | align-items: center;
29 | margin: 2rem;
30 | }
31 | .productReviewsForm > div > input {
32 | padding: 1vmax 4vmax;
33 | padding-right: 1vmax;
34 | width: 100%;
35 | box-sizing: border-box;
36 | border: 1px solid rgba(0, 0, 0, 0.267);
37 | border-radius: 4px;
38 | font: 300 0.9vmax cursive;
39 | outline: none;
40 | }
41 |
42 | .productReviewsForm > div > svg {
43 | position: absolute;
44 | transform: translateX(1vmax);
45 | font-size: 1.6vmax;
46 | color: rgba(0, 0, 0, 0.623);
47 | }
48 |
49 | @media screen and (max-width: 600px) {
50 | .productReviewsContainer {
51 | border-left: none;
52 | border-top: 1px solid rgba(0, 0, 0, 0.158);
53 | }
54 | .productReviewsForm > div > input {
55 | padding: 2.5vmax 5vmax;
56 | font: 300 1.7vmax cursive;
57 | }
58 |
59 | .productReviewsForm > div > svg {
60 | font-size: 2.8vmax;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Footer/Footer.css:
--------------------------------------------------------------------------------
1 | footer {
2 | margin-top: 10vmax;
3 | padding: 2vmax;
4 | background-color: rgb(34, 33, 33);
5 | color: white;
6 | display: flex;
7 | align-items: center;
8 | }
9 |
10 | .leftFooter {
11 | width: 20%;
12 | display: flex;
13 |
14 | flex-direction: column;
15 | align-items: center;
16 | }
17 | .leftFooter > h4 {
18 | font-family: "Roboto";
19 | font-size: 1vmax;
20 | }
21 | .leftFooter > p {
22 | text-align: center;
23 | font-size: 1.2vmax;
24 | font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande",
25 | "Lucida Sans Unicode", Geneva, Verdana, sans-serif;
26 | }
27 | .leftFooter > img {
28 | width: 10vmax;
29 | margin: 1vmax;
30 | cursor: pointer;
31 | }
32 |
33 | .midFooter {
34 | width: 60%;
35 |
36 | text-align: center;
37 | }
38 |
39 | .midFooter > h1 {
40 | font-size: 4vmax;
41 | font-family: "Roboto";
42 | color: #eb4034;
43 | }
44 | .midFooter > p {
45 | max-width: 60%;
46 | font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
47 | margin: 1vmax auto;
48 | }
49 |
50 | .rightFooter {
51 | width: 20%;
52 |
53 | display: flex;
54 | flex-direction: column;
55 | align-items: center;
56 | }
57 | .rightFooter > h4 {
58 | font-family: "Roboto";
59 | font-size: 1.4vmax;
60 | text-decoration: underline;
61 | }
62 | .rightFooter > a {
63 | text-decoration: none;
64 | font-size: 1.3vmax;
65 | font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
66 | color: white;
67 | transition: all 0.5s;
68 | margin: 0.5vmax;
69 | }
70 |
71 | .rightFooter > a:hover {
72 | color: #eb4034;
73 | }
74 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/CheckoutSteps.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { Typography, Stepper, StepLabel, Step } from "@material-ui/core";
3 | import LocalShippingIcon from "@material-ui/icons/LocalShipping";
4 | import LibraryAddCheckIcon from "@material-ui/icons/LibraryAddCheck";
5 | import AccountBalanceIcon from "@material-ui/icons/AccountBalance";
6 | import "./CheckoutSteps.css";
7 |
8 | const CheckoutSteps = ({ activeStep }) => {
9 | const steps = [
10 | {
11 | label: Shipping Details ,
12 | icon: ,
13 | },
14 | {
15 | label: Confirm Order ,
16 | icon: ,
17 | },
18 | {
19 | label: Payment ,
20 | icon: ,
21 | },
22 | ];
23 |
24 | const stepStyles = {
25 | boxSizing: "border-box",
26 | };
27 |
28 | return (
29 |
30 |
31 | {steps.map((item, index) => (
32 | = index ? true : false}
36 | >
37 | = index ? "tomato" : "rgba(0, 0, 0, 0.649)",
40 | }}
41 | icon={item.icon}
42 | >
43 | {item.label}
44 |
45 |
46 | ))}
47 |
48 |
49 | );
50 | };
51 |
52 | export default CheckoutSteps;
53 |
--------------------------------------------------------------------------------
/frontend/src/component/Order/myOrders.css:
--------------------------------------------------------------------------------
1 | .myOrdersPage {
2 | width: 100vw;
3 | max-width: 100%;
4 | padding: 0 7vmax;
5 | box-sizing: border-box;
6 | background-color: rgb(235, 235, 235);
7 | position: fixed;
8 | top: 0;
9 | left: 0;
10 | height: 100vh;
11 | display: flex;
12 | flex-direction: column;
13 | }
14 |
15 | #myOrdersHeading {
16 | text-align: center;
17 | font: 400 1.2vmax "Roboto";
18 | padding: 0.5vmax;
19 | box-sizing: border-box;
20 | color: rgb(255, 255, 255);
21 | transition: all 0.5s;
22 | background-color: rgb(44, 44, 44);
23 | }
24 |
25 | .myOrdersTable {
26 | background-color: white;
27 | }
28 |
29 | .myOrdersTable div {
30 | font: 300 1vmax "Roboto";
31 | color: rgba(0, 0, 0, 0.678);
32 | border: none;
33 | }
34 |
35 | .myOrdersTable a {
36 | color: rgba(0, 0, 0, 0.527);
37 | transition: all 0.5s;
38 | }
39 |
40 | .myOrdersTable a:hover {
41 | color: tomato;
42 | }
43 |
44 | .MuiDataGrid-columnHeader {
45 | background-color: tomato;
46 | padding: 1vmax !important;
47 | }
48 |
49 | .MuiDataGrid-columnHeader div {
50 | color: rgb(255, 255, 255);
51 | font: 500 1.1vmax "Roboto" !important;
52 | }
53 |
54 | .MuiDataGrid-iconSeparator {
55 | display: none !important;
56 | }
57 |
58 | @media screen and (max-width: 600px) {
59 | .myOrdersPage {
60 | padding: 0;
61 | height: 93vh;
62 | }
63 |
64 | #myOrdersHeading {
65 | font: 400 2.2vmax "Roboto";
66 | padding: 4vw;
67 | }
68 |
69 | .myOrdersTable div {
70 | font: 300 4vw "Roboto";
71 | }
72 |
73 | .MuiDataGrid-columnHeader {
74 | padding: 20px !important;
75 | }
76 |
77 | .MuiDataGrid-columnHeader div {
78 | font: 500 5vw "Roboto" !important;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/frontend/src/component/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { CgMouse } from "react-icons/all";
3 | import "./Home.css";
4 | import ProductCard from "./ProductCard.js";
5 | import MetaData from "../layout/MetaData";
6 | import { clearErrors, getProduct } from "../../actions/productAction";
7 | import { useSelector, useDispatch } from "react-redux";
8 | import Loader from "../layout/Loader/Loader";
9 | import { useAlert } from "react-alert";
10 |
11 | const Home = () => {
12 | const alert = useAlert();
13 | const dispatch = useDispatch();
14 | const { loading, error, products } = useSelector((state) => state.products);
15 |
16 | useEffect(() => {
17 | if (error) {
18 | alert.error(error);
19 | dispatch(clearErrors());
20 | }
21 | dispatch(getProduct());
22 | }, [dispatch, error, alert]);
23 |
24 | return (
25 |
26 | {loading ? (
27 |
28 | ) : (
29 |
30 |
31 |
32 |
42 |
43 | Featured Products
44 |
45 |
46 | {products &&
47 | products.map((product) => (
48 |
49 | ))}
50 |
51 |
52 | )}
53 |
54 | );
55 | };
56 |
57 | export default Home;
58 |
--------------------------------------------------------------------------------
/frontend/src/component/User/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { useSelector } from "react-redux";
3 | import MetaData from "../layout/MetaData";
4 | import Loader from "../layout/Loader/Loader";
5 | import { Link } from "react-router-dom";
6 | import "./Profile.css";
7 |
8 | const Profile = ({ history }) => {
9 | const { user, loading, isAuthenticated } = useSelector((state) => state.user);
10 |
11 | useEffect(() => {
12 | if (isAuthenticated === false) {
13 | history.push("/login");
14 | }
15 | }, [history, isAuthenticated]);
16 | return (
17 |
18 | {loading ? (
19 |
20 | ) : (
21 |
22 |
23 |
24 |
25 |
My Profile
26 |
27 |
Edit Profile
28 |
29 |
30 |
31 |
Full Name
32 |
{user.name}
33 |
34 |
35 |
Email
36 |
{user.email}
37 |
38 |
39 |
Joined On
40 |
{String(user.createdAt).substr(0, 10)}
41 |
42 |
43 |
44 | My Orders
45 | Change Password
46 |
47 |
48 |
49 |
50 | )}
51 |
52 | );
53 | };
54 |
55 | export default Profile;
56 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/payment.css:
--------------------------------------------------------------------------------
1 | .paymentContainer {
2 | display: grid;
3 | place-items: center;
4 | background-color: rgb(255, 255, 255);
5 | height: 65vh;
6 | margin: 2vmax;
7 | }
8 |
9 | .paymentForm {
10 | width: 22%;
11 | height: 100%;
12 | }
13 |
14 | .paymentForm > p {
15 | font: 400 2vmax "Roboto";
16 | color: rgba(0, 0, 0, 0.753);
17 | border-bottom: 1px solid rgba(0, 0, 0, 0.13);
18 | padding: 1vmax 0;
19 | text-align: center;
20 | width: 50%;
21 | margin: auto;
22 | }
23 |
24 | .paymentForm > div {
25 | display: flex;
26 | align-items: center;
27 | margin: 2vmax 0;
28 | }
29 |
30 | .paymentInput {
31 | padding: 1vmax 4vmax;
32 | padding-right: 1vmax;
33 | width: 100%;
34 | box-sizing: border-box;
35 | border: 1px solid rgba(0, 0, 0, 0.267);
36 | border-radius: 4px;
37 | outline: none;
38 | }
39 |
40 | .paymentForm > div > svg {
41 | position: absolute;
42 | transform: translateX(1vmax);
43 | font-size: 1.6vmax;
44 | color: rgba(0, 0, 0, 0.623);
45 | }
46 |
47 | .paymentFormBtn {
48 | border: none;
49 | background-color: tomato;
50 | color: white;
51 | font: 300 0.9vmax "Roboto";
52 | width: 100%;
53 | padding: 0.8vmax;
54 | cursor: pointer;
55 | transition: all 0.5s;
56 | outline: none;
57 | }
58 |
59 | .paymentFormBtn:hover {
60 | background-color: rgb(179, 66, 46);
61 | }
62 |
63 | @media screen and (max-width: 600px) {
64 | .paymentForm {
65 | width: 90%;
66 | }
67 |
68 | .paymentForm > p {
69 | font: 400 8vw "Roboto";
70 | padding: 4vw 0;
71 | width: 60%;
72 | }
73 |
74 | .paymentForm > div {
75 | margin: 10vw 0;
76 | }
77 |
78 | .paymentInput {
79 | padding: 4vw 10vw;
80 | }
81 |
82 | .paymentForm > div > svg {
83 | font-size: 6vw;
84 | }
85 |
86 | .paymentFormBtn {
87 | font: 300 4vw "Roboto";
88 | padding: 4vw;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/backend/models/productModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const productSchema = mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: [true, "Please Enter product Name"],
7 | trim: true,
8 | },
9 | description: {
10 | type: String,
11 | required: [true, "Please Enter product Description"],
12 | },
13 | price: {
14 | type: Number,
15 | required: [true, "Please Enter product Price"],
16 | maxLength: [8, "Price cannot exceed 8 characters"],
17 | },
18 | ratings: {
19 | type: Number,
20 | default: 0,
21 | },
22 | images: [
23 | {
24 | public_id: {
25 | type: String,
26 | required: true,
27 | },
28 | url: {
29 | type: String,
30 | required: true,
31 | },
32 | },
33 | ],
34 | category: {
35 | type: String,
36 | required: [true, "Please Enter Product Category"],
37 | },
38 | Stock: {
39 | type: Number,
40 | required: [true, "Please Enter product Stock"],
41 | maxLength: [4, "Stock cannot exceed 4 characters"],
42 | default: 1,
43 | },
44 | numOfReviews: {
45 | type: Number,
46 | default: 0,
47 | },
48 | reviews: [
49 | {
50 | user: {
51 | type: mongoose.Schema.ObjectId,
52 | ref: "User",
53 | required: true,
54 | },
55 | name: {
56 | type: String,
57 | required: true,
58 | },
59 | rating: {
60 | type: Number,
61 | required: true,
62 | },
63 | comment: {
64 | type: String,
65 | required: true,
66 | },
67 | },
68 | ],
69 |
70 | user: {
71 | type: mongoose.Schema.ObjectId,
72 | ref: "User",
73 | required: true,
74 | },
75 | createdAt: {
76 | type: Date,
77 | default: Date.now,
78 | },
79 | });
80 |
81 | module.exports = mongoose.model("Product", productSchema);
82 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.12.3",
7 | "@material-ui/data-grid": "^4.0.0-alpha.37",
8 | "@material-ui/icons": "^4.11.2",
9 | "@material-ui/lab": "^4.0.0-alpha.60",
10 | "@stripe/react-stripe-js": "^1.4.1",
11 | "@stripe/stripe-js": "^1.17.1",
12 | "@testing-library/jest-dom": "^5.14.1",
13 | "@testing-library/react": "^11.2.7",
14 | "@testing-library/user-event": "^12.8.3",
15 | "axios": "^0.21.1",
16 | "chart.js": "^3.5.1",
17 | "country-state-city": "^3.0.1",
18 | "overlay-navbar": "^1.0.4",
19 | "react": "^17.0.2",
20 | "react-alert": "^7.0.3",
21 | "react-alert-template-basic": "^1.0.2",
22 | "react-chartjs-2": "^3.0.4",
23 | "react-dom": "^17.0.2",
24 | "react-helmet": "^6.1.0",
25 | "react-icons": "^4.2.0",
26 | "react-js-pagination": "^3.0.3",
27 | "react-material-ui-carousel": "^2.3.1",
28 | "react-redux": "^7.2.4",
29 | "react-router-dom": "^5.2.0",
30 | "react-scripts": "4.0.3",
31 | "redux": "^4.1.1",
32 | "redux-devtools-extension": "^2.13.9",
33 | "redux-thunk": "^2.3.0",
34 | "web-vitals": "^1.1.2",
35 | "webfontloader": "^1.6.28"
36 | },
37 | "scripts": {
38 | "start": "react-scripts start",
39 | "build": "react-scripts build",
40 | "test": "react-scripts test",
41 | "eject": "react-scripts eject"
42 | },
43 | "eslintConfig": {
44 | "extends": [
45 | "react-app",
46 | "react-app/jest"
47 | ]
48 | },
49 | "browserslist": {
50 | "production": [
51 | ">0.2%",
52 | "not dead",
53 | "not op_mini all"
54 | ],
55 | "development": [
56 | "last 1 chrome version",
57 | "last 1 firefox version",
58 | "last 1 safari version"
59 | ]
60 | },
61 | "proxy": "http://192.168.29.21:4000"
62 | }
63 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/About/About.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./aboutSection.css";
3 | import { Button, Typography, Avatar } from "@material-ui/core";
4 | import YouTubeIcon from "@material-ui/icons/YouTube";
5 | import InstagramIcon from "@material-ui/icons/Instagram";
6 | const About = () => {
7 | const visitInstagram = () => {
8 | window.location = "https://instagram.com/meabhisingh";
9 | };
10 | return (
11 |
12 |
13 |
14 |
15 |
About Us
16 |
17 |
18 |
19 |
24 |
Abhishek Singh
25 |
26 | Visit Instagram
27 |
28 |
29 | This is a sample wesbite made by @meabhisingh. Only with the
30 | purpose to teach MERN Stack on the channel 6 Pack Programmer
31 |
32 |
33 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default About;
53 |
--------------------------------------------------------------------------------
/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 | newProductReducer,
6 | newReviewReducer,
7 | productDetailsReducer,
8 | productReducer,
9 | productReviewsReducer,
10 | productsReducer,
11 | reviewReducer,
12 | } from "./reducers/productReducer";
13 |
14 | import {
15 | allUsersReducer,
16 | forgotPasswordReducer,
17 | profileReducer,
18 | userDetailsReducer,
19 | userReducer,
20 | } from "./reducers/userReducer";
21 |
22 | import { cartReducer } from "./reducers/cartReducer";
23 | import {
24 | allOrdersReducer,
25 | myOrdersReducer,
26 | newOrderReducer,
27 | orderDetailsReducer,
28 | orderReducer,
29 | } from "./reducers/orderReducer";
30 |
31 | const reducer = combineReducers({
32 | products: productsReducer,
33 | productDetails: productDetailsReducer,
34 | user: userReducer,
35 | profile: profileReducer,
36 | forgotPassword: forgotPasswordReducer,
37 | cart: cartReducer,
38 | newOrder: newOrderReducer,
39 | myOrders: myOrdersReducer,
40 | orderDetails: orderDetailsReducer,
41 | newReview: newReviewReducer,
42 | newProduct: newProductReducer,
43 | product: productReducer,
44 | allOrders: allOrdersReducer,
45 | order: orderReducer,
46 | allUsers: allUsersReducer,
47 | userDetails: userDetailsReducer,
48 | productReviews: productReviewsReducer,
49 | review: reviewReducer,
50 | });
51 |
52 | let initialState = {
53 | cart: {
54 | cartItems: localStorage.getItem("cartItems")
55 | ? JSON.parse(localStorage.getItem("cartItems"))
56 | : [],
57 | shippingInfo: localStorage.getItem("shippingInfo")
58 | ? JSON.parse(localStorage.getItem("shippingInfo"))
59 | : {},
60 | },
61 | };
62 |
63 | const middleware = [thunk];
64 |
65 | const store = createStore(
66 | reducer,
67 | initialState,
68 | composeWithDevTools(applyMiddleware(...middleware))
69 | );
70 |
71 | export default store;
72 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./sidebar.css";
3 | import logo from "../../images/logo.png";
4 | import { Link } from "react-router-dom";
5 | import { TreeView, TreeItem } from "@material-ui/lab";
6 | import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
7 | import PostAddIcon from "@material-ui/icons/PostAdd";
8 | import AddIcon from "@material-ui/icons/Add";
9 | import ImportExportIcon from "@material-ui/icons/ImportExport";
10 | import ListAltIcon from "@material-ui/icons/ListAlt";
11 | import DashboardIcon from "@material-ui/icons/Dashboard";
12 | import PeopleIcon from "@material-ui/icons/People";
13 | import RateReviewIcon from "@material-ui/icons/RateReview";
14 |
15 | const Sidebar = () => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | Dashboard
24 |
25 |
26 |
27 |
}
29 | defaultExpandIcon={
}
30 | >
31 |
32 |
33 | } />
34 |
35 |
36 |
37 | } />
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Orders
46 |
47 |
48 |
49 |
50 | Users
51 |
52 |
53 |
54 |
55 |
56 | Reviews
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default Sidebar;
64 |
--------------------------------------------------------------------------------
/frontend/src/constants/productConstants.js:
--------------------------------------------------------------------------------
1 | export const ALL_PRODUCT_REQUEST = "ALL_PRODUCT_REQUEST";
2 | export const ALL_PRODUCT_SUCCESS = "ALL_PRODUCT_SUCCESS";
3 | export const ALL_PRODUCT_FAIL = "ALL_PRODUCT_FAIL";
4 |
5 | export const ADMIN_PRODUCT_REQUEST = "ADMIN_PRODUCT_REQUEST";
6 | export const ADMIN_PRODUCT_SUCCESS = "ADMIN_PRODUCT_SUCCESS";
7 | export const ADMIN_PRODUCT_FAIL = "ADMIN_PRODUCT_FAIL";
8 |
9 | export const NEW_PRODUCT_REQUEST = "NEW_PRODUCT_REQUEST";
10 | export const NEW_PRODUCT_SUCCESS = "NEW_PRODUCT_SUCCESS";
11 | export const NEW_PRODUCT_RESET = "NEW_PRODUCT_RESET";
12 | export const NEW_PRODUCT_FAIL = "NEW_PRODUCT_FAIL";
13 |
14 | export const UPDATE_PRODUCT_REQUEST = "UPDATE_PRODUCT_REQUEST";
15 | export const UPDATE_PRODUCT_SUCCESS = "UPDATE_PRODUCT_SUCCESS";
16 | export const UPDATE_PRODUCT_RESET = "UPDATE_PRODUCT_RESET";
17 | export const UPDATE_PRODUCT_FAIL = "UPDATE_PRODUCT_FAIL";
18 |
19 | export const DELETE_PRODUCT_REQUEST = "DELETE_PRODUCT_REQUEST";
20 | export const DELETE_PRODUCT_SUCCESS = "DELETE_PRODUCT_SUCCESS";
21 | export const DELETE_PRODUCT_RESET = "DELETE_PRODUCT_RESET";
22 | export const DELETE_PRODUCT_FAIL = "DELETE_PRODUCT_FAIL";
23 |
24 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST";
25 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS";
26 | export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL";
27 |
28 | export const NEW_REVIEW_REQUEST = "NEW_REVIEW_REQUEST";
29 | export const NEW_REVIEW_SUCCESS = "NEW_REVIEW_SUCCESS";
30 | export const NEW_REVIEW_RESET = "NEW_REVIEW_RESET";
31 | export const NEW_REVIEW_FAIL = "NEW_REVIEW_FAIL";
32 |
33 | export const ALL_REVIEW_REQUEST = "ALL_REVIEW_REQUEST";
34 | export const ALL_REVIEW_SUCCESS = "ALL_REVIEW_SUCCESS";
35 | export const ALL_REVIEW_FAIL = "ALL_REVIEW_FAIL";
36 |
37 | export const DELETE_REVIEW_REQUEST = "DELETE_REVIEW_REQUEST";
38 | export const DELETE_REVIEW_SUCCESS = "DELETE_REVIEW_SUCCESS";
39 | export const DELETE_REVIEW_RESET = "DELETE_REVIEW_RESET";
40 | export const DELETE_REVIEW_FAIL = "DELETE_REVIEW_FAIL";
41 |
42 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
43 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/dashboard.css:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | width: 100vw;
3 | max-width: 100%;
4 | display: grid;
5 | grid-template-columns: 1fr 5fr;
6 | position: absolute;
7 | }
8 |
9 | .dashboardContainer {
10 | border-left: 1px solid rgba(0, 0, 0, 0.13);
11 | background-color: rgb(255, 255, 255);
12 | padding: 3rem 0;
13 | }
14 |
15 | .dashboardContainer > h1 {
16 | color: rgba(0, 0, 0, 0.733);
17 | font: 300 2rem "Roboto";
18 | text-align: center;
19 | width: 50%;
20 | padding: 1.5rem;
21 | margin: auto;
22 | }
23 |
24 | .dashboardSummary {
25 | margin: 2rem 0;
26 | }
27 |
28 | .dashboardSummary > div {
29 | display: flex;
30 | background-color: white;
31 | justify-content: center;
32 | }
33 | .dashboardSummary > div > p {
34 | background-color: rgba(70, 117, 218, 0.932);
35 | color: white;
36 | font: 300 1.3rem "Roboto";
37 | text-align: center;
38 | padding: 1.5rem;
39 | width: 100%;
40 | margin: 0 2rem;
41 | }
42 | .dashboardSummaryBox2 > a {
43 | color: rgb(0, 0, 0);
44 | font: 300 2rem "Roboto";
45 | text-align: center;
46 | background-color: rgb(255, 233, 174);
47 | text-decoration: none;
48 | padding: 1.5rem;
49 | width: 10vmax;
50 | height: 10vmax;
51 | margin: 2rem;
52 | border-radius: 100%;
53 | display: flex;
54 | justify-content: center;
55 | align-items: center;
56 | flex-direction: column;
57 | }
58 |
59 | .dashboardSummaryBox2 > a:first-child {
60 | background-color: rgb(255, 110, 110);
61 | color: rgb(255, 255, 255);
62 | }
63 |
64 | .dashboardSummaryBox2 > a:last-child {
65 | background-color: rgb(51, 51, 51);
66 | color: rgb(255, 255, 255);
67 | }
68 |
69 | .lineChart {
70 | width: 80%;
71 | margin: auto;
72 | }
73 |
74 | .doughnutChart {
75 | width: 30vmax;
76 | margin: auto;
77 | }
78 |
79 | @media screen and (max-width: 600px) {
80 | .dashboard {
81 | grid-template-columns: 1fr;
82 | }
83 |
84 | .dashboardContainer {
85 | border-left: none;
86 | }
87 |
88 | .dashboardSummary > div > p {
89 | margin: 0;
90 | }
91 |
92 | .dashboardSummaryBox2 > a {
93 | padding: 0.5rem;
94 | margin: 1rem;
95 | font: 300 0.9rem "Roboto";
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/backend/models/orderModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const orderSchema = new mongoose.Schema({
4 | shippingInfo: {
5 | address: {
6 | type: String,
7 | required: true,
8 | },
9 | city: {
10 | type: String,
11 | required: true,
12 | },
13 |
14 | state: {
15 | type: String,
16 | required: true,
17 | },
18 |
19 | country: {
20 | type: String,
21 | required: true,
22 | },
23 | pinCode: {
24 | type: Number,
25 | required: true,
26 | },
27 | phoneNo: {
28 | type: Number,
29 | required: true,
30 | },
31 | },
32 | orderItems: [
33 | {
34 | name: {
35 | type: String,
36 | required: true,
37 | },
38 | price: {
39 | type: Number,
40 | required: true,
41 | },
42 | quantity: {
43 | type: Number,
44 | required: true,
45 | },
46 | image: {
47 | type: String,
48 | required: true,
49 | },
50 | product: {
51 | type: mongoose.Schema.ObjectId,
52 | ref: "Product",
53 | required: true,
54 | },
55 | },
56 | ],
57 | user: {
58 | type: mongoose.Schema.ObjectId,
59 | ref: "User",
60 | required: true,
61 | },
62 | paymentInfo: {
63 | id: {
64 | type: String,
65 | required: true,
66 | },
67 | status: {
68 | type: String,
69 | required: true,
70 | },
71 | },
72 | paidAt: {
73 | type: Date,
74 | required: true,
75 | },
76 | itemsPrice: {
77 | type: Number,
78 | required: true,
79 | default: 0,
80 | },
81 | taxPrice: {
82 | type: Number,
83 | required: true,
84 | default: 0,
85 | },
86 | shippingPrice: {
87 | type: Number,
88 | required: true,
89 | default: 0,
90 | },
91 | totalPrice: {
92 | type: Number,
93 | required: true,
94 | default: 0,
95 | },
96 | orderStatus: {
97 | type: String,
98 | required: true,
99 | default: "Processing",
100 | },
101 | deliveredAt: Date,
102 | createdAt: {
103 | type: Date,
104 | default: Date.now,
105 | },
106 | });
107 |
108 | module.exports = mongoose.model("Order", orderSchema);
109 |
--------------------------------------------------------------------------------
/frontend/src/component/Product/Products.css:
--------------------------------------------------------------------------------
1 | .productsHeading {
2 | margin: 2vmax auto;
3 | width: 15vw;
4 | border-bottom: 1px solid rgba(0, 0, 0, 0.171);
5 | padding: 2vmax;
6 | color: rgba(0, 0, 0, 0.678);
7 | font: 500 1.5vmax "Roboto";
8 | text-align: center;
9 | }
10 |
11 | .products {
12 | display: flex;
13 | flex-wrap: wrap;
14 | padding: 0 5vmax;
15 | justify-content: center;
16 | min-height: 30vh;
17 | }
18 |
19 | .paginationBox {
20 | display: flex;
21 | justify-content: center;
22 | margin: 6vmax;
23 | }
24 |
25 | .pagination {
26 | display: flex;
27 | justify-content: center;
28 | padding: 0;
29 | }
30 |
31 | .page-item {
32 | background-color: rgb(255, 255, 255);
33 | list-style: none;
34 | border: 1px solid rgba(0, 0, 0, 0.178);
35 | padding: 1vmax 1.5vmax;
36 | transition: all 0.3s;
37 | cursor: pointer;
38 | }
39 | .page-item:first-child {
40 | border-radius: 5px 0 0 5px;
41 | }
42 |
43 | .page-item:last-child {
44 | border-radius: 0 5px 5px 0;
45 | }
46 | .page-link {
47 | text-decoration: none;
48 | font: 300 0.7vmax "Roboto";
49 | color: rgb(80, 80, 80);
50 | transition: all 0.3s;
51 | }
52 |
53 | .page-item:hover {
54 | background-color: rgb(230, 230, 230);
55 | }
56 |
57 | .page-item:hover .page-link {
58 | color: rgb(0, 0, 0);
59 | }
60 |
61 | .pageItemActive {
62 | background-color: tomato;
63 | }
64 |
65 | .pageLinkActive {
66 | color: white;
67 | }
68 |
69 | .filterBox {
70 | width: 10vmax;
71 | position: absolute;
72 | top: 10vmax;
73 | left: 4vmax;
74 | }
75 |
76 | .categoryBox {
77 | padding: 0%;
78 | }
79 |
80 | .category-link {
81 | list-style: none;
82 | color: rgba(0, 0, 0, 0.61);
83 | font: 400 0.8vmax "Roboto";
84 | margin: 0.4vmax;
85 | cursor: pointer;
86 | transition: all 0.5s;
87 | }
88 | .category-link:hover {
89 | color: tomato;
90 | }
91 |
92 | .filterBox > fieldset {
93 | border: 1px solid rgba(0, 0, 0, 0.329);
94 | }
95 |
96 | @media screen and (max-width: 600px) {
97 | .filterBox {
98 | width: 20vmax;
99 | position: static;
100 | margin: auto;
101 | }
102 |
103 | .page-link {
104 | font: 300 1.7vmax "Roboto";
105 | }
106 | .category-link {
107 | font: 400 1.8vmax "Roboto";
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/backend/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const validator = require("validator");
3 | const bcrypt = require("bcryptjs");
4 | const jwt = require("jsonwebtoken");
5 | const crypto = require("crypto");
6 |
7 | const userSchema = new mongoose.Schema({
8 | name: {
9 | type: String,
10 | required: [true, "Please Enter Your Name"],
11 | maxLength: [30, "Name cannot exceed 30 characters"],
12 | minLength: [4, "Name should have more than 4 characters"],
13 | },
14 | email: {
15 | type: String,
16 | required: [true, "Please Enter Your Email"],
17 | unique: true,
18 | validate: [validator.isEmail, "Please Enter a valid Email"],
19 | },
20 | password: {
21 | type: String,
22 | required: [true, "Please Enter Your Password"],
23 | minLength: [8, "Password should be greater than 8 characters"],
24 | select: false,
25 | },
26 | avatar: {
27 | public_id: {
28 | type: String,
29 | required: true,
30 | },
31 | url: {
32 | type: String,
33 | required: true,
34 | },
35 | },
36 | role: {
37 | type: String,
38 | default: "user",
39 | },
40 | createdAt: {
41 | type: Date,
42 | default: Date.now,
43 | },
44 |
45 | resetPasswordToken: String,
46 | resetPasswordExpire: Date,
47 | });
48 |
49 | userSchema.pre("save", async function (next) {
50 | if (!this.isModified("password")) {
51 | next();
52 | }
53 |
54 | this.password = await bcrypt.hash(this.password, 10);
55 | });
56 |
57 | // JWT TOKEN
58 | userSchema.methods.getJWTToken = function () {
59 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
60 | expiresIn: process.env.JWT_EXPIRE,
61 | });
62 | };
63 |
64 | // Compare Password
65 |
66 | userSchema.methods.comparePassword = async function (password) {
67 | return await bcrypt.compare(password, this.password);
68 | };
69 |
70 | // Generating Password Reset Token
71 | userSchema.methods.getResetPasswordToken = function () {
72 | // Generating Token
73 | const resetToken = crypto.randomBytes(20).toString("hex");
74 |
75 | // Hashing and adding resetPasswordToken to userSchema
76 | this.resetPasswordToken = crypto
77 | .createHash("sha256")
78 | .update(resetToken)
79 | .digest("hex");
80 |
81 | this.resetPasswordExpire = Date.now() + 15 * 60 * 1000;
82 |
83 | return resetToken;
84 | };
85 |
86 | module.exports = mongoose.model("User", userSchema);
87 |
--------------------------------------------------------------------------------
/frontend/src/constants/userConstants.js:
--------------------------------------------------------------------------------
1 | export const LOGIN_REQUEST = "LOGIN_REQUEST";
2 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
3 | export const LOGIN_FAIL = "LOGIN_FAIL";
4 |
5 | export const REGISTER_USER_REQUEST = "REGISTER_USER_REQUEST";
6 | export const REGISTER_USER_SUCCESS = "REGISTER_USER_SUCCESS";
7 | export const REGISTER_USER_FAIL = "REGISTER_USER_FAIL";
8 |
9 | export const LOAD_USER_REQUEST = "LOAD_USER_REQUEST";
10 | export const LOAD_USER_SUCCESS = "LOAD_USER_SUCCESS";
11 | export const LOAD_USER_FAIL = "LOAD_USER_FAIL";
12 |
13 | export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
14 | export const LOGOUT_FAIL = "LOGOUT_FAIL";
15 |
16 | export const UPDATE_PROFILE_REQUEST = "UPDATE_PROFILE_REQUEST";
17 | export const UPDATE_PROFILE_SUCCESS = "UPDATE_PROFILE_SUCCESS";
18 | export const UPDATE_PROFILE_RESET = "UPDATE_PROFILE_RESET";
19 | export const UPDATE_PROFILE_FAIL = "UPDATE_PROFILE_FAIL";
20 |
21 | export const UPDATE_PASSWORD_REQUEST = "UPDATE_PASSWORD_REQUEST";
22 | export const UPDATE_PASSWORD_SUCCESS = "UPDATE_PASSWORD_SUCCESS";
23 | export const UPDATE_PASSWORD_RESET = "UPDATE_PASSWORD_RESET";
24 | export const UPDATE_PASSWORD_FAIL = "UPDATE_PASSWORD_FAIL";
25 |
26 | export const FORGOT_PASSWORD_REQUEST = "FORGOT_PASSWORD_REQUEST";
27 | export const FORGOT_PASSWORD_SUCCESS = "FORGOT_PASSWORD_SUCCESS";
28 | export const FORGOT_PASSWORD_FAIL = "FORGOT_PASSWORD_FAIL";
29 |
30 | export const RESET_PASSWORD_REQUEST = "RESET_PASSWORD_REQUEST";
31 | export const RESET_PASSWORD_SUCCESS = "RESET_PASSWORD_SUCCESS";
32 | export const RESET_PASSWORD_FAIL = "RESET_PASSWORD_FAIL";
33 |
34 | export const ALL_USERS_REQUEST = "ALL_USERS_REQUEST";
35 | export const ALL_USERS_SUCCESS = "ALL_USERS_SUCCESS";
36 | export const ALL_USERS_FAIL = "ALL_USERS_FAIL";
37 |
38 | export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST";
39 | export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS";
40 | export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL";
41 |
42 | export const UPDATE_USER_REQUEST = "UPDATE_USER_REQUEST";
43 | export const UPDATE_USER_SUCCESS = "UPDATE_USER_SUCCESS";
44 | export const UPDATE_USER_RESET = "UPDATE_USER_RESET";
45 | export const UPDATE_USER_FAIL = "UPDATE_USER_FAIL";
46 |
47 | export const DELETE_USER_REQUEST = "DELETE_USER_REQUEST";
48 | export const DELETE_USER_SUCCESS = "DELETE_USER_SUCCESS";
49 | export const DELETE_USER_FAIL = "DELETE_USER_FAIL";
50 | export const DELETE_USER_RESET = "DELETE_USER_RESET";
51 |
52 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
53 |
--------------------------------------------------------------------------------
/frontend/src/component/Order/orderDetails.css:
--------------------------------------------------------------------------------
1 | .orderDetailsPage {
2 | background-color: white;
3 | }
4 |
5 | .orderDetailsContainer > h1 {
6 | font: 300 3vmax "Roboto";
7 | margin: 4vmax 0;
8 | color: tomato;
9 | }
10 |
11 | .orderDetailsContainer {
12 | padding: 5vmax;
13 | padding-bottom: 0%;
14 | }
15 |
16 | .orderDetailsContainer > p {
17 | font: 400 1.8vmax "Roboto";
18 | }
19 |
20 | .orderDetailsContainerBox,
21 | .orderDetailsCartItemsContainer {
22 | margin: 2vmax;
23 | }
24 |
25 | .orderDetailsContainerBox > div {
26 | display: flex;
27 | margin: 1vmax 0;
28 | }
29 |
30 | .orderDetailsContainerBox > div > p {
31 | font: 400 1vmax "Roboto";
32 | color: black;
33 | }
34 | .orderDetailsContainerBox > div > span {
35 | margin: 0 1vmax;
36 | font: 100 1vmax "Roboto";
37 | color: #575757;
38 | }
39 |
40 | .orderDetailsCartItems > p {
41 | font: 400 1.8vmax "Roboto";
42 | }
43 |
44 | .orderDetailsCartItems {
45 | padding: 2vmax 5vmax;
46 | border-top: 1px solid rgba(0, 0, 0, 0.164);
47 | }
48 |
49 | .orderDetailsCartItemsContainer > div {
50 | display: flex;
51 | font: 400 1vmax "Roboto";
52 | align-items: center;
53 | margin: 2vmax 0;
54 | }
55 |
56 | .orderDetailsCartItemsContainer > div > img {
57 | width: 3vmax;
58 | }
59 |
60 | .orderDetailsCartItemsContainer > div > a {
61 | color: #575757;
62 | margin: 0 2vmax;
63 | width: 60%;
64 | text-decoration: none;
65 | }
66 |
67 | .orderDetailsCartItemsContainer > div > span {
68 | font: 100 1vmax "Roboto";
69 | color: #5e5e5e;
70 | }
71 |
72 | @media screen and (max-width: 600px) {
73 | .orderDetailsContainer > p {
74 | font: 400 6vw "Roboto";
75 | }
76 |
77 | .orderDetailsContainerBox > div {
78 | margin: 6vw 0;
79 | }
80 |
81 | .orderDetailsContainerBox > div > p {
82 | font: 400 4vw "Roboto";
83 | }
84 | .orderDetailsContainerBox > div > span {
85 | font: 100 4vw "Roboto";
86 | }
87 |
88 | .orderDetailsCartItems > p {
89 | font: 400 6vw "Roboto";
90 | }
91 |
92 | .orderDetailsCartItemsContainer > div {
93 | font: 400 4vw "Roboto";
94 | margin: 4vw 0;
95 | }
96 |
97 | .orderDetailsCartItemsContainer > div > img {
98 | width: 10vw;
99 | }
100 |
101 | .orderDetailsCartItemsContainer > div > a {
102 | margin: 2vw;
103 | width: 30%;
104 | }
105 |
106 | .orderDetailsCartItemsContainer > div > span {
107 | font: 100 4vw "Roboto";
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/frontend/src/component/User/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from "react";
2 | import "./ForgotPassword.css";
3 | import Loader from "../layout/Loader/Loader";
4 | import MailOutlineIcon from "@material-ui/icons/MailOutline";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { clearErrors, forgotPassword } from "../../actions/userAction";
7 | import { useAlert } from "react-alert";
8 | import MetaData from "../layout/MetaData";
9 |
10 | const ForgotPassword = () => {
11 | const dispatch = useDispatch();
12 | const alert = useAlert();
13 |
14 | const { error, message, loading } = useSelector(
15 | (state) => state.forgotPassword
16 | );
17 |
18 | const [email, setEmail] = useState("");
19 |
20 | const forgotPasswordSubmit = (e) => {
21 | e.preventDefault();
22 |
23 | const myForm = new FormData();
24 |
25 | myForm.set("email", email);
26 | dispatch(forgotPassword(myForm));
27 | };
28 |
29 | useEffect(() => {
30 | if (error) {
31 | alert.error(error);
32 | dispatch(clearErrors());
33 | }
34 |
35 | if (message) {
36 | alert.success(message);
37 | }
38 | }, [dispatch, error, alert, message]);
39 |
40 | return (
41 |
42 | {loading ? (
43 |
44 | ) : (
45 |
46 |
47 |
48 |
49 |
Forgot Password
50 |
51 |
73 |
74 |
75 |
76 | )}
77 |
78 | );
79 | };
80 |
81 | export default ForgotPassword;
82 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/Shipping.css:
--------------------------------------------------------------------------------
1 | .shippingContainer {
2 | width: 100vw;
3 | max-width: 100%;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | flex-direction: column;
8 | }
9 |
10 | .shippingBox {
11 | background-color: white;
12 | width: 25vw;
13 | height: 90vh;
14 | box-sizing: border-box;
15 | overflow: hidden;
16 | }
17 |
18 | .shippingHeading {
19 | text-align: center;
20 | color: rgba(0, 0, 0, 0.664);
21 | font: 400 1.3vmax "Roboto";
22 | padding: 1.3vmax;
23 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
24 | width: 50%;
25 | margin: auto;
26 | }
27 |
28 | .shippingForm {
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | margin: auto;
33 | padding: 2vmax;
34 | justify-content: space-evenly;
35 | height: 80%;
36 | transition: all 0.5s;
37 | }
38 |
39 | .shippingForm > div {
40 | display: flex;
41 | width: 100%;
42 | align-items: center;
43 | }
44 |
45 | .shippingForm > div > input,
46 | .shippingForm > div > select {
47 | padding: 1vmax 4vmax;
48 | padding-right: 1vmax;
49 | width: 100%;
50 | box-sizing: border-box;
51 | border: 1px solid rgba(0, 0, 0, 0.267);
52 | border-radius: 4px;
53 | font: 300 0.9vmax cursive;
54 | outline: none;
55 | }
56 |
57 | .shippingForm > div > svg {
58 | position: absolute;
59 | transform: translateX(1vmax);
60 | font-size: 1.6vmax;
61 | color: rgba(0, 0, 0, 0.623);
62 | }
63 |
64 | .shippingBtn {
65 | border: none;
66 | background-color: tomato;
67 | color: white;
68 | font: 300 1vmax "Roboto";
69 | width: 100%;
70 | padding: 1vmax;
71 | cursor: pointer;
72 | transition: all 0.5s;
73 | outline: none;
74 | margin: 2vmax;
75 | }
76 |
77 | .shippingBtn:hover {
78 | background-color: rgb(179, 66, 46);
79 | }
80 |
81 | @media screen and (max-width: 600px) {
82 | .shippingBox {
83 | width: 100vw;
84 | height: 95vh;
85 | }
86 |
87 | .shippingHeading {
88 | font: 400 6vw "Roboto";
89 | padding: 5vw;
90 | }
91 |
92 | .shippingForm {
93 | padding: 11vw;
94 | }
95 |
96 | .shippingForm > div > input,
97 | .shippingForm > div > select {
98 | padding: 5vw 10vw;
99 | font: 300 4vw cursive;
100 | }
101 |
102 | .shippingForm > div > svg {
103 | font-size: 6vw;
104 | transform: translateX(3vw);
105 | }
106 |
107 | .shippingBtn {
108 | font: 300 4vw "Roboto";
109 | padding: 4vw;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/component/User/ResetPassword.css:
--------------------------------------------------------------------------------
1 | .resetPasswordContainer {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .resetPasswordBox {
15 | background-color: white;
16 | width: 25vw;
17 | height: 70vh;
18 | box-sizing: border-box;
19 | overflow: hidden;
20 | }
21 |
22 | .resetPasswordHeading {
23 | text-align: center;
24 | color: rgba(0, 0, 0, 0.664);
25 | font: 400 1.3vmax "Roboto";
26 | padding: 1.3vmax;
27 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
28 | width: 50%;
29 | margin: auto;
30 | }
31 |
32 | .resetPasswordForm {
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | margin: auto;
37 | padding: 2vmax;
38 | justify-content: space-evenly;
39 | height: 70%;
40 | transition: all 0.5s;
41 | }
42 |
43 | .resetPasswordForm > div {
44 | display: flex;
45 | width: 100%;
46 | align-items: center;
47 | }
48 |
49 | .resetPasswordForm > div > input {
50 | padding: 1vmax 4vmax;
51 | padding-right: 1vmax;
52 | width: 100%;
53 | box-sizing: border-box;
54 | border: 1px solid rgba(0, 0, 0, 0.267);
55 | border-radius: 4px;
56 | font: 300 0.9vmax cursive;
57 | outline: none;
58 | }
59 |
60 | .resetPasswordForm > div > svg {
61 | position: absolute;
62 | transform: translateX(1vmax);
63 | font-size: 1.6vmax;
64 | color: rgba(0, 0, 0, 0.623);
65 | }
66 |
67 | .resetPasswordBtn {
68 | border: none;
69 | background-color: tomato;
70 | color: white;
71 | font: 300 0.9vmax "Roboto";
72 | width: 100%;
73 | padding: 0.8vmax;
74 | cursor: pointer;
75 | transition: all 0.5s;
76 | border-radius: 4px;
77 | outline: none;
78 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
79 | }
80 |
81 | .resetPasswordBtn:hover {
82 | background-color: rgb(179, 66, 46);
83 | }
84 |
85 | @media screen and (max-width: 600px) {
86 | .resetPasswordContainer {
87 | background-color: white;
88 | }
89 | .resetPasswordBox {
90 | width: 100vw;
91 | height: 95vh;
92 | }
93 |
94 | .resetPasswordForm {
95 | padding: 5vmax;
96 | }
97 |
98 | .resetPasswordForm > div > input {
99 | padding: 2.5vmax 5vmax;
100 | font: 300 1.7vmax cursive;
101 | }
102 |
103 | .resetPasswordForm > div > svg {
104 | font-size: 2.8vmax;
105 | }
106 |
107 | .resetPasswordBtn {
108 | font: 300 1.9vmax "Roboto";
109 | padding: 1.8vmax;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/component/User/ForgotPassword.css:
--------------------------------------------------------------------------------
1 | .forgotPasswordContainer {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .forgotPasswordBox {
15 | background-color: white;
16 | width: 25vw;
17 | height: 40vh;
18 | box-sizing: border-box;
19 | overflow: hidden;
20 | }
21 |
22 | .forgotPasswordHeading {
23 | text-align: center;
24 | color: rgba(0, 0, 0, 0.664);
25 | font: 400 1.3vmax "Roboto";
26 | padding: 1.3vmax;
27 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
28 | width: 50%;
29 | margin: auto;
30 | }
31 |
32 | .forgotPasswordForm {
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | margin: auto;
37 | padding: 2vmax;
38 | justify-content: space-evenly;
39 | height: 70%;
40 | transition: all 0.5s;
41 | }
42 |
43 | .forgotPasswordForm > div {
44 | display: flex;
45 | width: 100%;
46 | align-items: center;
47 | }
48 |
49 | .forgotPasswordForm > div > input {
50 | padding: 1vmax 4vmax;
51 | padding-right: 1vmax;
52 | width: 100%;
53 | box-sizing: border-box;
54 | border: 1px solid rgba(0, 0, 0, 0.267);
55 | border-radius: 4px;
56 | font: 300 0.9vmax cursive;
57 | outline: none;
58 | }
59 |
60 | .forgotPasswordForm > div > svg {
61 | position: absolute;
62 | transform: translateX(1vmax);
63 | font-size: 1.6vmax;
64 | color: rgba(0, 0, 0, 0.623);
65 | }
66 |
67 | .forgotPasswordBtn {
68 | border: none;
69 | background-color: tomato;
70 | color: white;
71 | font: 300 0.9vmax "Roboto";
72 | width: 100%;
73 | padding: 0.8vmax;
74 | cursor: pointer;
75 | transition: all 0.5s;
76 | border-radius: 4px;
77 | outline: none;
78 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
79 | }
80 |
81 | .forgotPasswordBtn:hover {
82 | background-color: rgb(179, 66, 46);
83 | }
84 |
85 | @media screen and (max-width: 600px) {
86 | .forgotPasswordContainer {
87 | background-color: white;
88 | }
89 | .forgotPasswordBox {
90 | width: 100vw;
91 | height: 95vh;
92 | }
93 |
94 | .forgotPasswordForm {
95 | padding: 5vmax;
96 | }
97 |
98 | .forgotPasswordForm > div > input {
99 | padding: 2.5vmax 5vmax;
100 | font: 300 1.7vmax cursive;
101 | }
102 |
103 | .forgotPasswordForm > div > svg {
104 | font-size: 2.8vmax;
105 | }
106 |
107 | .forgotPasswordBtn {
108 | font: 300 1.9vmax "Roboto";
109 | padding: 1.8vmax;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/component/User/UpdatePassword.css:
--------------------------------------------------------------------------------
1 | .updatePasswordContainer {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .updatePasswordBox {
15 | background-color: white;
16 | width: 25vw;
17 | height: 70vh;
18 | box-sizing: border-box;
19 | overflow: hidden;
20 | }
21 |
22 | .updatePasswordHeading {
23 | text-align: center;
24 | color: rgba(0, 0, 0, 0.664);
25 | font: 400 1.3vmax "Roboto";
26 | padding: 1.3vmax;
27 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
28 | width: 50%;
29 | margin: auto;
30 | }
31 |
32 | .updatePasswordForm {
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | margin: auto;
37 | padding: 2vmax;
38 | justify-content: space-evenly;
39 | height: 70%;
40 | transition: all 0.5s;
41 | }
42 |
43 | .updatePasswordForm > div {
44 | display: flex;
45 | width: 100%;
46 | align-items: center;
47 | }
48 |
49 | .updatePasswordForm > div > input {
50 | padding: 1vmax 4vmax;
51 | padding-right: 1vmax;
52 | width: 100%;
53 | box-sizing: border-box;
54 | border: 1px solid rgba(0, 0, 0, 0.267);
55 | border-radius: 4px;
56 | font: 300 0.9vmax cursive;
57 | outline: none;
58 | }
59 |
60 | .updatePasswordForm > div > svg {
61 | position: absolute;
62 | transform: translateX(1vmax);
63 | font-size: 1.6vmax;
64 | color: rgba(0, 0, 0, 0.623);
65 | }
66 |
67 | .updatePasswordBtn {
68 | border: none;
69 | background-color: tomato;
70 | color: white;
71 | font: 300 0.9vmax "Roboto";
72 | width: 100%;
73 | padding: 0.8vmax;
74 | cursor: pointer;
75 | transition: all 0.5s;
76 | border-radius: 4px;
77 | outline: none;
78 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
79 | }
80 |
81 | .updatePasswordBtn:hover {
82 | background-color: rgb(179, 66, 46);
83 | }
84 |
85 | @media screen and (max-width: 600px) {
86 | .updatePasswordContainer {
87 | background-color: white;
88 | }
89 | .updatePasswordBox {
90 | width: 100vw;
91 | height: 95vh;
92 | }
93 |
94 | .updatePasswordForm {
95 | padding: 5vmax;
96 | }
97 |
98 | .updatePasswordForm > div > input {
99 | padding: 2.5vmax 5vmax;
100 | font: 300 1.7vmax cursive;
101 | }
102 |
103 | .updatePasswordForm > div > svg {
104 | font-size: 2.8vmax;
105 | }
106 |
107 | .updatePasswordBtn {
108 | font: 300 1.9vmax "Roboto";
109 | padding: 1.8vmax;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/About/aboutSection.css:
--------------------------------------------------------------------------------
1 | .aboutSection {
2 | height: 100vh;
3 | width: 100vw;
4 | max-width: 100%;
5 | background-color: white;
6 | display: grid;
7 | grid-template-columns: 4fr 4fr;
8 | position: fixed;
9 | }
10 |
11 | .aboutSectionGradient {
12 | background-image: linear-gradient(
13 | to bottom right,
14 | rgb(78, 81, 255),
15 | rgb(74, 137, 189)
16 | );
17 | }
18 |
19 | .aboutSectionContainer {
20 | position: absolute;
21 | left: 50%;
22 | top: 50%;
23 | background-color: rgb(255, 255, 255);
24 | width: 80vw;
25 | height: 80vh;
26 | transform: translateX(-50%) translateY(-50%);
27 | box-shadow: -10px 10px 10px rgba(0, 0, 0, 0.192);
28 |
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | }
33 |
34 | .aboutSectionContainer > h1 {
35 | font: 400 3vmax "Roboto";
36 | color: tomato;
37 | margin: 2vmax;
38 | }
39 |
40 | .aboutSectionContainer > div {
41 | display: flex;
42 | width: 100%;
43 | }
44 |
45 | .aboutSectionContainer > div > div {
46 | width: 100%;
47 | display: flex;
48 | flex-direction: column;
49 | align-items: center;
50 | padding: 2vmax;
51 | box-sizing: border-box;
52 | }
53 |
54 | .aboutSectionContainer > div > div > p {
55 | color: rgba(0, 0, 0, 0.623);
56 | font: 100 1vmax "Roboto";
57 | }
58 |
59 | .aboutSectionContainer > div > div > button {
60 | margin: 1vmax 0;
61 | }
62 |
63 | .aboutSectionContainer > div > div > span {
64 | font: 100 1vmax "Roboto";
65 | color: rgba(0, 0, 0, 0.616);
66 | text-align: center;
67 | width: 80%;
68 | }
69 |
70 | .aboutSectionContainer2 {
71 | border-left: 1px solid rgba(0, 0, 0, 0.116);
72 | }
73 |
74 | .aboutSectionContainer2 > h2 {
75 | color: rgba(0, 0, 0, 0.623);
76 | font: 100 2vmax "Roboto";
77 | margin: 2vmax;
78 | }
79 |
80 | .aboutSectionContainer2 > a > svg {
81 | font-size: 4vmax;
82 | }
83 |
84 | .youtubeSvgIcon {
85 | color: tomato;
86 | }
87 |
88 | .instagramSvgIcon {
89 | color: rgb(144, 81, 202);
90 | }
91 |
92 | @media screen and (max-width: 600px) {
93 | .aboutSectionContainer > div {
94 | display: block;
95 | }
96 |
97 | .aboutSectionContainer > h1 {
98 | font: 400 5vmax "Roboto";
99 | margin: 3vmax;
100 | }
101 |
102 | .aboutSectionContainer > div > div > p {
103 | font: 100 2vmax "Roboto";
104 | }
105 |
106 | .aboutSectionContainer > div > div > span {
107 | font: 100 1.4vmax "Roboto";
108 | width: 70%;
109 | }
110 |
111 | .aboutSectionContainer2 {
112 | border-left: none;
113 | }
114 |
115 | .aboutSectionContainer2 > h2 {
116 | font: 100 3vmax "Roboto";
117 | margin: 2vmax;
118 | }
119 |
120 | .aboutSectionContainer2 > a > svg {
121 | font-size: 6vmax;
122 | margin: 1vmax;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/frontend/src/component/layout/Header/UserOptions.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from "react";
2 | import "./Header.css";
3 | import { SpeedDial, SpeedDialAction } from "@material-ui/lab";
4 | import Backdrop from "@material-ui/core/Backdrop";
5 | import DashboardIcon from "@material-ui/icons/Dashboard";
6 | import PersonIcon from "@material-ui/icons/Person";
7 | import ExitToAppIcon from "@material-ui/icons/ExitToApp";
8 | import ListAltIcon from "@material-ui/icons/ListAlt";
9 | import ShoppingCartIcon from "@material-ui/icons/ShoppingCart";
10 | import { useHistory } from "react-router-dom";
11 | import { useAlert } from "react-alert";
12 | import { logout } from "../../../actions/userAction";
13 | import { useDispatch, useSelector } from "react-redux";
14 |
15 | const UserOptions = ({ user }) => {
16 | const { cartItems } = useSelector((state) => state.cart);
17 |
18 | const [open, setOpen] = useState(false);
19 | const history = useHistory();
20 | const alert = useAlert();
21 | const dispatch = useDispatch();
22 |
23 | const options = [
24 | { icon: , name: "Orders", func: orders },
25 | { icon: , name: "Profile", func: account },
26 | {
27 | icon: (
28 | 0 ? "tomato" : "unset" }}
30 | />
31 | ),
32 | name: `Cart(${cartItems.length})`,
33 | func: cart,
34 | },
35 | { icon: , name: "Logout", func: logoutUser },
36 | ];
37 |
38 | if (user.role === "admin") {
39 | options.unshift({
40 | icon: ,
41 | name: "Dashboard",
42 | func: dashboard,
43 | });
44 | }
45 |
46 | function dashboard() {
47 | history.push("/admin/dashboard");
48 | }
49 |
50 | function orders() {
51 | history.push("/orders");
52 | }
53 | function account() {
54 | history.push("/account");
55 | }
56 | function cart() {
57 | history.push("/cart");
58 | }
59 | function logoutUser() {
60 | dispatch(logout());
61 | alert.success("Logout Successfully");
62 | }
63 |
64 | return (
65 |
66 |
67 | setOpen(false)}
70 | onOpen={() => setOpen(true)}
71 | style={{ zIndex: "11" }}
72 | open={open}
73 | direction="down"
74 | className="speedDial"
75 | icon={
76 |
81 | }
82 | >
83 | {options.map((item) => (
84 |
91 | ))}
92 |
93 |
94 | );
95 | };
96 |
97 | export default UserOptions;
98 |
--------------------------------------------------------------------------------
/frontend/src/component/User/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from "react";
2 | import "./ResetPassword.css";
3 | import Loader from "../layout/Loader/Loader";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { clearErrors, resetPassword } from "../../actions/userAction";
6 | import { useAlert } from "react-alert";
7 | import MetaData from "../layout/MetaData";
8 | import LockOpenIcon from "@material-ui/icons/LockOpen";
9 | import LockIcon from "@material-ui/icons/Lock";
10 |
11 | const ResetPassword = ({ history, match }) => {
12 | const dispatch = useDispatch();
13 | const alert = useAlert();
14 |
15 | const { error, success, loading } = useSelector(
16 | (state) => state.forgotPassword
17 | );
18 |
19 | const [password, setPassword] = useState("");
20 | const [confirmPassword, setConfirmPassword] = useState("");
21 |
22 | const resetPasswordSubmit = (e) => {
23 | e.preventDefault();
24 |
25 | const myForm = new FormData();
26 |
27 | myForm.set("password", password);
28 | myForm.set("confirmPassword", confirmPassword);
29 |
30 | dispatch(resetPassword(match.params.token, myForm));
31 | };
32 |
33 | useEffect(() => {
34 | if (error) {
35 | alert.error(error);
36 | dispatch(clearErrors());
37 | }
38 |
39 | if (success) {
40 | alert.success("Password Updated Successfully");
41 |
42 | history.push("/login");
43 | }
44 | }, [dispatch, error, alert, history, success]);
45 |
46 | return (
47 |
48 | {loading ? (
49 |
50 | ) : (
51 |
52 |
53 |
54 |
55 |
Update Profile
56 |
57 |
87 |
88 |
89 |
90 | )}
91 |
92 | );
93 | };
94 |
95 | export default ResetPassword;
96 |
--------------------------------------------------------------------------------
/frontend/src/component/Home/Home.css:
--------------------------------------------------------------------------------
1 | .banner {
2 | background-image: url("../../images/cover.jfif");
3 | background-position: center;
4 | background-repeat: no-repeat;
5 | background-size: cover;
6 | height: 100vmin;
7 | display: flex;
8 | flex-direction: column;
9 | text-align: center;
10 | align-items: center;
11 | justify-content: center;
12 | color: white;
13 | }
14 |
15 | .banner > h1 {
16 | margin: 5vmax;
17 |
18 | font: 600 2.5vmax "Roboto";
19 | }
20 |
21 | .banner > p {
22 | font: 300 1.4vmax "Lucida Sans";
23 | }
24 |
25 | .banner > a > button {
26 | margin-bottom: 5vmax;
27 | cursor: pointer;
28 | background-color: white;
29 | border: 1px solid white;
30 | border-radius: 0;
31 | padding: 1vmax;
32 | transition: all 0.5s;
33 | width: 9vmax;
34 | font: 500 1vmax "Roboto";
35 | }
36 | .banner > a > button:hover {
37 | background-color: rgba(255, 255, 255, 0);
38 | color: white;
39 | }
40 |
41 | .banner::after {
42 | content: "";
43 | width: 100vw;
44 | height: 100vmin;
45 | background-color: #ffffff;
46 | position: absolute;
47 | top: 0%;
48 | left: 0;
49 | clip-path: polygon(100% 68%, 0 100%, 100% 100%);
50 | max-width: 100%;
51 | }
52 |
53 | .homeHeading {
54 | text-align: center;
55 | font-family: Roboto;
56 | font-size: 1.4vmax;
57 | border-bottom: 1px solid rgba(21, 21, 21, 0.5);
58 | width: 20vmax;
59 | padding: 1vmax;
60 | margin: 5vmax auto;
61 | color: rgb(0, 0, 0, 0.7);
62 | }
63 |
64 | .container {
65 | display: flex;
66 | margin: 2vmax auto;
67 | width: 80vw;
68 | flex-wrap: wrap;
69 | justify-content: center;
70 | max-width: 100%;
71 | }
72 |
73 | .productCard {
74 | width: 14vmax;
75 | display: flex;
76 | flex-direction: column;
77 | text-decoration: none;
78 | color: rgb(48, 48, 48);
79 | margin: 2vmax;
80 | transition: all 0.5s;
81 | padding-bottom: 0.5vmax;
82 | }
83 |
84 | .productCard > img {
85 | width: 14vmax;
86 | }
87 |
88 | .productCard > div {
89 | margin: 0.5vmax;
90 | display: flex;
91 | justify-content: flex-start;
92 | align-items: center;
93 | }
94 |
95 | .productCardSpan {
96 | margin: 0.5vmax;
97 | font: 300 0.7vmax "Roboto";
98 | }
99 |
100 | .productCard > p {
101 | font-family: "Roboto";
102 | font-size: 1.2vmax;
103 | margin: 1vmax 0.5vmax;
104 | margin-bottom: 0;
105 | }
106 |
107 | .productCard > span {
108 | margin: 0.5vmax;
109 | color: tomato;
110 | font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif;
111 | font-size: 1vmax;
112 | }
113 |
114 | .productCard:hover {
115 | box-shadow: 0 0 5px rgba(15, 15, 15, 0.26);
116 |
117 | transform: translateY(-1vmax);
118 | }
119 |
120 | @media screen and (max-width: 600px) {
121 | .productCard > p {
122 | font-size: 1.7vmax;
123 | }
124 |
125 | .productCard > div {
126 | margin: 0vmax;
127 | display: block;
128 | }
129 |
130 | .productCard > span {
131 | font-size: 1.5vmax;
132 | }
133 |
134 | .productCard > div > span {
135 | margin: 0 0.5vmax;
136 | font: 300 1vmax "Roboto";
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/frontend/src/component/Order/MyOrders.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { DataGrid } from "@material-ui/data-grid";
3 | import "./myOrders.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { clearErrors, myOrders } from "../../actions/orderAction";
6 | import Loader from "../layout/Loader/Loader";
7 | import { Link } from "react-router-dom";
8 | import { useAlert } from "react-alert";
9 | import Typography from "@material-ui/core/Typography";
10 | import MetaData from "../layout/MetaData";
11 | import LaunchIcon from "@material-ui/icons/Launch";
12 |
13 | const MyOrders = () => {
14 | const dispatch = useDispatch();
15 |
16 | const alert = useAlert();
17 |
18 | const { loading, error, orders } = useSelector((state) => state.myOrders);
19 | const { user } = useSelector((state) => state.user);
20 |
21 | const columns = [
22 | { field: "id", headerName: "Order ID", minWidth: 300, flex: 1 },
23 |
24 | {
25 | field: "status",
26 | headerName: "Status",
27 | minWidth: 150,
28 | flex: 0.5,
29 | cellClassName: (params) => {
30 | return params.getValue(params.id, "status") === "Delivered"
31 | ? "greenColor"
32 | : "redColor";
33 | },
34 | },
35 | {
36 | field: "itemsQty",
37 | headerName: "Items Qty",
38 | type: "number",
39 | minWidth: 150,
40 | flex: 0.3,
41 | },
42 |
43 | {
44 | field: "amount",
45 | headerName: "Amount",
46 | type: "number",
47 | minWidth: 270,
48 | flex: 0.5,
49 | },
50 |
51 | {
52 | field: "actions",
53 | flex: 0.3,
54 | headerName: "Actions",
55 | minWidth: 150,
56 | type: "number",
57 | sortable: false,
58 | renderCell: (params) => {
59 | return (
60 |
61 |
62 |
63 | );
64 | },
65 | },
66 | ];
67 | const rows = [];
68 |
69 | orders &&
70 | orders.forEach((item, index) => {
71 | rows.push({
72 | itemsQty: item.orderItems.length,
73 | id: item._id,
74 | status: item.orderStatus,
75 | amount: item.totalPrice,
76 | });
77 | });
78 |
79 | useEffect(() => {
80 | if (error) {
81 | alert.error(error);
82 | dispatch(clearErrors());
83 | }
84 |
85 | dispatch(myOrders());
86 | }, [dispatch, alert, error]);
87 |
88 | return (
89 |
90 |
91 |
92 | {loading ? (
93 |
94 | ) : (
95 |
96 |
104 |
105 | {user.name}'s Orders
106 |
107 | )}
108 |
109 | );
110 | };
111 |
112 | export default MyOrders;
113 |
--------------------------------------------------------------------------------
/frontend/src/component/User/Profile.css:
--------------------------------------------------------------------------------
1 | .profileContainer {
2 | display: flex;
3 | height: 100vh;
4 | width: 100vw;
5 | position: fixed;
6 | top: 0%;
7 | left: 0%;
8 | max-width: 100%;
9 | background-color: white;
10 | }
11 |
12 | .profileContainer > div {
13 | display: flex;
14 | height: 100vh;
15 | width: 100vw;
16 | max-width: 100%;
17 | flex-direction: column;
18 | justify-content: center;
19 | align-items: center;
20 | }
21 |
22 | .profileContainer > div:first-child > h1 {
23 | color: rgba(0, 0, 0, 0.555);
24 | font: 500 2.2vmax "Roboto";
25 | transform: translateX(-10vmax) translateY(-2vmax);
26 | }
27 |
28 | .profileContainer > div:first-child > img {
29 | width: 20vmax;
30 | border-radius: 100%;
31 | transition: all 0.5s;
32 | }
33 |
34 | .profileContainer > div:first-child > img:hover {
35 | transform: scale(1.05);
36 | }
37 |
38 | .profileContainer > div:first-child > a {
39 | border: none;
40 | background-color: tomato;
41 | font: 400 1vmax "Roboto";
42 | color: white;
43 | text-decoration: none;
44 | padding: 0.5vmax;
45 | width: 30%;
46 | margin: 4vmax;
47 | text-align: center;
48 | transition: all 0.5s;
49 | }
50 |
51 | .profileContainer > div:first-child > a:hover {
52 | background-color: rgb(204, 78, 56);
53 | }
54 |
55 | .profileContainer > div:last-child {
56 | justify-content: space-evenly;
57 | align-items: flex-start;
58 | padding: 5vmax;
59 | box-sizing: border-box;
60 | }
61 |
62 | .profileContainer > div:last-child > div > h4 {
63 | color: black;
64 | font: 400 1.2vmax "Roboto";
65 | }
66 |
67 | .profileContainer > div:last-child > div > p {
68 | color: rgba(0, 0, 0, 0.418);
69 | font: 400 1vmax cursive;
70 | margin: 0.2vmax;
71 | }
72 |
73 | .profileContainer > div:last-child > div:last-child {
74 | display: flex;
75 | flex-direction: column;
76 | width: 60%;
77 | }
78 |
79 | .profileContainer > div:last-child > div:last-child > a {
80 | border: none;
81 | background-color: rgb(68, 68, 68);
82 | font: 400 1vmax "Roboto";
83 | color: white;
84 | text-decoration: none;
85 | padding: 0.5vmax;
86 | text-align: center;
87 | transition: all 0.5s;
88 | margin: 1vmax 0;
89 | }
90 |
91 | .profileContainer > div:last-child > div:last-child > a:hover {
92 | background-color: rgb(31, 31, 31);
93 | }
94 |
95 | @media screen and (max-width: 600px) {
96 | .profileContainer {
97 | flex-direction: column;
98 | }
99 |
100 | .profileContainer > div:first-child > h1 {
101 | font: 500 3.2vmax "Roboto";
102 | transform: translateY(-2vmax);
103 | }
104 |
105 | .profileContainer > div:first-child > a {
106 | font: 400 1.7vmax "Roboto";
107 | padding: 1vmax;
108 | }
109 |
110 | .profileContainer > div:last-child {
111 | text-align: center;
112 | align-items: center;
113 | }
114 |
115 | .profileContainer > div:last-child > div > h4 {
116 | font: 400 2.5vmax "Roboto";
117 | }
118 |
119 | .profileContainer > div:last-child > div > p {
120 | font: 400 2vmax cursive;
121 | margin: 0.5vmax 0;
122 | }
123 |
124 | .profileContainer > div:last-child > div:last-child > a {
125 | font: 400 1.8vmax "Roboto";
126 | padding: 1vmax;
127 | margin: 2vmax 0;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Sidebar from "./Sidebar.js";
3 | import "./dashboard.css";
4 | import { Typography } from "@material-ui/core";
5 | import { Link } from "react-router-dom";
6 | import { Doughnut, Line } from "react-chartjs-2";
7 | import { useSelector, useDispatch } from "react-redux";
8 | import { getAdminProduct } from "../../actions/productAction";
9 | import { getAllOrders } from "../../actions/orderAction.js";
10 | import { getAllUsers } from "../../actions/userAction.js";
11 | import MetaData from "../layout/MetaData";
12 |
13 | const Dashboard = () => {
14 | const dispatch = useDispatch();
15 |
16 | const { products } = useSelector((state) => state.products);
17 |
18 | const { orders } = useSelector((state) => state.allOrders);
19 |
20 | const { users } = useSelector((state) => state.allUsers);
21 |
22 | let outOfStock = 0;
23 |
24 | products &&
25 | products.forEach((item) => {
26 | if (item.Stock === 0) {
27 | outOfStock += 1;
28 | }
29 | });
30 |
31 | useEffect(() => {
32 | dispatch(getAdminProduct());
33 | dispatch(getAllOrders());
34 | dispatch(getAllUsers());
35 | }, [dispatch]);
36 |
37 | let totalAmount = 0;
38 | orders &&
39 | orders.forEach((item) => {
40 | totalAmount += item.totalPrice;
41 | });
42 |
43 | const lineState = {
44 | labels: ["Initial Amount", "Amount Earned"],
45 | datasets: [
46 | {
47 | label: "TOTAL AMOUNT",
48 | backgroundColor: ["tomato"],
49 | hoverBackgroundColor: ["rgb(197, 72, 49)"],
50 | data: [0, totalAmount],
51 | },
52 | ],
53 | };
54 |
55 | const doughnutState = {
56 | labels: ["Out of Stock", "InStock"],
57 | datasets: [
58 | {
59 | backgroundColor: ["#00A6B4", "#6800B4"],
60 | hoverBackgroundColor: ["#4B5000", "#35014F"],
61 | data: [outOfStock, products.length - outOfStock],
62 | },
63 | ],
64 | };
65 |
66 | return (
67 |
68 |
69 |
70 |
71 |
72 |
Dashboard
73 |
74 |
75 |
76 |
77 | Total Amount ₹{totalAmount}
78 |
79 |
80 |
81 |
82 |
Product
83 |
{products && products.length}
84 |
85 |
86 |
Orders
87 |
{orders && orders.length}
88 |
89 |
90 |
Users
91 |
{users && users.length}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | );
106 | };
107 |
108 | export default Dashboard;
109 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/newProduct.css:
--------------------------------------------------------------------------------
1 | .newProductContainer {
2 | width: 100%;
3 | box-sizing: border-box;
4 | background-color: rgb(221, 221, 221);
5 | border-left: 1px solid rgba(0, 0, 0, 0.158);
6 | display: flex;
7 | flex-direction: column;
8 | height: 100vh;
9 | }
10 | .newProductContainer h1 {
11 | color: rgba(0, 0, 0, 0.733);
12 | font: 300 2rem "Roboto";
13 | text-align: center;
14 | }
15 |
16 | .createProductForm {
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | margin: auto;
21 | padding: 3vmax;
22 | justify-content: space-evenly;
23 | height: 70%;
24 | width: 40vh;
25 | background-color: white;
26 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.267);
27 | }
28 |
29 | .createProductForm > div {
30 | display: flex;
31 | width: 100%;
32 | align-items: center;
33 | }
34 | .createProductForm > div > input,
35 | .createProductForm > div > select,
36 | .createProductForm > div > textarea {
37 | padding: 1vmax 4vmax;
38 | padding-right: 1vmax;
39 | width: 100%;
40 | box-sizing: border-box;
41 | border: 1px solid rgba(0, 0, 0, 0.267);
42 | border-radius: 4px;
43 | font: 300 0.9vmax cursive;
44 | outline: none;
45 | }
46 |
47 | .createProductForm > div > svg {
48 | position: absolute;
49 | transform: translateX(1vmax);
50 | font-size: 1.6vmax;
51 | color: rgba(0, 0, 0, 0.623);
52 | }
53 |
54 | #createProductFormFile > input {
55 | display: flex;
56 | padding: 0%;
57 | }
58 |
59 | #createProductFormFile > input::file-selector-button {
60 | cursor: pointer;
61 | width: 100%;
62 | z-index: 2;
63 | height: 5vh;
64 | border: none;
65 | margin: 0%;
66 | font: 400 0.8vmax cursive;
67 | transition: all 0.5s;
68 | padding: 0 1vmax;
69 | color: rgba(0, 0, 0, 0.623);
70 | background-color: rgb(255, 255, 255);
71 | }
72 |
73 | #createProductFormFile > input::file-selector-button:hover {
74 | background-color: rgb(235, 235, 235);
75 | }
76 |
77 | #createProductFormImage {
78 | width: 100%;
79 | overflow: auto;
80 | }
81 |
82 | #createProductFormImage > img {
83 | width: 3vmax;
84 | margin: 0 0.5vmax;
85 | }
86 | #createProductBtn {
87 | border: none;
88 | background-color: tomato;
89 | color: white;
90 | font: 300 0.9vmax "Roboto";
91 | width: 100%;
92 | padding: 0.8vmax;
93 | cursor: pointer;
94 | transition: all 0.5s;
95 | border-radius: 4px;
96 | outline: none;
97 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
98 | }
99 |
100 | #createProductBtn:hover {
101 | background-color: rgb(179, 66, 46);
102 | }
103 |
104 | @media screen and (max-width: 600px) {
105 | .newProductContainer {
106 | background-color: rgb(255, 255, 255);
107 | }
108 | .createProductForm {
109 | padding: 5vmax;
110 | }
111 |
112 | .createProductForm > div > input,
113 | .createProductForm > div > select,
114 | .createProductForm > div > textarea {
115 | padding: 2.5vmax 5vmax;
116 | font: 300 1.7vmax cursive;
117 | }
118 |
119 | .createProductForm > div > svg {
120 | font-size: 2.8vmax;
121 | }
122 |
123 | #createProductFormFile > img {
124 | width: 8vmax;
125 | border-radius: 100%;
126 | }
127 |
128 | #createProductFormFile > input::file-selector-button {
129 | height: 7vh;
130 | font: 400 1.8vmax cursive;
131 | }
132 |
133 | #createProductBtn {
134 | font: 300 1.9vmax "Roboto";
135 | padding: 1.8vmax;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/backend/controllers/orderController.js:
--------------------------------------------------------------------------------
1 | const Order = require("../models/orderModel");
2 | const Product = require("../models/productModel");
3 | const ErrorHander = require("../utils/errorhander");
4 | const catchAsyncErrors = require("../middleware/catchAsyncErrors");
5 |
6 | // Create new Order
7 | exports.newOrder = catchAsyncErrors(async (req, res, next) => {
8 | const {
9 | shippingInfo,
10 | orderItems,
11 | paymentInfo,
12 | itemsPrice,
13 | taxPrice,
14 | shippingPrice,
15 | totalPrice,
16 | } = req.body;
17 |
18 | const order = await Order.create({
19 | shippingInfo,
20 | orderItems,
21 | paymentInfo,
22 | itemsPrice,
23 | taxPrice,
24 | shippingPrice,
25 | totalPrice,
26 | paidAt: Date.now(),
27 | user: req.user._id,
28 | });
29 |
30 | res.status(201).json({
31 | success: true,
32 | order,
33 | });
34 | });
35 |
36 | // get Single Order
37 | exports.getSingleOrder = catchAsyncErrors(async (req, res, next) => {
38 | const order = await Order.findById(req.params.id).populate(
39 | "user",
40 | "name email"
41 | );
42 |
43 | if (!order) {
44 | return next(new ErrorHander("Order not found with this Id", 404));
45 | }
46 |
47 | res.status(200).json({
48 | success: true,
49 | order,
50 | });
51 | });
52 |
53 | // get logged in user Orders
54 | exports.myOrders = catchAsyncErrors(async (req, res, next) => {
55 | const orders = await Order.find({ user: req.user._id });
56 |
57 | res.status(200).json({
58 | success: true,
59 | orders,
60 | });
61 | });
62 |
63 | // get all Orders -- Admin
64 | exports.getAllOrders = catchAsyncErrors(async (req, res, next) => {
65 | const orders = await Order.find();
66 |
67 | let totalAmount = 0;
68 |
69 | orders.forEach((order) => {
70 | totalAmount += order.totalPrice;
71 | });
72 |
73 | res.status(200).json({
74 | success: true,
75 | totalAmount,
76 | orders,
77 | });
78 | });
79 |
80 | // update Order Status -- Admin
81 | exports.updateOrder = catchAsyncErrors(async (req, res, next) => {
82 | const order = await Order.findById(req.params.id);
83 |
84 | if (!order) {
85 | return next(new ErrorHander("Order not found with this Id", 404));
86 | }
87 |
88 | if (order.orderStatus === "Delivered") {
89 | return next(new ErrorHander("You have already delivered this order", 400));
90 | }
91 |
92 | if (req.body.status === "Shipped") {
93 | order.orderItems.forEach(async (o) => {
94 | await updateStock(o.product, o.quantity);
95 | });
96 | }
97 | order.orderStatus = req.body.status;
98 |
99 | if (req.body.status === "Delivered") {
100 | order.deliveredAt = Date.now();
101 | }
102 |
103 | await order.save({ validateBeforeSave: false });
104 | res.status(200).json({
105 | success: true,
106 | });
107 | });
108 |
109 | async function updateStock(id, quantity) {
110 | const product = await Product.findById(id);
111 |
112 | product.Stock -= quantity;
113 |
114 | await product.save({ validateBeforeSave: false });
115 | }
116 |
117 | // delete Order -- Admin
118 | exports.deleteOrder = catchAsyncErrors(async (req, res, next) => {
119 | const order = await Order.findById(req.params.id);
120 |
121 | if (!order) {
122 | return next(new ErrorHander("Order not found with this Id", 404));
123 | }
124 |
125 | await order.remove();
126 |
127 | res.status(200).json({
128 | success: true,
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/frontend/src/component/User/UpdateProfile.css:
--------------------------------------------------------------------------------
1 | .updateProfileContainer {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .updateProfileBox {
15 | background-color: white;
16 | width: 25vw;
17 | height: 70vh;
18 | box-sizing: border-box;
19 | overflow: hidden;
20 | }
21 |
22 | .updateProfileHeading {
23 | text-align: center;
24 | color: rgba(0, 0, 0, 0.664);
25 | font: 400 1.3vmax "Roboto";
26 | padding: 1.3vmax;
27 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
28 | width: 50%;
29 | margin: auto;
30 | }
31 |
32 | .updateProfileForm {
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | margin: auto;
37 | padding: 2vmax;
38 | justify-content: space-evenly;
39 | height: 70%;
40 | transition: all 0.5s;
41 | }
42 |
43 | .updateProfileForm > div {
44 | display: flex;
45 | width: 100%;
46 | align-items: center;
47 | }
48 |
49 | .updateProfileForm > div > input {
50 | padding: 1vmax 4vmax;
51 | padding-right: 1vmax;
52 | width: 100%;
53 | box-sizing: border-box;
54 | border: 1px solid rgba(0, 0, 0, 0.267);
55 | border-radius: 4px;
56 | font: 300 0.9vmax cursive;
57 | outline: none;
58 | }
59 |
60 | .updateProfileForm > div > svg {
61 | position: absolute;
62 | transform: translateX(1vmax);
63 | font-size: 1.6vmax;
64 | color: rgba(0, 0, 0, 0.623);
65 | }
66 |
67 | #updateProfileImage > img {
68 | width: 3vmax;
69 | border-radius: 100%;
70 | margin: 1vmax;
71 | }
72 | #updateProfileImage > input {
73 | display: flex;
74 | padding: 0%;
75 | }
76 |
77 | #updateProfileImage > input::file-selector-button {
78 | cursor: pointer;
79 | width: 100%;
80 | z-index: 2;
81 | height: 5vh;
82 | border: none;
83 | margin: 0%;
84 | font: 400 0.8vmax cursive;
85 | transition: all 0.5s;
86 | padding: 0 1vmax;
87 | color: rgba(0, 0, 0, 0.623);
88 | background-color: rgb(255, 255, 255);
89 | }
90 |
91 | #updateProfileImage > input::file-selector-button:hover {
92 | background-color: rgb(235, 235, 235);
93 | }
94 |
95 | .updateProfileBtn {
96 | border: none;
97 | background-color: tomato;
98 | color: white;
99 | font: 300 0.9vmax "Roboto";
100 | width: 100%;
101 | padding: 0.8vmax;
102 | cursor: pointer;
103 | transition: all 0.5s;
104 | border-radius: 4px;
105 | outline: none;
106 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
107 | }
108 |
109 | .updateProfileBtn:hover {
110 | background-color: rgb(179, 66, 46);
111 | }
112 |
113 | @media screen and (max-width: 600px) {
114 | .updateProfileContainer {
115 | background-color: white;
116 | }
117 | .updateProfileBox {
118 | width: 100vw;
119 | height: 95vh;
120 | }
121 |
122 | .updateProfileForm {
123 | padding: 5vmax;
124 | }
125 |
126 | .updateProfileForm > div > input {
127 | padding: 2.5vmax 5vmax;
128 | font: 300 1.7vmax cursive;
129 | }
130 |
131 | .updateProfileForm > div > svg {
132 | font-size: 2.8vmax;
133 | }
134 |
135 | #updateProfileImage > img {
136 | width: 8vmax;
137 | border-radius: 100%;
138 | }
139 |
140 | #updateProfileImage > input::file-selector-button {
141 | height: 7vh;
142 | font: 400 1.8vmax cursive;
143 | }
144 |
145 | .updateProfileBtn {
146 | font: 300 1.9vmax "Roboto";
147 | padding: 1.8vmax;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/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 | ### `npm 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 | ### `npm 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 | ### `npm run 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 | ### `npm run 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 | ### `npm run 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/component/Cart/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import "./Cart.css";
3 | import CartItemCard from "./CartItemCard";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { addItemsToCart, removeItemsFromCart } from "../../actions/cartAction";
6 | import { Typography } from "@material-ui/core";
7 | import RemoveShoppingCartIcon from "@material-ui/icons/RemoveShoppingCart";
8 | import { Link } from "react-router-dom";
9 |
10 | const Cart = ({ history }) => {
11 | const dispatch = useDispatch();
12 | const { cartItems } = useSelector((state) => state.cart);
13 |
14 | const increaseQuantity = (id, quantity, stock) => {
15 | const newQty = quantity + 1;
16 | if (stock <= quantity) {
17 | return;
18 | }
19 | dispatch(addItemsToCart(id, newQty));
20 | };
21 |
22 | const decreaseQuantity = (id, quantity) => {
23 | const newQty = quantity - 1;
24 | if (1 >= quantity) {
25 | return;
26 | }
27 | dispatch(addItemsToCart(id, newQty));
28 | };
29 |
30 | const deleteCartItems = (id) => {
31 | dispatch(removeItemsFromCart(id));
32 | };
33 |
34 | const checkoutHandler = () => {
35 | history.push("/login?redirect=shipping");
36 | };
37 |
38 | return (
39 |
40 | {cartItems.length === 0 ? (
41 |
42 |
43 |
44 | No Product in Your Cart
45 | View Products
46 |
47 | ) : (
48 |
49 |
50 |
51 |
Product
52 |
Quantity
53 |
Subtotal
54 |
55 |
56 | {cartItems &&
57 | cartItems.map((item) => (
58 |
59 |
60 |
61 |
63 | decreaseQuantity(item.product, item.quantity)
64 | }
65 | >
66 | -
67 |
68 |
69 |
71 | increaseQuantity(
72 | item.product,
73 | item.quantity,
74 | item.stock
75 | )
76 | }
77 | >
78 | +
79 |
80 |
81 |
{`₹${
82 | item.price * item.quantity
83 | }`}
84 |
85 | ))}
86 |
87 |
88 |
89 |
90 |
Gross Total
91 |
{`₹${cartItems.reduce(
92 | (acc, item) => acc + item.quantity * item.price,
93 | 0
94 | )}`}
95 |
96 |
97 |
98 | Check Out
99 |
100 |
101 |
102 |
103 | )}
104 |
105 | );
106 | };
107 |
108 | export default Cart;
109 |
--------------------------------------------------------------------------------
/frontend/src/actions/orderAction.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_ORDER_REQUEST,
3 | CREATE_ORDER_SUCCESS,
4 | CREATE_ORDER_FAIL,
5 | MY_ORDERS_REQUEST,
6 | MY_ORDERS_SUCCESS,
7 | MY_ORDERS_FAIL,
8 | ALL_ORDERS_REQUEST,
9 | ALL_ORDERS_SUCCESS,
10 | ALL_ORDERS_FAIL,
11 | UPDATE_ORDER_REQUEST,
12 | UPDATE_ORDER_SUCCESS,
13 | UPDATE_ORDER_FAIL,
14 | DELETE_ORDER_REQUEST,
15 | DELETE_ORDER_SUCCESS,
16 | DELETE_ORDER_FAIL,
17 | ORDER_DETAILS_REQUEST,
18 | ORDER_DETAILS_SUCCESS,
19 | ORDER_DETAILS_FAIL,
20 | CLEAR_ERRORS,
21 | } from "../constants/orderConstants";
22 |
23 | import axios from "axios";
24 |
25 | // Create Order
26 | export const createOrder = (order) => async (dispatch) => {
27 | try {
28 | dispatch({ type: CREATE_ORDER_REQUEST });
29 |
30 | const config = {
31 | headers: {
32 | "Content-Type": "application/json",
33 | },
34 | };
35 | const { data } = await axios.post("/api/v1/order/new", order, config);
36 |
37 | dispatch({ type: CREATE_ORDER_SUCCESS, payload: data });
38 | } catch (error) {
39 | dispatch({
40 | type: CREATE_ORDER_FAIL,
41 | payload: error.response.data.message,
42 | });
43 | }
44 | };
45 |
46 | // My Orders
47 | export const myOrders = () => async (dispatch) => {
48 | try {
49 | dispatch({ type: MY_ORDERS_REQUEST });
50 |
51 | const { data } = await axios.get("/api/v1/orders/me");
52 |
53 | dispatch({ type: MY_ORDERS_SUCCESS, payload: data.orders });
54 | } catch (error) {
55 | dispatch({
56 | type: MY_ORDERS_FAIL,
57 | payload: error.response.data.message,
58 | });
59 | }
60 | };
61 |
62 | // Get All Orders (admin)
63 | export const getAllOrders = () => async (dispatch) => {
64 | try {
65 | dispatch({ type: ALL_ORDERS_REQUEST });
66 |
67 | const { data } = await axios.get("/api/v1/admin/orders");
68 |
69 | dispatch({ type: ALL_ORDERS_SUCCESS, payload: data.orders });
70 | } catch (error) {
71 | dispatch({
72 | type: ALL_ORDERS_FAIL,
73 | payload: error.response.data.message,
74 | });
75 | }
76 | };
77 |
78 | // Update Order
79 | export const updateOrder = (id, order) => async (dispatch) => {
80 | try {
81 | dispatch({ type: UPDATE_ORDER_REQUEST });
82 |
83 | const config = {
84 | headers: {
85 | "Content-Type": "application/json",
86 | },
87 | };
88 | const { data } = await axios.put(
89 | `/api/v1/admin/order/${id}`,
90 | order,
91 | config
92 | );
93 |
94 | dispatch({ type: UPDATE_ORDER_SUCCESS, payload: data.success });
95 | } catch (error) {
96 | dispatch({
97 | type: UPDATE_ORDER_FAIL,
98 | payload: error.response.data.message,
99 | });
100 | }
101 | };
102 |
103 | // Delete Order
104 | export const deleteOrder = (id) => async (dispatch) => {
105 | try {
106 | dispatch({ type: DELETE_ORDER_REQUEST });
107 |
108 | const { data } = await axios.delete(`/api/v1/admin/order/${id}`);
109 |
110 | dispatch({ type: DELETE_ORDER_SUCCESS, payload: data.success });
111 | } catch (error) {
112 | dispatch({
113 | type: DELETE_ORDER_FAIL,
114 | payload: error.response.data.message,
115 | });
116 | }
117 | };
118 |
119 | // Get Order Details
120 | export const getOrderDetails = (id) => async (dispatch) => {
121 | try {
122 | dispatch({ type: ORDER_DETAILS_REQUEST });
123 |
124 | const { data } = await axios.get(`/api/v1/order/${id}`);
125 |
126 | dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data.order });
127 | } catch (error) {
128 | dispatch({
129 | type: ORDER_DETAILS_FAIL,
130 | payload: error.response.data.message,
131 | });
132 | }
133 | };
134 |
135 | // Clearing Errors
136 | export const clearErrors = () => async (dispatch) => {
137 | dispatch({ type: CLEAR_ERRORS });
138 | };
139 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/Cart.css:
--------------------------------------------------------------------------------
1 | .emptyCart {
2 | margin: auto;
3 | text-align: center;
4 | padding: 10vmax;
5 | height: 50vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | .emptyCart > svg {
12 | font-size: 5vmax;
13 | color: tomato;
14 | }
15 | .emptyCart > p {
16 | font-size: 2vmax;
17 | }
18 | .emptyCart > a {
19 | background-color: rgb(51, 51, 51);
20 | color: white;
21 | border: none;
22 | padding: 1vmax 3vmax;
23 | cursor: pointer;
24 | font: 400 1vmax "Roboto";
25 | text-decoration: none;
26 | }
27 |
28 | .cartPage {
29 | padding: 5vmax;
30 | }
31 |
32 | .cartHeader {
33 | background-color: tomato;
34 | width: 90%;
35 | box-sizing: border-box;
36 | margin: auto;
37 | color: white;
38 | display: grid;
39 | grid-template-columns: 4fr 1fr 1fr;
40 | font: 300 0.7vmax "Roboto";
41 | }
42 | .cartHeader > p {
43 | margin: 10px;
44 | }
45 | .cartHeader > p:last-child {
46 | text-align: end;
47 | }
48 |
49 | .cartContainer {
50 | width: 90%;
51 | margin: auto;
52 | display: grid;
53 | grid-template-columns: 4fr 1fr 1fr;
54 | }
55 |
56 | .cartInput {
57 | display: flex;
58 | align-items: center;
59 | height: 8vmax;
60 | }
61 |
62 | .cartInput > button {
63 | border: none;
64 | background-color: rgba(0, 0, 0, 0.616);
65 | padding: 0.5vmax;
66 | cursor: pointer;
67 | color: white;
68 | transition: all 0.5s;
69 | }
70 | .cartInput > button:hover {
71 | background-color: rgba(0, 0, 0, 0.767);
72 | }
73 |
74 | .cartInput > input {
75 | border: none;
76 | padding: 0.5vmax;
77 | width: 1vmax;
78 | text-align: center;
79 | outline: none;
80 | font: 400 0.8vmax "Roboto";
81 | color: rgba(0, 0, 0, 0.74);
82 | }
83 |
84 | .cartSubtotal {
85 | display: flex;
86 | padding: 0.5vmax;
87 | height: 8vmax;
88 | align-items: center;
89 | box-sizing: border-box;
90 | font: 300 1vmax cursive;
91 | justify-content: flex-end;
92 | color: rgba(0, 0, 0, 0.753);
93 | }
94 |
95 | .cartGrossProfit {
96 | display: grid;
97 | grid-template-columns: 2fr 1.2fr;
98 | }
99 |
100 | .cartGrossProfitBox {
101 | border-top: 3px solid tomato;
102 | margin: 1vmax 4vmax;
103 | box-sizing: border-box;
104 | padding: 2vmax 0;
105 | font: 300 1vmax "Roboto";
106 | display: flex;
107 | justify-content: space-between;
108 | }
109 |
110 | .checkOutBtn {
111 | display: flex;
112 | justify-content: flex-end;
113 | }
114 | .checkOutBtn > button {
115 | background-color: tomato;
116 | color: white;
117 | border: none;
118 | padding: 0.8vmax 4vmax;
119 | width: 50%;
120 | font: 300 0.8vmax "Roboto";
121 | margin: 1vmax 4vmax;
122 | cursor: pointer;
123 | border-radius: 30px;
124 | }
125 |
126 | @media screen and (max-width: 600px) {
127 | .cartPage {
128 | padding: 0;
129 | min-height: 60vh;
130 | }
131 |
132 | .cartHeader {
133 | width: 100%;
134 | font: 300 1.7vmax "Roboto";
135 | grid-template-columns: 3fr 1fr 1fr;
136 | }
137 |
138 | .cartContainer {
139 | width: 100%;
140 | grid-template-columns: 3fr 1fr 1fr;
141 | }
142 |
143 | .cartInput {
144 | height: 20vmax;
145 | }
146 |
147 | .cartInput > button {
148 | padding: 1.5vmax;
149 | }
150 |
151 | .cartInput > input {
152 | width: 2vmax;
153 | padding: 1.5vmax;
154 | font: 400 1.8vmax "Roboto";
155 | }
156 |
157 | .cartSubtotal {
158 | padding: 1.5vmax;
159 | height: 20vmax;
160 | font: 300 2vmax "Roboto";
161 | }
162 |
163 | .cartGrossProfit {
164 | display: grid;
165 | grid-template-columns: 0fr 2fr;
166 | }
167 |
168 | .cartGrossProfitBox {
169 | padding: 2vmax;
170 | font: 300 2vmax "Roboto";
171 | }
172 |
173 | .checkOutBtn > button {
174 | padding: 2vmax 4vmax;
175 | width: 100%;
176 | font: 300 2vmax "Roboto";
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/ConfirmOrder.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import CheckoutSteps from "../Cart/CheckoutSteps";
3 | import { useSelector } from "react-redux";
4 | import MetaData from "../layout/MetaData";
5 | import "./ConfirmOrder.css";
6 | import { Link } from "react-router-dom";
7 | import { Typography } from "@material-ui/core";
8 |
9 | const ConfirmOrder = ({ history }) => {
10 | const { shippingInfo, cartItems } = useSelector((state) => state.cart);
11 | const { user } = useSelector((state) => state.user);
12 |
13 | const subtotal = cartItems.reduce(
14 | (acc, item) => acc + item.quantity * item.price,
15 | 0
16 | );
17 |
18 | const shippingCharges = subtotal > 1000 ? 0 : 200;
19 |
20 | const tax = subtotal * 0.18;
21 |
22 | const totalPrice = subtotal + tax + shippingCharges;
23 |
24 | const address = `${shippingInfo.address}, ${shippingInfo.city}, ${shippingInfo.state}, ${shippingInfo.pinCode}, ${shippingInfo.country}`;
25 |
26 | const proceedToPayment = () => {
27 | const data = {
28 | subtotal,
29 | shippingCharges,
30 | tax,
31 | totalPrice,
32 | };
33 |
34 | sessionStorage.setItem("orderInfo", JSON.stringify(data));
35 |
36 | history.push("/process/payment");
37 | };
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
Shipping Info
47 |
48 |
49 |
Name:
50 |
{user.name}
51 |
52 |
53 |
Phone:
54 |
{shippingInfo.phoneNo}
55 |
56 |
57 |
Address:
58 |
{address}
59 |
60 |
61 |
62 |
63 |
Your Cart Items:
64 |
65 | {cartItems &&
66 | cartItems.map((item) => (
67 |
68 |
69 |
70 | {item.name}
71 | {" "}
72 |
73 | {item.quantity} X ₹{item.price} ={" "}
74 | ₹{item.price * item.quantity}
75 |
76 |
77 | ))}
78 |
79 |
80 |
81 | {/* */}
82 |
83 |
84 |
Order Summery
85 |
86 |
87 |
Subtotal:
88 |
₹{subtotal}
89 |
90 |
91 |
Shipping Charges:
92 |
₹{shippingCharges}
93 |
94 |
95 |
GST:
96 |
₹{tax}
97 |
98 |
99 |
100 |
101 |
102 | Total:
103 |
104 |
₹{totalPrice}
105 |
106 |
107 |
Proceed To Payment
108 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | export default ConfirmOrder;
116 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/ProductList.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { DataGrid } from "@material-ui/data-grid";
3 | import "./productList.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import {
6 | clearErrors,
7 | getAdminProduct,
8 | deleteProduct,
9 | } from "../../actions/productAction";
10 | import { Link } from "react-router-dom";
11 | import { useAlert } from "react-alert";
12 | import { Button } from "@material-ui/core";
13 | import MetaData from "../layout/MetaData";
14 | import EditIcon from "@material-ui/icons/Edit";
15 | import DeleteIcon from "@material-ui/icons/Delete";
16 | import SideBar from "./Sidebar";
17 | import { DELETE_PRODUCT_RESET } from "../../constants/productConstants";
18 |
19 | const ProductList = ({ history }) => {
20 | const dispatch = useDispatch();
21 |
22 | const alert = useAlert();
23 |
24 | const { error, products } = useSelector((state) => state.products);
25 |
26 | const { error: deleteError, isDeleted } = useSelector(
27 | (state) => state.product
28 | );
29 |
30 | const deleteProductHandler = (id) => {
31 | dispatch(deleteProduct(id));
32 | };
33 |
34 | useEffect(() => {
35 | if (error) {
36 | alert.error(error);
37 | dispatch(clearErrors());
38 | }
39 |
40 | if (deleteError) {
41 | alert.error(deleteError);
42 | dispatch(clearErrors());
43 | }
44 |
45 | if (isDeleted) {
46 | alert.success("Product Deleted Successfully");
47 | history.push("/admin/dashboard");
48 | dispatch({ type: DELETE_PRODUCT_RESET });
49 | }
50 |
51 | dispatch(getAdminProduct());
52 | }, [dispatch, alert, error, deleteError, history, isDeleted]);
53 |
54 | const columns = [
55 | { field: "id", headerName: "Product ID", minWidth: 200, flex: 0.5 },
56 |
57 | {
58 | field: "name",
59 | headerName: "Name",
60 | minWidth: 350,
61 | flex: 1,
62 | },
63 | {
64 | field: "stock",
65 | headerName: "Stock",
66 | type: "number",
67 | minWidth: 150,
68 | flex: 0.3,
69 | },
70 |
71 | {
72 | field: "price",
73 | headerName: "Price",
74 | type: "number",
75 | minWidth: 270,
76 | flex: 0.5,
77 | },
78 |
79 | {
80 | field: "actions",
81 | flex: 0.3,
82 | headerName: "Actions",
83 | minWidth: 150,
84 | type: "number",
85 | sortable: false,
86 | renderCell: (params) => {
87 | return (
88 |
89 |
90 |
91 |
92 |
93 |
95 | deleteProductHandler(params.getValue(params.id, "id"))
96 | }
97 | >
98 |
99 |
100 |
101 | );
102 | },
103 | },
104 | ];
105 |
106 | const rows = [];
107 |
108 | products &&
109 | products.forEach((item) => {
110 | rows.push({
111 | id: item._id,
112 | stock: item.Stock,
113 | price: item.price,
114 | name: item.name,
115 | });
116 | });
117 |
118 | return (
119 |
120 |
121 |
122 |
123 |
124 |
125 |
ALL PRODUCTS
126 |
127 |
135 |
136 |
137 |
138 | );
139 | };
140 |
141 | export default ProductList;
142 |
--------------------------------------------------------------------------------
/frontend/src/component/User/UpdatePassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from "react";
2 | import "./UpdatePassword.css";
3 | import Loader from "../layout/Loader/Loader";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import { clearErrors, updatePassword } from "../../actions/userAction";
6 | import { useAlert } from "react-alert";
7 | import { UPDATE_PASSWORD_RESET } from "../../constants/userConstants";
8 | import MetaData from "../layout/MetaData";
9 | import LockOpenIcon from "@material-ui/icons/LockOpen";
10 | import LockIcon from "@material-ui/icons/Lock";
11 | import VpnKeyIcon from "@material-ui/icons/VpnKey";
12 |
13 | const UpdatePassword = ({ history }) => {
14 | const dispatch = useDispatch();
15 | const alert = useAlert();
16 |
17 | const { error, isUpdated, loading } = useSelector((state) => state.profile);
18 |
19 | const [oldPassword, setOldPassword] = useState("");
20 | const [newPassword, setNewPassword] = useState("");
21 | const [confirmPassword, setConfirmPassword] = useState("");
22 |
23 | const updatePasswordSubmit = (e) => {
24 | e.preventDefault();
25 |
26 | const myForm = new FormData();
27 |
28 | myForm.set("oldPassword", oldPassword);
29 | myForm.set("newPassword", newPassword);
30 | myForm.set("confirmPassword", confirmPassword);
31 |
32 | dispatch(updatePassword(myForm));
33 | };
34 |
35 | useEffect(() => {
36 | if (error) {
37 | alert.error(error);
38 | dispatch(clearErrors());
39 | }
40 |
41 | if (isUpdated) {
42 | alert.success("Profile Updated Successfully");
43 |
44 | history.push("/account");
45 |
46 | dispatch({
47 | type: UPDATE_PASSWORD_RESET,
48 | });
49 | }
50 | }, [dispatch, error, alert, history, isUpdated]);
51 |
52 | return (
53 |
54 | {loading ? (
55 |
56 | ) : (
57 |
58 |
59 |
60 |
61 |
Update Profile
62 |
63 |
104 |
105 |
106 |
107 | )}
108 |
109 | );
110 | };
111 |
112 | export default UpdatePassword;
113 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/UsersList.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { DataGrid } from "@material-ui/data-grid";
3 | import "./productList.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { Link } from "react-router-dom";
6 | import { useAlert } from "react-alert";
7 | import { Button } from "@material-ui/core";
8 | import MetaData from "../layout/MetaData";
9 | import EditIcon from "@material-ui/icons/Edit";
10 | import DeleteIcon from "@material-ui/icons/Delete";
11 | import SideBar from "./Sidebar";
12 | import { getAllUsers, clearErrors, deleteUser } from "../../actions/userAction";
13 | import { DELETE_USER_RESET } from "../../constants/userConstants";
14 |
15 | const UsersList = ({ history }) => {
16 | const dispatch = useDispatch();
17 |
18 | const alert = useAlert();
19 |
20 | const { error, users } = useSelector((state) => state.allUsers);
21 |
22 | const {
23 | error: deleteError,
24 | isDeleted,
25 | message,
26 | } = useSelector((state) => state.profile);
27 |
28 | const deleteUserHandler = (id) => {
29 | dispatch(deleteUser(id));
30 | };
31 |
32 | useEffect(() => {
33 | if (error) {
34 | alert.error(error);
35 | dispatch(clearErrors());
36 | }
37 |
38 | if (deleteError) {
39 | alert.error(deleteError);
40 | dispatch(clearErrors());
41 | }
42 |
43 | if (isDeleted) {
44 | alert.success(message);
45 | history.push("/admin/users");
46 | dispatch({ type: DELETE_USER_RESET });
47 | }
48 |
49 | dispatch(getAllUsers());
50 | }, [dispatch, alert, error, deleteError, history, isDeleted, message]);
51 |
52 | const columns = [
53 | { field: "id", headerName: "User ID", minWidth: 180, flex: 0.8 },
54 |
55 | {
56 | field: "email",
57 | headerName: "Email",
58 | minWidth: 200,
59 | flex: 1,
60 | },
61 | {
62 | field: "name",
63 | headerName: "Name",
64 | minWidth: 150,
65 | flex: 0.5,
66 | },
67 |
68 | {
69 | field: "role",
70 | headerName: "Role",
71 | type: "number",
72 | minWidth: 150,
73 | flex: 0.3,
74 | cellClassName: (params) => {
75 | return params.getValue(params.id, "role") === "admin"
76 | ? "greenColor"
77 | : "redColor";
78 | },
79 | },
80 |
81 | {
82 | field: "actions",
83 | flex: 0.3,
84 | headerName: "Actions",
85 | minWidth: 150,
86 | type: "number",
87 | sortable: false,
88 | renderCell: (params) => {
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
97 | deleteUserHandler(params.getValue(params.id, "id"))
98 | }
99 | >
100 |
101 |
102 |
103 | );
104 | },
105 | },
106 | ];
107 |
108 | const rows = [];
109 |
110 | users &&
111 | users.forEach((item) => {
112 | rows.push({
113 | id: item._id,
114 | role: item.role,
115 | email: item.email,
116 | name: item.name,
117 | });
118 | });
119 |
120 | return (
121 |
122 |
123 |
124 |
125 |
126 |
127 |
ALL USERS
128 |
129 |
137 |
138 |
139 |
140 | );
141 | };
142 |
143 | export default UsersList;
144 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/OrderList.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { DataGrid } from "@material-ui/data-grid";
3 | import "./productList.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { Link } from "react-router-dom";
6 | import { useAlert } from "react-alert";
7 | import { Button } from "@material-ui/core";
8 | import MetaData from "../layout/MetaData";
9 | import EditIcon from "@material-ui/icons/Edit";
10 | import DeleteIcon from "@material-ui/icons/Delete";
11 | import SideBar from "./Sidebar";
12 | import {
13 | deleteOrder,
14 | getAllOrders,
15 | clearErrors,
16 | } from "../../actions/orderAction";
17 | import { DELETE_ORDER_RESET } from "../../constants/orderConstants";
18 |
19 | const OrderList = ({ history }) => {
20 | const dispatch = useDispatch();
21 |
22 | const alert = useAlert();
23 |
24 | const { error, orders } = useSelector((state) => state.allOrders);
25 |
26 | const { error: deleteError, isDeleted } = useSelector((state) => state.order);
27 |
28 | const deleteOrderHandler = (id) => {
29 | dispatch(deleteOrder(id));
30 | };
31 |
32 | useEffect(() => {
33 | if (error) {
34 | alert.error(error);
35 | dispatch(clearErrors());
36 | }
37 |
38 | if (deleteError) {
39 | alert.error(deleteError);
40 | dispatch(clearErrors());
41 | }
42 |
43 | if (isDeleted) {
44 | alert.success("Order Deleted Successfully");
45 | history.push("/admin/orders");
46 | dispatch({ type: DELETE_ORDER_RESET });
47 | }
48 |
49 | dispatch(getAllOrders());
50 | }, [dispatch, alert, error, deleteError, history, isDeleted]);
51 |
52 | const columns = [
53 | { field: "id", headerName: "Order ID", minWidth: 300, flex: 1 },
54 |
55 | {
56 | field: "status",
57 | headerName: "Status",
58 | minWidth: 150,
59 | flex: 0.5,
60 | cellClassName: (params) => {
61 | return params.getValue(params.id, "status") === "Delivered"
62 | ? "greenColor"
63 | : "redColor";
64 | },
65 | },
66 | {
67 | field: "itemsQty",
68 | headerName: "Items Qty",
69 | type: "number",
70 | minWidth: 150,
71 | flex: 0.4,
72 | },
73 |
74 | {
75 | field: "amount",
76 | headerName: "Amount",
77 | type: "number",
78 | minWidth: 270,
79 | flex: 0.5,
80 | },
81 |
82 | {
83 | field: "actions",
84 | flex: 0.3,
85 | headerName: "Actions",
86 | minWidth: 150,
87 | type: "number",
88 | sortable: false,
89 | renderCell: (params) => {
90 | return (
91 |
92 |
93 |
94 |
95 |
96 |
98 | deleteOrderHandler(params.getValue(params.id, "id"))
99 | }
100 | >
101 |
102 |
103 |
104 | );
105 | },
106 | },
107 | ];
108 |
109 | const rows = [];
110 |
111 | orders &&
112 | orders.forEach((item) => {
113 | rows.push({
114 | id: item._id,
115 | itemsQty: item.orderItems.length,
116 | amount: item.totalPrice,
117 | status: item.orderStatus,
118 | });
119 | });
120 |
121 | return (
122 |
123 |
124 |
125 |
126 |
127 |
128 |
ALL ORDERS
129 |
130 |
138 |
139 |
140 |
141 | );
142 | };
143 |
144 | export default OrderList;
145 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/UpdateUser.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { useAlert } from "react-alert";
4 | import { Button } from "@material-ui/core";
5 | import MetaData from "../layout/MetaData";
6 | import MailOutlineIcon from "@material-ui/icons/MailOutline";
7 | import PersonIcon from "@material-ui/icons/Person";
8 | import VerifiedUserIcon from "@material-ui/icons/VerifiedUser";
9 | import SideBar from "./Sidebar";
10 | import { UPDATE_USER_RESET } from "../../constants/userConstants";
11 | import {
12 | getUserDetails,
13 | updateUser,
14 | clearErrors,
15 | } from "../../actions/userAction";
16 | import Loader from "../layout/Loader/Loader";
17 |
18 | const UpdateUser = ({ history, match }) => {
19 | const dispatch = useDispatch();
20 | const alert = useAlert();
21 |
22 | const { loading, error, user } = useSelector((state) => state.userDetails);
23 |
24 | const {
25 | loading: updateLoading,
26 | error: updateError,
27 | isUpdated,
28 | } = useSelector((state) => state.profile);
29 |
30 | const [name, setName] = useState("");
31 | const [email, setEmail] = useState("");
32 | const [role, setRole] = useState("");
33 |
34 | const userId = match.params.id;
35 |
36 | useEffect(() => {
37 | if (user && user._id !== userId) {
38 | dispatch(getUserDetails(userId));
39 | } else {
40 | setName(user.name);
41 | setEmail(user.email);
42 | setRole(user.role);
43 | }
44 | if (error) {
45 | alert.error(error);
46 | dispatch(clearErrors());
47 | }
48 |
49 | if (updateError) {
50 | alert.error(updateError);
51 | dispatch(clearErrors());
52 | }
53 |
54 | if (isUpdated) {
55 | alert.success("User Updated Successfully");
56 | history.push("/admin/users");
57 | dispatch({ type: UPDATE_USER_RESET });
58 | }
59 | }, [dispatch, alert, error, history, isUpdated, updateError, user, userId]);
60 |
61 | const updateUserSubmitHandler = (e) => {
62 | e.preventDefault();
63 |
64 | const myForm = new FormData();
65 |
66 | myForm.set("name", name);
67 | myForm.set("email", email);
68 | myForm.set("role", role);
69 |
70 | dispatch(updateUser(userId, myForm));
71 | };
72 |
73 | return (
74 |
75 |
76 |
77 |
78 |
79 | {loading ? (
80 |
81 | ) : (
82 |
128 | )}
129 |
130 |
131 |
132 | );
133 | };
134 |
135 | export default UpdateUser;
136 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/ConfirmOrder.css:
--------------------------------------------------------------------------------
1 | .confirmOrderPage {
2 | height: 100vh;
3 | background-color: white;
4 | display: grid;
5 | grid-template-columns: 6fr 3fr;
6 | }
7 |
8 | .confirmOrderPage > div:last-child {
9 | border-left: 1px solid rgba(0, 0, 0, 0.247);
10 | }
11 |
12 | .confirmshippingArea {
13 | padding: 5vmax;
14 | padding-bottom: 0%;
15 | }
16 |
17 | .confirmshippingArea > p {
18 | font: 400 1.8vmax "Roboto";
19 | }
20 |
21 | .confirmshippingAreaBox,
22 | .confirmCartItemsContainer {
23 | margin: 2vmax;
24 | }
25 |
26 | .confirmshippingAreaBox > div {
27 | display: flex;
28 | margin: 1vmax 0;
29 | }
30 |
31 | .confirmshippingAreaBox > div > p {
32 | font: 400 1vmax "Roboto";
33 | color: black;
34 | }
35 | .confirmshippingAreaBox > div > span {
36 | margin: 0 1vmax;
37 | font: 100 1vmax "Roboto";
38 | color: #575757;
39 | }
40 |
41 | .confirmCartItems > p {
42 | font: 400 1.8vmax "Roboto";
43 | }
44 |
45 | .confirmCartItems {
46 | padding: 5vmax;
47 | padding-top: 2vmax;
48 | }
49 |
50 | .confirmCartItemsContainer {
51 | max-height: 20vmax;
52 | overflow-y: auto;
53 | }
54 |
55 | .confirmCartItemsContainer > div {
56 | display: flex;
57 | font: 400 1vmax "Roboto";
58 | align-items: center;
59 | justify-content: space-between;
60 | margin: 2vmax 0;
61 | }
62 |
63 | .confirmCartItemsContainer > div > img {
64 | width: 3vmax;
65 | }
66 |
67 | .confirmCartItemsContainer > div > a {
68 | color: #575757;
69 | margin: 0 2vmax;
70 | width: 60%;
71 | text-decoration: none;
72 | }
73 |
74 | .confirmCartItemsContainer > div > span {
75 | font: 100 1vmax "Roboto";
76 | color: #5e5e5e;
77 | }
78 |
79 | .orderSummary {
80 | padding: 7vmax;
81 | }
82 |
83 | .orderSummary > p {
84 | text-align: center;
85 | font: 400 1.8vmax "Roboto";
86 | border-bottom: 1px solid rgba(0, 0, 0, 0.267);
87 | padding: 1vmax;
88 | width: 100%;
89 | margin: auto;
90 | box-sizing: border-box;
91 | }
92 |
93 | .orderSummary > div > div {
94 | display: flex;
95 | font: 300 1vmax "Roboto";
96 | justify-content: space-between;
97 | margin: 2vmax 0;
98 | }
99 | .orderSummary > div > div > span {
100 | color: rgba(0, 0, 0, 0.692);
101 | }
102 |
103 | .orderSummaryTotal {
104 | display: flex;
105 | font: 300 1vmax "Roboto";
106 | justify-content: space-between;
107 | border-top: 1px solid rgba(0, 0, 0, 0.363);
108 | padding: 2vmax 0;
109 | }
110 |
111 | .orderSummary > button {
112 | background-color: tomato;
113 | color: white;
114 | width: 100%;
115 | padding: 1vmax;
116 | border: none;
117 | margin: auto;
118 | cursor: pointer;
119 | transition: 0.5s;
120 | font: 400 1vmax "Roboto";
121 | }
122 |
123 | .orderSummary > button:hover {
124 | background-color: rgb(192, 71, 50);
125 | }
126 |
127 | @media screen and (max-width: 600px) {
128 | .confirmOrderPage {
129 | grid-template-columns: 1fr;
130 | height: unset;
131 | }
132 |
133 | .confirmOrderPage > div:last-child {
134 | border-left: 0;
135 | border-top: 1px solid rgba(0, 0, 0, 0.247);
136 | }
137 |
138 | .confirmshippingArea > p {
139 | font: 400 6vw "Roboto";
140 | }
141 |
142 | .confirmshippingAreaBox > div {
143 | display: flex;
144 | margin: 6vw 0;
145 | }
146 |
147 | .confirmshippingAreaBox > div > p {
148 | font: 400 4vw "Roboto";
149 | }
150 | .confirmshippingAreaBox > div > span {
151 | font: 100 4vw "Roboto";
152 | }
153 |
154 | .confirmCartItems > p {
155 | font: 400 6vw "Roboto";
156 | }
157 |
158 | .confirmCartItemsContainer {
159 | max-height: 50vw;
160 | }
161 |
162 | .confirmCartItemsContainer > div {
163 | font: 400 4vw "Roboto";
164 | margin: 4vw 0;
165 | }
166 |
167 | .confirmCartItemsContainer > div > img {
168 | width: 10vw;
169 | }
170 |
171 | .confirmCartItemsContainer > div > a {
172 | margin: 0;
173 | width: 30%;
174 | }
175 |
176 | .confirmCartItemsContainer > div > span {
177 | font: 100 4vw "Roboto";
178 | }
179 |
180 | .orderSummary {
181 | padding: 12vw;
182 | }
183 |
184 | .orderSummary > p {
185 | font: 400 6vw "Roboto";
186 | padding: 4vw;
187 | }
188 |
189 | .orderSummary > div > div {
190 | font: 300 4vw "Roboto";
191 | }
192 |
193 | .orderSummaryTotal {
194 | font: 300 4vw "Roboto";
195 | padding: 5vw 0;
196 | }
197 |
198 | .orderSummary > button {
199 | padding: 4vw;
200 | margin: 4vw auto;
201 | font: 400 4vw "Roboto";
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/frontend/src/component/User/UpdateProfile.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from "react";
2 | import "./UpdateProfile.css";
3 | import Loader from "../layout/Loader/Loader";
4 | import MailOutlineIcon from "@material-ui/icons/MailOutline";
5 | import FaceIcon from "@material-ui/icons/Face";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { clearErrors, updateProfile, loadUser } from "../../actions/userAction";
8 | import { useAlert } from "react-alert";
9 | import { UPDATE_PROFILE_RESET } from "../../constants/userConstants";
10 | import MetaData from "../layout/MetaData";
11 |
12 | const UpdateProfile = ({ history }) => {
13 | const dispatch = useDispatch();
14 | const alert = useAlert();
15 |
16 | const { user } = useSelector((state) => state.user);
17 | const { error, isUpdated, loading } = useSelector((state) => state.profile);
18 |
19 | const [name, setName] = useState("");
20 | const [email, setEmail] = useState("");
21 | const [avatar, setAvatar] = useState();
22 | const [avatarPreview, setAvatarPreview] = useState("/Profile.png");
23 |
24 | const updateProfileSubmit = (e) => {
25 | e.preventDefault();
26 |
27 | const myForm = new FormData();
28 |
29 | myForm.set("name", name);
30 | myForm.set("email", email);
31 | myForm.set("avatar", avatar);
32 | dispatch(updateProfile(myForm));
33 | };
34 |
35 | const updateProfileDataChange = (e) => {
36 | const reader = new FileReader();
37 |
38 | reader.onload = () => {
39 | if (reader.readyState === 2) {
40 | setAvatarPreview(reader.result);
41 | setAvatar(reader.result);
42 | }
43 | };
44 |
45 | reader.readAsDataURL(e.target.files[0]);
46 | };
47 |
48 | useEffect(() => {
49 | if (user) {
50 | setName(user.name);
51 | setEmail(user.email);
52 | setAvatarPreview(user.avatar.url);
53 | }
54 |
55 | if (error) {
56 | alert.error(error);
57 | dispatch(clearErrors());
58 | }
59 |
60 | if (isUpdated) {
61 | alert.success("Profile Updated Successfully");
62 | dispatch(loadUser());
63 |
64 | history.push("/account");
65 |
66 | dispatch({
67 | type: UPDATE_PROFILE_RESET,
68 | });
69 | }
70 | }, [dispatch, error, alert, history, user, isUpdated]);
71 | return (
72 |
73 | {loading ? (
74 |
75 | ) : (
76 |
77 |
78 |
79 |
80 |
Update Profile
81 |
82 |
125 |
126 |
127 |
128 | )}
129 |
130 | );
131 | };
132 |
133 | export default UpdateProfile;
134 |
--------------------------------------------------------------------------------
/frontend/src/reducers/orderReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_ORDER_REQUEST,
3 | CREATE_ORDER_SUCCESS,
4 | CREATE_ORDER_FAIL,
5 | MY_ORDERS_REQUEST,
6 | MY_ORDERS_SUCCESS,
7 | MY_ORDERS_FAIL,
8 | ALL_ORDERS_REQUEST,
9 | ALL_ORDERS_SUCCESS,
10 | ALL_ORDERS_FAIL,
11 | UPDATE_ORDER_REQUEST,
12 | UPDATE_ORDER_SUCCESS,
13 | UPDATE_ORDER_FAIL,
14 | UPDATE_ORDER_RESET,
15 | DELETE_ORDER_REQUEST,
16 | DELETE_ORDER_SUCCESS,
17 | DELETE_ORDER_FAIL,
18 | DELETE_ORDER_RESET,
19 | ORDER_DETAILS_REQUEST,
20 | ORDER_DETAILS_SUCCESS,
21 | ORDER_DETAILS_FAIL,
22 | CLEAR_ERRORS,
23 | } from "../constants/orderConstants";
24 |
25 | export const newOrderReducer = (state = {}, action) => {
26 | switch (action.type) {
27 | case CREATE_ORDER_REQUEST:
28 | return {
29 | ...state,
30 | loading: true,
31 | };
32 |
33 | case CREATE_ORDER_SUCCESS:
34 | return {
35 | loading: false,
36 | order: action.payload,
37 | };
38 |
39 | case CREATE_ORDER_FAIL:
40 | return {
41 | loading: false,
42 | error: action.payload,
43 | };
44 | case CLEAR_ERRORS:
45 | return {
46 | ...state,
47 | error: null,
48 | };
49 |
50 | default:
51 | return state;
52 | }
53 | };
54 |
55 | export const myOrdersReducer = (state = { orders: [] }, action) => {
56 | switch (action.type) {
57 | case MY_ORDERS_REQUEST:
58 | return {
59 | loading: true,
60 | };
61 |
62 | case MY_ORDERS_SUCCESS:
63 | return {
64 | loading: false,
65 | orders: action.payload,
66 | };
67 |
68 | case MY_ORDERS_FAIL:
69 | return {
70 | loading: false,
71 | error: action.payload,
72 | };
73 | case CLEAR_ERRORS:
74 | return {
75 | ...state,
76 | error: null,
77 | };
78 |
79 | default:
80 | return state;
81 | }
82 | };
83 |
84 | export const allOrdersReducer = (state = { orders: [] }, action) => {
85 | switch (action.type) {
86 | case ALL_ORDERS_REQUEST:
87 | return {
88 | loading: true,
89 | };
90 |
91 | case ALL_ORDERS_SUCCESS:
92 | return {
93 | loading: false,
94 | orders: action.payload,
95 | };
96 |
97 | case ALL_ORDERS_FAIL:
98 | return {
99 | loading: false,
100 | error: action.payload,
101 | };
102 | case CLEAR_ERRORS:
103 | return {
104 | ...state,
105 | error: null,
106 | };
107 |
108 | default:
109 | return state;
110 | }
111 | };
112 |
113 | export const orderReducer = (state = {}, action) => {
114 | switch (action.type) {
115 | case UPDATE_ORDER_REQUEST:
116 | case DELETE_ORDER_REQUEST:
117 | return {
118 | ...state,
119 | loading: true,
120 | };
121 |
122 | case UPDATE_ORDER_SUCCESS:
123 | return {
124 | ...state,
125 | loading: false,
126 | isUpdated: action.payload,
127 | };
128 |
129 | case DELETE_ORDER_SUCCESS:
130 | return {
131 | ...state,
132 | loading: false,
133 | isDeleted: action.payload,
134 | };
135 |
136 | case UPDATE_ORDER_FAIL:
137 | case DELETE_ORDER_FAIL:
138 | return {
139 | ...state,
140 | loading: false,
141 | error: action.payload,
142 | };
143 | case UPDATE_ORDER_RESET:
144 | return {
145 | ...state,
146 | isUpdated: false,
147 | };
148 |
149 | case DELETE_ORDER_RESET:
150 | return {
151 | ...state,
152 | isDeleted: false,
153 | };
154 | case CLEAR_ERRORS:
155 | return {
156 | ...state,
157 | error: null,
158 | };
159 |
160 | default:
161 | return state;
162 | }
163 | };
164 |
165 | export const orderDetailsReducer = (state = { order: {} }, action) => {
166 | switch (action.type) {
167 | case ORDER_DETAILS_REQUEST:
168 | return {
169 | loading: true,
170 | };
171 |
172 | case ORDER_DETAILS_SUCCESS:
173 | return {
174 | loading: false,
175 | order: action.payload,
176 | };
177 |
178 | case ORDER_DETAILS_FAIL:
179 | return {
180 | loading: false,
181 | error: action.payload,
182 | };
183 | case CLEAR_ERRORS:
184 | return {
185 | ...state,
186 | error: null,
187 | };
188 |
189 | default:
190 | return state;
191 | }
192 | };
193 |
--------------------------------------------------------------------------------
/frontend/src/component/Product/Products.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import "./Products.css";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { clearErrors, getProduct } from "../../actions/productAction";
5 | import Loader from "../layout/Loader/Loader";
6 | import ProductCard from "../Home/ProductCard";
7 | import Pagination from "react-js-pagination";
8 | import Slider from "@material-ui/core/Slider";
9 | import { useAlert } from "react-alert";
10 | import Typography from "@material-ui/core/Typography";
11 | import MetaData from "../layout/MetaData";
12 |
13 | const categories = [
14 | "Laptop",
15 | "Footwear",
16 | "Bottom",
17 | "Tops",
18 | "Attire",
19 | "Camera",
20 | "SmartPhones",
21 | ];
22 |
23 | const Products = ({ match }) => {
24 | const dispatch = useDispatch();
25 |
26 | const alert = useAlert();
27 |
28 | const [currentPage, setCurrentPage] = useState(1);
29 | const [price, setPrice] = useState([0, 25000]);
30 | const [category, setCategory] = useState("");
31 |
32 | const [ratings, setRatings] = useState(0);
33 |
34 | const {
35 | products,
36 | loading,
37 | error,
38 | productsCount,
39 | resultPerPage,
40 | filteredProductsCount,
41 | } = useSelector((state) => state.products);
42 |
43 | const keyword = match.params.keyword;
44 |
45 | const setCurrentPageNo = (e) => {
46 | setCurrentPage(e);
47 | };
48 |
49 | const priceHandler = (event, newPrice) => {
50 | setPrice(newPrice);
51 | };
52 | let count = filteredProductsCount;
53 |
54 | useEffect(() => {
55 | if (error) {
56 | alert.error(error);
57 | dispatch(clearErrors());
58 | }
59 |
60 | dispatch(getProduct(keyword, currentPage, price, category, ratings));
61 | }, [dispatch, keyword, currentPage, price, category, ratings, alert, error]);
62 |
63 | return (
64 |
65 | {loading ? (
66 |
67 | ) : (
68 |
69 |
70 | Products
71 |
72 |
73 | {products &&
74 | products.map((product) => (
75 |
76 | ))}
77 |
78 |
79 |
80 |
Price
81 |
89 |
90 |
Categories
91 |
92 | {categories.map((category) => (
93 | setCategory(category)}
97 | >
98 | {category}
99 |
100 | ))}
101 |
102 |
103 |
104 | Ratings Above
105 | {
108 | setRatings(newRating);
109 | }}
110 | aria-labelledby="continuous-slider"
111 | valueLabelDisplay="auto"
112 | min={0}
113 | max={5}
114 | />
115 |
116 |
117 | {resultPerPage < count && (
118 |
134 | )}
135 |
136 | )}
137 |
138 | );
139 | };
140 |
141 | export default Products;
142 |
--------------------------------------------------------------------------------
/frontend/src/component/Order/OrderDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import "./orderDetails.css";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import MetaData from "../layout/MetaData";
5 | import { Link } from "react-router-dom";
6 | import { Typography } from "@material-ui/core";
7 | import { getOrderDetails, clearErrors } from "../../actions/orderAction";
8 | import Loader from "../layout/Loader/Loader";
9 | import { useAlert } from "react-alert";
10 |
11 | const OrderDetails = ({ match }) => {
12 | const { order, error, loading } = useSelector((state) => state.orderDetails);
13 |
14 | const dispatch = useDispatch();
15 | const alert = useAlert();
16 |
17 | useEffect(() => {
18 | if (error) {
19 | alert.error(error);
20 | dispatch(clearErrors());
21 | }
22 |
23 | dispatch(getOrderDetails(match.params.id));
24 | }, [dispatch, alert, error, match.params.id]);
25 | return (
26 |
27 | {loading ? (
28 |
29 | ) : (
30 |
31 |
32 |
33 |
34 |
35 | Order #{order && order._id}
36 |
37 |
Shipping Info
38 |
39 |
40 |
Name:
41 |
{order.user && order.user.name}
42 |
43 |
44 |
Phone:
45 |
46 | {order.shippingInfo && order.shippingInfo.phoneNo}
47 |
48 |
49 |
50 |
Address:
51 |
52 | {order.shippingInfo &&
53 | `${order.shippingInfo.address}, ${order.shippingInfo.city}, ${order.shippingInfo.state}, ${order.shippingInfo.pinCode}, ${order.shippingInfo.country}`}
54 |
55 |
56 |
57 |
Payment
58 |
59 |
60 |
68 | {order.paymentInfo &&
69 | order.paymentInfo.status === "succeeded"
70 | ? "PAID"
71 | : "NOT PAID"}
72 |
73 |
74 |
75 |
76 |
Amount:
77 |
{order.totalPrice && order.totalPrice}
78 |
79 |
80 |
81 |
Order Status
82 |
83 |
84 |
91 | {order.orderStatus && order.orderStatus}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
Order Items:
99 |
100 | {order.orderItems &&
101 | order.orderItems.map((item) => (
102 |
103 |
104 |
105 | {item.name}
106 | {" "}
107 |
108 | {item.quantity} X ₹{item.price} ={" "}
109 | ₹{item.price * item.quantity}
110 |
111 |
112 | ))}
113 |
114 |
115 |
116 |
117 | )}
118 |
119 | );
120 | };
121 |
122 | export default OrderDetails;
123 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/Payment.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useRef } from "react";
2 | import CheckoutSteps from "../Cart/CheckoutSteps";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import MetaData from "../layout/MetaData";
5 | import { Typography } from "@material-ui/core";
6 | import { useAlert } from "react-alert";
7 | import {
8 | CardNumberElement,
9 | CardCvcElement,
10 | CardExpiryElement,
11 | useStripe,
12 | useElements,
13 | } from "@stripe/react-stripe-js";
14 |
15 | import axios from "axios";
16 | import "./payment.css";
17 | import CreditCardIcon from "@material-ui/icons/CreditCard";
18 | import EventIcon from "@material-ui/icons/Event";
19 | import VpnKeyIcon from "@material-ui/icons/VpnKey";
20 | import { createOrder, clearErrors } from "../../actions/orderAction";
21 |
22 | const Payment = ({ history }) => {
23 | const orderInfo = JSON.parse(sessionStorage.getItem("orderInfo"));
24 |
25 | const dispatch = useDispatch();
26 | const alert = useAlert();
27 | const stripe = useStripe();
28 | const elements = useElements();
29 | const payBtn = useRef(null);
30 |
31 | const { shippingInfo, cartItems } = useSelector((state) => state.cart);
32 | const { user } = useSelector((state) => state.user);
33 | const { error } = useSelector((state) => state.newOrder);
34 |
35 | const paymentData = {
36 | amount: Math.round(orderInfo.totalPrice * 100),
37 | };
38 |
39 | const order = {
40 | shippingInfo,
41 | orderItems: cartItems,
42 | itemsPrice: orderInfo.subtotal,
43 | taxPrice: orderInfo.tax,
44 | shippingPrice: orderInfo.shippingCharges,
45 | totalPrice: orderInfo.totalPrice,
46 | };
47 |
48 | const submitHandler = async (e) => {
49 | e.preventDefault();
50 |
51 | payBtn.current.disabled = true;
52 |
53 | try {
54 | const config = {
55 | headers: {
56 | "Content-Type": "application/json",
57 | },
58 | };
59 | const { data } = await axios.post(
60 | "/api/v1/payment/process",
61 | paymentData,
62 | config
63 | );
64 |
65 | const client_secret = data.client_secret;
66 |
67 | if (!stripe || !elements) return;
68 |
69 | const result = await stripe.confirmCardPayment(client_secret, {
70 | payment_method: {
71 | card: elements.getElement(CardNumberElement),
72 | billing_details: {
73 | name: user.name,
74 | email: user.email,
75 | address: {
76 | line1: shippingInfo.address,
77 | city: shippingInfo.city,
78 | state: shippingInfo.state,
79 | postal_code: shippingInfo.pinCode,
80 | country: shippingInfo.country,
81 | },
82 | },
83 | },
84 | });
85 |
86 | if (result.error) {
87 | payBtn.current.disabled = false;
88 |
89 | alert.error(result.error.message);
90 | } else {
91 | if (result.paymentIntent.status === "succeeded") {
92 | order.paymentInfo = {
93 | id: result.paymentIntent.id,
94 | status: result.paymentIntent.status,
95 | };
96 |
97 | dispatch(createOrder(order));
98 |
99 | history.push("/success");
100 | } else {
101 | alert.error("There's some issue while processing payment ");
102 | }
103 | }
104 | } catch (error) {
105 | payBtn.current.disabled = false;
106 | alert.error(error.response.data.message);
107 | }
108 | };
109 |
110 | useEffect(() => {
111 | if (error) {
112 | alert.error(error);
113 | dispatch(clearErrors());
114 | }
115 | }, [dispatch, error, alert]);
116 |
117 | return (
118 |
119 |
120 |
121 |
145 |
146 | );
147 | };
148 |
149 | export default Payment;
150 |
--------------------------------------------------------------------------------
/frontend/src/component/User/LoginSignUp.css:
--------------------------------------------------------------------------------
1 | .LoginSignUpContainer {
2 | width: 100vw;
3 | height: 100vh;
4 | max-width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background-color: rgb(231, 231, 231);
9 | position: fixed;
10 | top: 0%;
11 | left: 0;
12 | }
13 |
14 | .LoginSignUpBox {
15 | background-color: white;
16 | width: 25vw;
17 | height: 70vh;
18 | box-sizing: border-box;
19 | overflow: hidden;
20 | }
21 | .login_signUp_toggle {
22 | display: flex;
23 | height: 3vmax;
24 | }
25 | .login_signUp_toggle > p {
26 | color: rgba(0, 0, 0, 0.678);
27 | font: 300 1vmax "Roboto";
28 | transition: all 0.5s;
29 | cursor: pointer;
30 | display: grid;
31 | place-items: center;
32 | width: 100%;
33 | }
34 | .login_signUp_toggle > p:hover {
35 | color: tomato;
36 | }
37 |
38 | .LoginSignUpBox > div > button {
39 | background-color: tomato;
40 | height: 3px;
41 | width: 50%;
42 | border: none;
43 | transition: all 0.5s;
44 | }
45 |
46 | .loginForm,
47 | .signUpForm {
48 | display: flex;
49 | flex-direction: column;
50 | align-items: center;
51 | margin: auto;
52 | padding: 2vmax;
53 | justify-content: space-evenly;
54 | height: 70%;
55 | transition: all 0.5s;
56 | }
57 |
58 | .signUpForm {
59 | transform: translateY(-100%) translateX(-100vmax);
60 | }
61 |
62 | .loginForm > div,
63 | .signUpForm > div {
64 | display: flex;
65 | width: 100%;
66 | align-items: center;
67 | }
68 | .loginForm > div > input,
69 | .signUpForm > div > input {
70 | padding: 1vmax 4vmax;
71 | padding-right: 1vmax;
72 | width: 100%;
73 | box-sizing: border-box;
74 | border: 1px solid rgba(0, 0, 0, 0.267);
75 | border-radius: 4px;
76 | font: 300 0.9vmax cursive;
77 | outline: none;
78 | }
79 |
80 | .loginForm > div > svg,
81 | .signUpForm > div > svg {
82 | position: absolute;
83 | transform: translateX(1vmax);
84 | font-size: 1.6vmax;
85 | color: rgba(0, 0, 0, 0.623);
86 | }
87 |
88 | .loginForm > a {
89 | color: rgba(0, 0, 0, 0.651);
90 | text-decoration: none;
91 | align-self: flex-end;
92 | transition: all 0.5s;
93 | font: 500 0.8vmax "Gill Sans";
94 | }
95 |
96 | .loginForm > a:hover {
97 | color: black;
98 | }
99 |
100 | #registerImage > img {
101 | width: 3vmax;
102 | border-radius: 100%;
103 | }
104 | #registerImage > input {
105 | display: flex;
106 | padding: 0%;
107 | }
108 |
109 | #registerImage > input::file-selector-button {
110 | cursor: pointer;
111 | width: 100%;
112 | z-index: 2;
113 | height: 5vh;
114 | border: none;
115 | margin: 0%;
116 | font: 400 0.8vmax cursive;
117 | transition: all 0.5s;
118 | padding: 0 1vmax;
119 | color: rgba(0, 0, 0, 0.623);
120 | background-color: rgb(255, 255, 255);
121 | }
122 |
123 | #registerImage > input::file-selector-button:hover {
124 | background-color: rgb(235, 235, 235);
125 | }
126 |
127 | .loginBtn,
128 | .signUpBtn {
129 | border: none;
130 | background-color: tomato;
131 | color: white;
132 | font: 300 0.9vmax "Roboto";
133 | width: 100%;
134 | padding: 0.8vmax;
135 | cursor: pointer;
136 | transition: all 0.5s;
137 | border-radius: 4px;
138 | outline: none;
139 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219);
140 | }
141 |
142 | .loginBtn:hover,
143 | .signUpBtn:hover {
144 | background-color: rgb(179, 66, 46);
145 | }
146 |
147 | .shiftToLeft {
148 | transform: translateX(-100%);
149 | }
150 | .shiftToNeutral {
151 | transform: translateX(0%);
152 | }
153 |
154 | .shiftToNeutralForm {
155 | transform: translateX(0%) translateY(-100%);
156 | }
157 |
158 | .shiftToRight {
159 | transform: translateX(100%);
160 | }
161 |
162 | @media screen and (max-width: 600px) {
163 | .LoginSignUpContainer {
164 | background-color: white;
165 | }
166 | .LoginSignUpBox {
167 | width: 100vw;
168 | height: 95vh;
169 | }
170 | .login_signUp_toggle {
171 | height: 5vmax;
172 | }
173 | .login_signUp_toggle > p {
174 | font: 300 1.5vmax "Roboto";
175 | }
176 |
177 | .loginForm,
178 | .signUpForm {
179 | padding: 5vmax;
180 | }
181 |
182 | .loginForm > div > input,
183 | .signUpForm > div > input {
184 | padding: 2.5vmax 5vmax;
185 | font: 300 1.7vmax cursive;
186 | }
187 |
188 | .loginForm > div > svg,
189 | .signUpForm > div > svg {
190 | font-size: 2.8vmax;
191 | }
192 |
193 | .loginForm > a {
194 | font: 500 1.8vmax "Gill Sans";
195 | }
196 |
197 | #registerImage > img {
198 | width: 8vmax;
199 | border-radius: 100%;
200 | }
201 |
202 | #registerImage > input::file-selector-button {
203 | height: 7vh;
204 | font: 400 1.8vmax cursive;
205 | }
206 |
207 | .loginBtn,
208 | .signUpBtn {
209 | font: 300 1.9vmax "Roboto";
210 | padding: 1.8vmax;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/ProductReviews.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { DataGrid } from "@material-ui/data-grid";
3 | import "./productReviews.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import {
6 | clearErrors,
7 | getAllReviews,
8 | deleteReviews,
9 | } from "../../actions/productAction";
10 | import { useAlert } from "react-alert";
11 | import { Button } from "@material-ui/core";
12 | import MetaData from "../layout/MetaData";
13 | import DeleteIcon from "@material-ui/icons/Delete";
14 | import Star from "@material-ui/icons/Star";
15 |
16 | import SideBar from "./Sidebar";
17 | import { DELETE_REVIEW_RESET } from "../../constants/productConstants";
18 |
19 | const ProductReviews = ({ history }) => {
20 | const dispatch = useDispatch();
21 |
22 | const alert = useAlert();
23 |
24 | const { error: deleteError, isDeleted } = useSelector(
25 | (state) => state.review
26 | );
27 |
28 | const { error, reviews, loading } = useSelector(
29 | (state) => state.productReviews
30 | );
31 |
32 | const [productId, setProductId] = useState("");
33 |
34 | const deleteReviewHandler = (reviewId) => {
35 | dispatch(deleteReviews(reviewId, productId));
36 | };
37 |
38 | const productReviewsSubmitHandler = (e) => {
39 | e.preventDefault();
40 | dispatch(getAllReviews(productId));
41 | };
42 |
43 | useEffect(() => {
44 | if (productId.length === 24) {
45 | dispatch(getAllReviews(productId));
46 | }
47 | if (error) {
48 | alert.error(error);
49 | dispatch(clearErrors());
50 | }
51 |
52 | if (deleteError) {
53 | alert.error(deleteError);
54 | dispatch(clearErrors());
55 | }
56 |
57 | if (isDeleted) {
58 | alert.success("Review Deleted Successfully");
59 | history.push("/admin/reviews");
60 | dispatch({ type: DELETE_REVIEW_RESET });
61 | }
62 | }, [dispatch, alert, error, deleteError, history, isDeleted, productId]);
63 |
64 | const columns = [
65 | { field: "id", headerName: "Review ID", minWidth: 200, flex: 0.5 },
66 |
67 | {
68 | field: "user",
69 | headerName: "User",
70 | minWidth: 200,
71 | flex: 0.6,
72 | },
73 |
74 | {
75 | field: "comment",
76 | headerName: "Comment",
77 | minWidth: 350,
78 | flex: 1,
79 | },
80 |
81 | {
82 | field: "rating",
83 | headerName: "Rating",
84 | type: "number",
85 | minWidth: 180,
86 | flex: 0.4,
87 |
88 | cellClassName: (params) => {
89 | return params.getValue(params.id, "rating") >= 3
90 | ? "greenColor"
91 | : "redColor";
92 | },
93 | },
94 |
95 | {
96 | field: "actions",
97 | flex: 0.3,
98 | headerName: "Actions",
99 | minWidth: 150,
100 | type: "number",
101 | sortable: false,
102 | renderCell: (params) => {
103 | return (
104 |
105 |
107 | deleteReviewHandler(params.getValue(params.id, "id"))
108 | }
109 | >
110 |
111 |
112 |
113 | );
114 | },
115 | },
116 | ];
117 |
118 | const rows = [];
119 |
120 | reviews &&
121 | reviews.forEach((item) => {
122 | rows.push({
123 | id: item._id,
124 | rating: item.rating,
125 | comment: item.comment,
126 | user: item.name,
127 | });
128 | });
129 |
130 | return (
131 |
132 |
133 |
134 |
135 |
136 |
137 |
164 |
165 | {reviews && reviews.length > 0 ? (
166 |
174 | ) : (
175 |
No Reviews Found
176 | )}
177 |
178 |
179 |
180 | );
181 | };
182 |
183 | export default ProductReviews;
184 |
--------------------------------------------------------------------------------
/frontend/src/component/Cart/Shipping.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from "react";
2 | import "./Shipping.css";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { saveShippingInfo } from "../../actions/cartAction";
5 | import MetaData from "../layout/MetaData";
6 | import PinDropIcon from "@material-ui/icons/PinDrop";
7 | import HomeIcon from "@material-ui/icons/Home";
8 | import LocationCityIcon from "@material-ui/icons/LocationCity";
9 | import PublicIcon from "@material-ui/icons/Public";
10 | import PhoneIcon from "@material-ui/icons/Phone";
11 | import TransferWithinAStationIcon from "@material-ui/icons/TransferWithinAStation";
12 | import { Country, State } from "country-state-city";
13 | import { useAlert } from "react-alert";
14 | import CheckoutSteps from "../Cart/CheckoutSteps";
15 |
16 | const Shipping = ({ history }) => {
17 | const dispatch = useDispatch();
18 | const alert = useAlert();
19 | const { shippingInfo } = useSelector((state) => state.cart);
20 |
21 | const [address, setAddress] = useState(shippingInfo.address);
22 | const [city, setCity] = useState(shippingInfo.city);
23 | const [state, setState] = useState(shippingInfo.state);
24 | const [country, setCountry] = useState(shippingInfo.country);
25 | const [pinCode, setPinCode] = useState(shippingInfo.pinCode);
26 | const [phoneNo, setPhoneNo] = useState(shippingInfo.phoneNo);
27 |
28 | const shippingSubmit = (e) => {
29 | e.preventDefault();
30 |
31 | if (phoneNo.length < 10 || phoneNo.length > 10) {
32 | alert.error("Phone Number should be 10 digits Long");
33 | return;
34 | }
35 | dispatch(
36 | saveShippingInfo({ address, city, state, country, pinCode, phoneNo })
37 | );
38 | history.push("/order/confirm");
39 | };
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Shipping Details
50 |
51 |
146 |
147 |
148 |
149 | );
150 | };
151 |
152 | export default Shipping;
153 |
--------------------------------------------------------------------------------
/frontend/src/component/Admin/NewProduct.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import "./newProduct.css";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { clearErrors, createProduct } from "../../actions/productAction";
5 | import { useAlert } from "react-alert";
6 | import { Button } from "@material-ui/core";
7 | import MetaData from "../layout/MetaData";
8 | import AccountTreeIcon from "@material-ui/icons/AccountTree";
9 | import DescriptionIcon from "@material-ui/icons/Description";
10 | import StorageIcon from "@material-ui/icons/Storage";
11 | import SpellcheckIcon from "@material-ui/icons/Spellcheck";
12 | import AttachMoneyIcon from "@material-ui/icons/AttachMoney";
13 | import SideBar from "./Sidebar";
14 | import { NEW_PRODUCT_RESET } from "../../constants/productConstants";
15 |
16 | const NewProduct = ({ history }) => {
17 | const dispatch = useDispatch();
18 | const alert = useAlert();
19 |
20 | const { loading, error, success } = useSelector((state) => state.newProduct);
21 |
22 | const [name, setName] = useState("");
23 | const [price, setPrice] = useState(0);
24 | const [description, setDescription] = useState("");
25 | const [category, setCategory] = useState("");
26 | const [Stock, setStock] = useState(0);
27 | const [images, setImages] = useState([]);
28 | const [imagesPreview, setImagesPreview] = useState([]);
29 |
30 | const categories = [
31 | "Laptop",
32 | "Footwear",
33 | "Bottom",
34 | "Tops",
35 | "Attire",
36 | "Camera",
37 | "SmartPhones",
38 | ];
39 |
40 | useEffect(() => {
41 | if (error) {
42 | alert.error(error);
43 | dispatch(clearErrors());
44 | }
45 |
46 | if (success) {
47 | alert.success("Product Created Successfully");
48 | history.push("/admin/dashboard");
49 | dispatch({ type: NEW_PRODUCT_RESET });
50 | }
51 | }, [dispatch, alert, error, history, success]);
52 |
53 | const createProductSubmitHandler = (e) => {
54 | e.preventDefault();
55 |
56 | const myForm = new FormData();
57 |
58 | myForm.set("name", name);
59 | myForm.set("price", price);
60 | myForm.set("description", description);
61 | myForm.set("category", category);
62 | myForm.set("Stock", Stock);
63 |
64 | images.forEach((image) => {
65 | myForm.append("images", image);
66 | });
67 | dispatch(createProduct(myForm));
68 | };
69 |
70 | const createProductImagesChange = (e) => {
71 | const files = Array.from(e.target.files);
72 |
73 | setImages([]);
74 | setImagesPreview([]);
75 |
76 | files.forEach((file) => {
77 | const reader = new FileReader();
78 |
79 | reader.onload = () => {
80 | if (reader.readyState === 2) {
81 | setImagesPreview((old) => [...old, reader.result]);
82 | setImages((old) => [...old, reader.result]);
83 | }
84 | };
85 |
86 | reader.readAsDataURL(file);
87 | });
88 | };
89 |
90 | return (
91 |
92 |
93 |
183 |
184 | );
185 | };
186 |
187 | export default NewProduct;
188 |
--------------------------------------------------------------------------------
/frontend/src/component/Product/ProductDetails.css:
--------------------------------------------------------------------------------
1 | .ProductDetails {
2 | background-color: rgb(255, 255, 255);
3 | width: 100vw;
4 | max-width: 100%;
5 | padding: 6vmax;
6 | box-sizing: border-box;
7 | display: flex;
8 | }
9 |
10 | .ProductDetails > div {
11 | width: 100%;
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: space-evenly;
15 | align-items: center;
16 | padding: 2vmax;
17 | box-sizing: border-box;
18 | border: 1px solid white;
19 | }
20 |
21 | .ProductDetails > div:last-child {
22 | align-items: flex-start;
23 | }
24 |
25 | .CarouselImage {
26 | width: 20vmax;
27 | }
28 |
29 | .detailsBlock-1 > h2 {
30 | color: rgb(54, 54, 54);
31 | font: 600 1.6vmax "Roboto";
32 | }
33 |
34 | .detailsBlock-1 > p {
35 | color: rgba(54, 54, 54, 0.582);
36 | font: 200 0.6vmax cursive;
37 | }
38 |
39 | .detailsBlock-2 {
40 | display: flex;
41 | justify-content: flex-start;
42 | align-items: center;
43 | border-top: 1px solid rgba(0, 0, 0, 0.205);
44 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
45 | width: 70%;
46 | padding: 1vmax 0;
47 | }
48 |
49 | .detailsBlock-2-span {
50 | font: 200 0.8vmax cursive;
51 | color: rgba(0, 0, 0, 0.699);
52 | }
53 |
54 | .detailsBlock-3 {
55 | width: 70%;
56 | }
57 |
58 | .detailsBlock-3 > h1 {
59 | color: rgba(17, 17, 17, 0.795);
60 | font: 400 1.8vmax "Franklin Gothic Medium";
61 | margin: 1vmax 0;
62 | }
63 | .detailsBlock-3-1 {
64 | display: flex;
65 | align-items: center;
66 | }
67 |
68 | .detailsBlock-3-1-1 > button {
69 | border: none;
70 | background-color: rgba(0, 0, 0, 0.616);
71 | padding: 0.5vmax;
72 | cursor: pointer;
73 | color: white;
74 | transition: all 0.5s;
75 | }
76 | .detailsBlock-3-1-1 > button:hover {
77 | background-color: rgba(0, 0, 0, 0.767);
78 | }
79 |
80 | .detailsBlock-3-1-1 > input {
81 | border: none;
82 | padding: 0.5vmax;
83 | width: 1vmax;
84 | text-align: center;
85 | outline: none;
86 | font: 400 0.8vmax "Roboto";
87 | color: rgba(0, 0, 0, 0.74);
88 | }
89 |
90 | .detailsBlock-3-1 > button:last-child {
91 | border: none;
92 | cursor: pointer;
93 | color: white;
94 | transition: all 0.5s;
95 | background-color: tomato;
96 | font: 500 0.7vmax "Roboto";
97 | border-radius: 20px;
98 | padding: 0.5vmax 2vmax;
99 | margin: 1vmax;
100 | outline: none;
101 | }
102 |
103 | .detailsBlock-3-1 > button:last-child:hover {
104 | background-color: rgb(214, 84, 61);
105 | }
106 |
107 | .detailsBlock-3 > p {
108 | border-top: 1px solid rgba(0, 0, 0, 0.205);
109 | border-bottom: 1px solid rgba(0, 0, 0, 0.205);
110 | padding: 1vmax 0;
111 | color: rgba(0, 0, 0, 0.651);
112 | font: 400 1vmax "Roboto";
113 | margin: 1vmax 0;
114 | }
115 |
116 | .detailsBlock-4 {
117 | color: rgba(0, 0, 0, 0.897);
118 | font: 500 1.2vmax sans-serif;
119 | }
120 |
121 | .detailsBlock-4 > p {
122 | color: rgba(0, 0, 0, 0.534);
123 | font: 300 0.8vmax sans-serif;
124 | }
125 |
126 | .submitReview {
127 | border: none;
128 | background-color: tomato;
129 | font: 500 0.7vmax "Roboto";
130 | border-radius: 20px;
131 | padding: 0.6vmax 2vmax;
132 | margin: 1vmax 0;
133 | color: white;
134 | cursor: pointer;
135 | transition: all 0.5s;
136 | outline: none;
137 | }
138 | .submitReview:hover {
139 | background-color: rgb(197, 68, 45);
140 | transform: scale(1.1);
141 | }
142 |
143 | .submitDialog {
144 | display: flex;
145 | flex-direction: column;
146 | }
147 | .submitDialogTextArea {
148 | border: 1px solid rgba(0, 0, 0, 0.082);
149 | margin: 1vmax 0;
150 | outline: none;
151 | padding: 1rem;
152 | font: 300 1rem "Roboto";
153 | }
154 |
155 | .reviewsHeading {
156 | color: #000000be;
157 | font: 500 1.4vmax "Roboto";
158 | text-align: center;
159 | border-bottom: 1px solid rgba(0, 0, 0, 0.226);
160 | padding: 1vmax;
161 | width: 20vmax;
162 | margin: auto;
163 | margin-bottom: 4vmax;
164 | }
165 | .reviews {
166 | display: flex;
167 | overflow: auto;
168 | }
169 |
170 | .reviewCard {
171 | flex: none;
172 |
173 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.226);
174 | border: 1px solid rgba(56, 56, 56, 0.116);
175 | width: 30vmax;
176 | display: flex;
177 | flex-direction: column;
178 | align-items: center;
179 | margin: 1vmax;
180 | padding: 3vmax;
181 | }
182 | .reviewCard > img {
183 | width: 5vmax;
184 | }
185 | .reviewCard > p {
186 | color: rgba(0, 0, 0, 0.836);
187 | font: 600 0.9vmax "Roboto";
188 | }
189 | .reviewCardComment {
190 | color: rgba(0, 0, 0, 0.445);
191 | font: 300 0.8vmax cursive;
192 | }
193 |
194 | .noReviews {
195 | font: 400 1.3vmax "Gill Sans";
196 | text-align: center;
197 | color: rgba(0, 0, 0, 0.548);
198 | }
199 |
200 | @media screen and (max-width: 600px) {
201 | .ProductDetails {
202 | flex-direction: column;
203 | height: unset;
204 | }
205 |
206 | .ProductDetails > div:last-child {
207 | align-items: center;
208 | }
209 |
210 | .detailsBlock-1 > h2 {
211 | font-size: 2.8vmax;
212 | text-align: center;
213 | }
214 |
215 | .detailsBlock-1 > p {
216 | text-align: center;
217 | font-size: 1vmax;
218 | }
219 |
220 | .detailsBlock-2 {
221 | justify-content: center;
222 | }
223 | .detailsBlock-2 > span {
224 | font-size: 1.5vmax;
225 | }
226 |
227 | .detailsBlock-3 > h1 {
228 | font: 700 3vmax "Franklin Gothic Medium";
229 | text-align: center;
230 | }
231 |
232 | .detailsBlock-3-1 {
233 | flex-direction: column;
234 | }
235 |
236 | .detailsBlock-3-1-1 {
237 | padding: 2vmax 0;
238 | }
239 | .detailsBlock-3-1-1 > button {
240 | padding: 1.2vmax;
241 | width: 4vmax;
242 | }
243 |
244 | .detailsBlock-3-1-1 > input {
245 | padding: 1.5vmax;
246 | width: 3vmax;
247 | font: 400 1.8vmax "Roboto";
248 | }
249 |
250 | .detailsBlock-3-1 > button:last-child {
251 | font: 500 1.7vmax "Roboto";
252 | padding: 1.5vmax;
253 | width: 20vmax;
254 | margin: 3vmax 0;
255 | }
256 |
257 | .detailsBlock-3 > p {
258 | padding: 2.5vmax 0;
259 | text-align: center;
260 | font: 400 2vmax "Roboto";
261 | }
262 |
263 | .detailsBlock-4 {
264 | font: 500 2.5vmax sans-serif;
265 | }
266 |
267 | .detailsBlock-4 > p {
268 | font: 300 1.8vmax sans-serif;
269 | }
270 |
271 | .submitReview {
272 | font: 500 1.7vmax "Roboto";
273 | padding: 1.5vmax;
274 | width: 20vmax;
275 | margin: 3vmax 0;
276 | }
277 |
278 | .reviewCard > p {
279 | font: 600 3vw "Roboto";
280 | }
281 | .reviewCardComment {
282 | font: 300 5vw cursive;
283 | }
284 | }
285 |
--------------------------------------------------------------------------------