├── 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 |
7 |
8 |
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 |
8 | 9 | 10 | 11 |
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 | ssa 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 | User 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 | 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 | {product.name} 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 |
21 | setKeyword(e.target.value)} 25 | /> 26 | 27 |
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 |
33 |

Welcome to Ecommerce

34 |

FIND AMAZING PRODUCTS BELOW

35 | 36 | 37 | 40 | 41 |
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 | {user.name} 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 | 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 |
34 | Our Brands 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
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 | Ecommerce 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 |
55 |
56 | 57 | setEmail(e.target.value)} 64 | /> 65 |
66 | 67 | 72 |
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 | Profile 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 |
61 |
62 | 63 | setPassword(e.target.value)} 69 | /> 70 |
71 |
72 | 73 | setConfirmPassword(e.target.value)} 79 | /> 80 |
81 | 86 |
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 | 68 | 69 | 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 | 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 | Product 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 | 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 | 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 |
67 |
68 | 69 | setOldPassword(e.target.value)} 75 | /> 76 |
77 | 78 |
79 | 80 | setNewPassword(e.target.value)} 86 | /> 87 |
88 |
89 | 90 | setConfirmPassword(e.target.value)} 96 | /> 97 |
98 | 103 |
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 | 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 | 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 |
86 |

Update User

87 | 88 |
89 | 90 | setName(e.target.value)} 96 | /> 97 |
98 |
99 | 100 | setEmail(e.target.value)} 106 | /> 107 |
108 | 109 |
110 | 111 | 116 |
117 | 118 | 127 |
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 |
87 |
88 | 89 | setName(e.target.value)} 96 | /> 97 |
98 |
99 | 100 | setEmail(e.target.value)} 107 | /> 108 |
109 | 110 |
111 | Avatar Preview 112 | 118 |
119 | 124 |
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 |
119 | 133 |
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 | Product 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 |
122 |
submitHandler(e)}> 123 | Card Info 124 |
125 | 126 | 127 |
128 |
129 | 130 | 131 |
132 |
133 | 134 | 135 |
136 | 137 | 143 |
144 |
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 | 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 |
141 |

ALL REVIEWS

142 | 143 |
144 | 145 | setProductId(e.target.value)} 151 | /> 152 |
153 | 154 | 163 |
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 |
56 |
57 | 58 | setAddress(e.target.value)} 64 | /> 65 |
66 | 67 |
68 | 69 | setCity(e.target.value)} 75 | /> 76 |
77 | 78 |
79 | 80 | setPinCode(e.target.value)} 86 | /> 87 |
88 | 89 |
90 | 91 | setPhoneNo(e.target.value)} 97 | size="10" 98 | /> 99 |
100 | 101 |
102 | 103 | 104 | 117 |
118 | 119 | {country && ( 120 |
121 | 122 | 123 | 136 |
137 | )} 138 | 139 | 145 |
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 |
94 | 95 |
96 |
101 |

Create Product

102 | 103 |
104 | 105 | setName(e.target.value)} 111 | /> 112 |
113 |
114 | 115 | setPrice(e.target.value)} 120 | /> 121 |
122 | 123 |
124 | 125 | 126 | 133 |
134 | 135 |
136 | 137 | 145 |
146 | 147 |
148 | 149 | setStock(e.target.value)} 154 | /> 155 |
156 | 157 |
158 | 165 |
166 | 167 |
168 | {imagesPreview.map((image, index) => ( 169 | Product Preview 170 | ))} 171 |
172 | 173 | 180 |
181 |
182 |
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 | --------------------------------------------------------------------------------