├── .gitIgnore ├── Procfile ├── README.md ├── backend ├── app.js ├── controller │ ├── CartController.js │ ├── OrderController.js │ ├── PaymentController.js │ ├── ProductController.js │ └── UserController.js ├── db │ └── Database.js ├── index.html ├── middleware │ ├── auth.js │ ├── catchAsyncErrors.js │ └── error.js ├── models │ ├── CartModel.js │ ├── OrderModel.js │ ├── ProductModel.js │ ├── UserModel.js │ └── WishListModel.js ├── routes │ ├── OrderRoute.js │ ├── PaymentRoute.js │ ├── ProductRoute.js │ ├── UserRoute.js │ └── WishListRoute.js ├── server.js └── utils │ ├── ErrorHandler.js │ ├── Features.js │ ├── jwtToken.js │ └── sendMail.js ├── frontend ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── profile.png │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── Assets │ ├── background.jpg │ ├── background2.jpg │ ├── bg.jpg │ ├── icons │ │ ├── bag.png │ │ ├── home.png │ │ ├── homeActive.png │ │ ├── logout.png │ │ ├── logoutActive.png │ │ ├── search.png │ │ ├── searchActive.png │ │ ├── shopping-bagActive.png │ │ ├── shopping-cart.png │ │ ├── shopping-cartActive.png │ │ ├── user.png │ │ └── userActive.png │ └── slider-2.png │ ├── Footer.jsx │ ├── actions │ ├── CartAction.js │ ├── FavouriteAction.js │ ├── OrderAction.js │ ├── ProductActions.js │ └── userAction.js │ ├── component │ ├── Admin │ │ ├── AllOrder.jsx │ │ ├── AllProducts.css │ │ ├── AllProducts.jsx │ │ ├── AllReviews.jsx │ │ ├── AllUsers.jsx │ │ ├── CreateProduct.jsx │ │ ├── Dashboard.jsx │ │ ├── EditProduct.jsx │ │ ├── Sidebar.css │ │ ├── Sidebar.js │ │ ├── UpdateOrder.css │ │ ├── UpdateOrder.jsx │ │ ├── UpdateUser.jsx │ │ ├── dashboard.css │ │ ├── newProduct.css │ │ └── productReviews.css │ ├── Authentication │ │ ├── LoginSignup.css │ │ └── LoginSignup.jsx │ ├── Home │ │ ├── Header.css │ │ ├── Header.jsx │ │ ├── Home.css │ │ └── Home.jsx │ ├── Products │ │ ├── Product.css │ │ ├── ProductCard.jsx │ │ ├── ProductDetails.jsx │ │ ├── Productdetails.css │ │ ├── Products.jsx │ │ ├── ReviewCard.jsx │ │ ├── Search.css │ │ └── Search.jsx │ ├── about │ │ ├── About.css │ │ └── About.jsx │ ├── cart │ │ ├── Cart.css │ │ ├── Cart.jsx │ │ ├── CartItemCard.css │ │ ├── CartItemCard.js │ │ ├── CheckoutSteps.css │ │ ├── CheckoutSteps.jsx │ │ ├── ConfirmOrder.css │ │ ├── ConfirmOrder.jsx │ │ ├── Favourite.css │ │ ├── FavouriteItemsCard │ │ ├── FavouriteItemsCard.css │ │ ├── FavouriteItemsCard.jsx │ │ ├── Favourites.jsx │ │ ├── Payment.jsx │ │ ├── Shipping.css │ │ ├── Shipping.jsx │ │ ├── Success.jsx │ │ ├── orderSuccess.css │ │ └── payment.css │ └── user │ │ ├── EditProfile.css │ │ ├── EditProfile.jsx │ │ ├── ForgotPassword.css │ │ ├── ForgotPassword.jsx │ │ ├── MoreOption.jsx │ │ ├── MyOrder.jsx │ │ ├── MyOrderDetails.jsx │ │ ├── Profile.css │ │ ├── Profile.jsx │ │ ├── ResetPassword.css │ │ ├── ResetPassword.jsx │ │ ├── UpdatePassword.css │ │ ├── UpdatePassword.jsx │ │ ├── myOrders.css │ │ └── orderDetails.css │ ├── constans │ ├── CartConstans.js │ ├── FavouriteConstans.js │ ├── OrderConstans.js │ ├── ProductConstans.js │ └── userContans.js │ ├── index.css │ ├── index.js │ ├── more │ ├── BottomTab.jsx │ ├── BottomTabs.css │ ├── CommingSoon.css │ ├── CommingSoon.jsx │ ├── Contact.jsx │ ├── Loader.js │ ├── Loading.css │ ├── Metadata.js │ ├── Notfound.jsx │ ├── Rules.css │ ├── Rules.jsx │ ├── Support.css │ ├── Support.jsx │ ├── UserData.jsx │ └── UserOption.css │ ├── reducers │ ├── CartReducer.js │ ├── FavouriteReducer.js │ ├── OrderReducer.js │ ├── ProductReducer.js │ └── userReducer.js │ ├── reportWebVitals.js │ ├── route │ └── ProtectedRoute.js │ └── store.js └── package.json /.gitIgnore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /frontend/node_modules 4 | /frontend/.pnp 5 | /frontend/.pnp.js 6 | 7 | # testing 8 | /frontend/coverage 9 | 10 | # production 11 | /frontend/build 12 | 13 | # misc 14 | /backend/config/.env 15 | /frontend/.DS_Store 16 | /frontend/.env.local 17 | /frontend/.env.development.local 18 | /frontend/.env.test.local 19 | /frontend/.env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node backend/server.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a MERN Stack Ecommerce website. I made it for Youtube tutorials. I think everyone will like it. 2 | In this project I am using: 3 | 4 | M = MONGODB FOR DATABASE 5 | E = EXPRESS JS FOR CREATING SERVER 6 | R = REACT JS FOR FRONTEND 7 | R = REDUX FOR CREATE STATE AND USE THAT IN COMPONENT 8 | N = NODE JS FOR BACKEND 9 | 10 | Some important website links: 11 | ** NODE JS 12 | https://nodejs.org/en/ 13 | ** REACT JS 14 | https://reactjs.org/ 15 | ** NODEMAILER 16 | https://nodemailer.com/about/ 17 | ** EMAIL JS 18 | https://www.emailjs.com/ 19 | 20 | ============================== CONNECTS WITH ME ======================== 21 | INSTAGRAM: https://www.instagram.com/dev_shahriar/ 22 | YOUTUBE: https://www.youtube.com/ 23 | FACEBOOK: https://www.facebook.com/shahriar.sajeeb.16 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const ErrorHandler = require("./middleware/error"); 4 | const cookieParser = require("cookie-parser"); 5 | const fileUpload = require("express-fileupload"); 6 | const bodyParser = require("body-parser"); 7 | const path = require("path"); 8 | 9 | app.use(express.json()); 10 | app.use(cookieParser()); 11 | app.use(bodyParser.urlencoded({extended:true,limit:"50mb"})); 12 | app.use(express.urlencoded({ limit: "50mb", extended: true })); 13 | app.use(fileUpload({useTempFiles: true})); 14 | 15 | // config 16 | if(process.env.NODE_ENV!=="PRODUCTION"){ 17 | require("dotenv").config({ 18 | path:"backend/config/.env" 19 | })} 20 | 21 | // Route imports 22 | const product = require("./routes/ProductRoute"); 23 | const user = require("./routes/UserRoute"); 24 | const order = require("./routes/OrderRoute"); 25 | const payment = require("./routes/PaymentRoute"); 26 | const cart = require("./routes/WishListRoute"); 27 | 28 | app.use("/api/v2",product); 29 | 30 | app.use("/api/v2",user); 31 | 32 | app.use("/api/v2",order); 33 | 34 | app.use("/api/v2",payment); 35 | 36 | app.use("/api/v2",cart); 37 | 38 | app.use(express.static(path.join(__dirname,"../frontend/build"))); 39 | 40 | app.get("*",(req,res) =>{ 41 | res.sendFile(path.resolve(__dirname,"../frontend/build/index.html")); 42 | }) 43 | 44 | // it's for errorHandeling 45 | app.use(ErrorHandler); 46 | 47 | module.exports = app -------------------------------------------------------------------------------- /backend/controller/CartController.js: -------------------------------------------------------------------------------- 1 | const Cart = require("../models/CartModel"); 2 | const Wishlist = require("../models/WishListModel"); 3 | const catchAsyncErrors = require("../middleware/catchAsyncErrors"); 4 | const ErrorHandler = require("../utils/ErrorHandler"); 5 | 6 | // Add to wishlist 7 | exports.addToWishlist = catchAsyncErrors(async (req, res, next) => { 8 | const { 9 | productName, 10 | quantity, 11 | productImage, 12 | productPrice, 13 | userId, 14 | productId, 15 | Stock, 16 | } = req.body; 17 | const wishList = await Wishlist.create({ 18 | productName, 19 | quantity, 20 | productImage, 21 | productPrice, 22 | userId, 23 | productId, 24 | Stock, 25 | }); 26 | 27 | res.status(200).json({ 28 | success: true, 29 | wishList, 30 | }); 31 | }); 32 | 33 | // get wishlistData Data 34 | exports.getWishlistData = catchAsyncErrors(async (req, res, next) => { 35 | const wishlistData = await Wishlist.find({ userId: req.user.id }); 36 | 37 | res.status(200).json({ 38 | success: true, 39 | wishlistData, 40 | }); 41 | }); 42 | 43 | // remove wishlistData 44 | exports.removeWishlistData = catchAsyncErrors(async (req, res, next) => { 45 | const wishlistData = await Wishlist.findById(req.params.id); 46 | 47 | if (!wishlistData) { 48 | return next(new ErrorHandler("No wishlistData found with this id", 404)); 49 | } 50 | 51 | await wishlistData.remove(); 52 | 53 | res.status(200).json({ 54 | success: true, 55 | message: "Item removed from wishlist", 56 | }); 57 | }); 58 | 59 | // add To Cart 60 | exports.addToCart = catchAsyncErrors(async (req, res, next) => { 61 | const { 62 | productName, 63 | quantity, 64 | productImage, 65 | productPrice, 66 | userId, 67 | productId, 68 | Stock, 69 | } = req.body; 70 | const cart = await Cart.create({ 71 | productName, 72 | quantity, 73 | productImage, 74 | productPrice, 75 | userId, 76 | productId, 77 | Stock, 78 | }); 79 | 80 | res.status(200).json({ 81 | success: true, 82 | cart, 83 | }); 84 | }); 85 | 86 | // update Cart 87 | exports.updateCart = catchAsyncErrors(async (req, res, next) => { 88 | const { 89 | quantity, 90 | } = req.body; 91 | const cart = await Cart.findByIdAndUpdate(req.params.id); 92 | 93 | if (!cart) { 94 | return next(new ErrorHandler("No cart found with this id", 404)); 95 | } 96 | 97 | await cart.update({ 98 | quantity, 99 | }); 100 | }); 101 | 102 | // get Cart Data 103 | exports.getCartData = catchAsyncErrors(async (req, res, next) => { 104 | const cartData = await Cart.find({ userId: req.user.id }); 105 | res.status(200).json({ 106 | success: true, 107 | cartData, 108 | }); 109 | }); 110 | 111 | // remove Cart Data 112 | exports.removeCartData = catchAsyncErrors(async (req, res, next) => { 113 | const cartData = await Cart.findById(req.params.id); 114 | 115 | if (!cartData) { 116 | return next(new ErrorHandler("Items is not found with this id", 404)); 117 | } 118 | 119 | await cartData.remove(); 120 | 121 | res.status(200).json({ 122 | success: true, 123 | message: "Item removed from cart", 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /backend/controller/OrderController.js: -------------------------------------------------------------------------------- 1 | const Order = require("../models/OrderModel"); 2 | const ErrorHandler = require("../utils/ErrorHandler.js"); 3 | const catchAsyncErrors = require("../middleware/catchAsyncErrors"); 4 | const Product = require("../models/ProductModel"); 5 | 6 | // Create Order 7 | exports.createOrder = catchAsyncErrors(async (req,res,next) =>{ 8 | 9 | const { 10 | shippingInfo, 11 | orderItems, 12 | paymentInfo, 13 | itemsPrice, 14 | taxPrice, 15 | shippingPrice, 16 | totalPrice, 17 | } = req.body; 18 | 19 | const order = await Order.create({ 20 | shippingInfo, 21 | orderItems, 22 | paymentInfo, 23 | itemsPrice, 24 | taxPrice, 25 | shippingPrice, 26 | totalPrice, 27 | paidAt:Date.now(), 28 | user: req.user._id, 29 | }); 30 | 31 | res.status(201).json({ 32 | success: true, 33 | order 34 | }); 35 | }); 36 | 37 | // Get Single order 38 | exports.getSingleOrder = catchAsyncErrors(async (req,res,next) =>{ 39 | const order = await Order.findById(req.params.id).populate( 40 | "user", 41 | "name email" 42 | ); 43 | 44 | if(!order){ 45 | return next(new ErrorHandler("Order not found with this id",404)); 46 | } 47 | 48 | res.status(200).json({ 49 | success: true, 50 | order 51 | }); 52 | }); 53 | 54 | // Get all orders 55 | exports.getAllOrders = catchAsyncErrors(async (req,res,next) =>{ 56 | const orders = await Order.find({user: req.user._id}); 57 | res.status(200).json({ 58 | success: true, 59 | orders 60 | }); 61 | }); 62 | 63 | // Get All orders ---Admin 64 | exports.getAdminAllOrders = 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.updateAdminOrder = catchAsyncErrors(async (req, res, next) => { 82 | 83 | const order = await Order.findById(req.params.id); 84 | 85 | if (!order) { 86 | return next(new ErrorHandler("Order not found with this Id", 404)); 87 | } 88 | 89 | if (order.orderStatus === "Delivered") { 90 | return next(new ErrorHandler("You have already delivered this order", 400)); 91 | } 92 | 93 | if (req.body.status === "Shipped") { 94 | order.orderItems.forEach(async (o) => { 95 | await updateStock(o.product, o.quantity); 96 | }); 97 | } 98 | order.orderStatus = req.body.status; 99 | 100 | if (req.body.status === "Delivered") { 101 | order.deliveredAt = Date.now(); 102 | } 103 | 104 | await order.save({ validateBeforeSave: false }); 105 | res.status(200).json({ 106 | success: true, 107 | }); 108 | }); 109 | 110 | async function updateStock(id, quantity) { 111 | 112 | const product = await Product.findById(id); 113 | 114 | product.Stock -= quantity; 115 | 116 | await product.save({ validateBeforeSave: false }); 117 | } 118 | 119 | 120 | // delete Order ---Admin 121 | exports.deleteOrder = catchAsyncErrors(async (req,res,next) =>{ 122 | 123 | const order = await Order.findById(req.params.id); 124 | 125 | if(!order){ 126 | return next(new ErrorHandler("Order not found with this Id", 404)); 127 | } 128 | 129 | await order.remove(); 130 | 131 | res.status(200).json({ 132 | success: true, 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /backend/controller/PaymentController.js: -------------------------------------------------------------------------------- 1 | const catchAsyncErrors = require("../middleware/catchAsyncErrors"); 2 | 3 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); 4 | 5 | exports.Payment = catchAsyncErrors(async (req, res, next) => { 6 | const myPayment = await stripe.paymentIntents.create({ 7 | amount: req.body.amount, 8 | currency: "usd", 9 | metadata: { 10 | company: "MERN", 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 | -------------------------------------------------------------------------------- /backend/db/Database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | 4 | const connectDatabase = () =>{ 5 | mongoose.connect(process.env.DB_URL,{ 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | }).then((data) =>{ 9 | console.log(`mongodb is connected with server: ${data.connection.host}`); 10 | }) 11 | } 12 | 13 | module.exports = connectDatabase -------------------------------------------------------------------------------- /backend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |

12 | 13 | 26 | -------------------------------------------------------------------------------- /backend/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const ErrorHandler = require("../utils/ErrorHandler"); 2 | const catchAsyncErrors = require("./catchAsyncErrors"); 3 | const jwt = require("jsonwebtoken"); 4 | const User = require("../models/UserModel"); 5 | 6 | 7 | exports.isAuthenticatedUser = catchAsyncErrors(async (req,res,next) =>{ 8 | const { token } = req.cookies; 9 | 10 | if (!token) { 11 | return next(new ErrorHandler("Please Login for access this resource", 401)); 12 | } 13 | 14 | const decodedData = jwt.verify(token, process.env.JWT_SECRET_KEY); 15 | 16 | req.user = await User.findById(decodedData.id); 17 | 18 | next(); 19 | }); 20 | 21 | // Admin Roles 22 | exports.authorizeRoles = (...roles) =>{ 23 | return (req,res,next) =>{ 24 | if(!roles.includes(req.user.role)){ 25 | return next(new ErrorHandler(`${req.user.role} can not access this resources`)); 26 | }; 27 | next(); 28 | } 29 | } -------------------------------------------------------------------------------- /backend/middleware/catchAsyncErrors.js: -------------------------------------------------------------------------------- 1 | module.exports = (theFunc) => (req,res,next) =>{ 2 | Promise.resolve(theFunc(req,res,next)).catch(next); 3 | }; -------------------------------------------------------------------------------- /backend/middleware/error.js: -------------------------------------------------------------------------------- 1 | const ErrorHandler = require("../utils/ErrorHandler"); 2 | 3 | module.exports = (err,req,res,next) =>{ 4 | err.statusCode = err.statusCode || 500 5 | err.message = err.message || "Interval server error" 6 | 7 | // wrong mongodb id error 8 | if(err.name === "CastError"){ 9 | const message = `Resources not found with this id..Invalid ${err.path}`; 10 | err = new ErrorHandler(message, 400); 11 | } 12 | 13 | 14 | // Duplicate key error 15 | if (err.code === 11000) { 16 | const message = `Duplicate ${Object.keys(err.keyValue)} Entered`; 17 | err = new ErrorHandler(message, 400); 18 | } 19 | 20 | // Wrong Jwt error 21 | if (err.name === "JsonWebTokenError") { 22 | const message = `Your url is invalid please try again`; 23 | err = new ErrorHandler(message, 400); 24 | } 25 | 26 | //Jwt expired error 27 | if (err.name === "TokenExpiredError") { 28 | const message = `Your url is expired please try again`; 29 | err = new ErrorHandler(message, 400); 30 | } 31 | 32 | res.status(err.statusCode).json({ 33 | success: false, 34 | message: err.message 35 | }) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /backend/models/CartModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const cartSchema = new mongoose.Schema({ 4 | productName: { 5 | type: String, 6 | required: [true, "Please enter your product name"], 7 | }, 8 | productPrice: { 9 | type: Number, 10 | required: [true, "Please enter your product price"], 11 | }, 12 | productImage: { 13 | type: String, 14 | required: [true, "Please enter your product image"], 15 | }, 16 | quantity: { 17 | type: Number, 18 | required: [true, "Please enter your product quantity"], 19 | }, 20 | userId: { 21 | type: String, 22 | required: [true, "Please enter your user id"], 23 | }, 24 | productId: { 25 | type: String, 26 | required: [true, "Please enter your user id"], 27 | }, 28 | Stock: { 29 | type: Number, 30 | required: [true, "Please enter your product stock"], 31 | } 32 | }); 33 | 34 | module.exports = mongoose.model("Cart", cartSchema); 35 | -------------------------------------------------------------------------------- /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 | state: { 14 | type: String, 15 | // required: true, 16 | }, 17 | country: { 18 | type: String, 19 | // required: true, 20 | }, 21 | pinCode: { 22 | type: Number, 23 | // required: true, 24 | }, 25 | phoneNo: { 26 | type: Number, 27 | // required: true, 28 | }, 29 | }, 30 | orderItems: [ 31 | { 32 | productName: { 33 | type: String, 34 | required: true, 35 | }, 36 | productPrice: { 37 | type: Number, 38 | required: true, 39 | }, 40 | quantity: { 41 | type: Number, 42 | required: true, 43 | }, 44 | productImage: { 45 | type: String, 46 | required: true, 47 | }, 48 | productId: { 49 | type: mongoose.Schema.ObjectId, 50 | ref: "Product", 51 | required: true, 52 | }, 53 | }, 54 | ], 55 | user: { 56 | type: mongoose.Schema.ObjectId, 57 | ref: "User", 58 | required: true, 59 | }, 60 | paymentInfo: { 61 | id: { 62 | type: String, 63 | required: true, 64 | }, 65 | status: { 66 | type: String, 67 | required: true, 68 | }, 69 | }, 70 | paidAt: { 71 | type: Date, 72 | required: true, 73 | }, 74 | itemsPrice: { 75 | type: Number, 76 | required: true, 77 | default: 0, 78 | }, 79 | taxPrice: { 80 | type: Number, 81 | default: 0, 82 | }, 83 | shippingPrice: { 84 | type: Number, 85 | required: true, 86 | default: 0, 87 | }, 88 | totalPrice: { 89 | type: Number, 90 | required: true, 91 | default: 0, 92 | }, 93 | orderStatus: { 94 | type: String, 95 | required: true, 96 | default: "Processing", 97 | }, 98 | deliveredAt: Date, 99 | createdAt: { 100 | type: Date, 101 | default: Date.now, 102 | }, 103 | }); 104 | 105 | module.exports = mongoose.model("Order", orderSchema); -------------------------------------------------------------------------------- /backend/models/ProductModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const productSchema = new mongoose.Schema({ 4 | name:{ 5 | type:String, 6 | required:[true, "Please enter a name of a product"], 7 | trim: true, 8 | maxLength:[20, "Product name not exceed than 20 characters"] 9 | }, 10 | description:{ 11 | type:String, 12 | required:[true, "Please add a description of your product"], 13 | maxlength:[4000,"Description is can not exceed than 4000 characters"] 14 | }, 15 | price:{ 16 | type:Number, 17 | required: [true, "Please add a price for your product"], 18 | maxLength:[8, "Price can not exceed than 8 characters"], 19 | }, 20 | offerPrice:{ 21 | type:String, 22 | maxLength: [4, "Discount price can not exceed than 4 characters"], 23 | }, 24 | color:{ 25 | type: String, 26 | }, 27 | size:{ 28 | type: String, 29 | }, 30 | ratings:{ 31 | type: Number, 32 | default: 0, 33 | }, 34 | images:[ 35 | { 36 | public_id:{ 37 | type:String, 38 | required:true, 39 | }, 40 | url:{ 41 | type:String, 42 | required:true, 43 | }, 44 | } 45 | ], 46 | category:{ 47 | type: String, 48 | required:[true,"Please add a category of your product"], 49 | }, 50 | Stock:{ 51 | type: Number, 52 | required:[true,"Please add some stoke for your product"], 53 | maxLength: [3, "Stock can not exceed than 3 characters"], 54 | }, 55 | numOfReviews:{ 56 | type: Number, 57 | default: 0 58 | }, 59 | reviews:[ 60 | { 61 | user: { 62 | type:mongoose.Schema.ObjectId, 63 | ref:"User", 64 | required: true, 65 | }, 66 | name:{ 67 | type: String, 68 | required: true, 69 | }, 70 | rating:{ 71 | type: Number, 72 | required: true, 73 | }, 74 | comment:{ 75 | type:String, 76 | }, 77 | time:{ 78 | type: Date, 79 | default: Date.now() 80 | }, 81 | }, 82 | ], 83 | user:{ 84 | type: mongoose.Schema.ObjectId, 85 | ref:"User", 86 | // required: true 87 | }, 88 | createAt:{ 89 | type:Date, 90 | default: Date.now() 91 | } 92 | }) 93 | 94 | module.exports = mongoose.model("Product",productSchema); -------------------------------------------------------------------------------- /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 your Name"], 11 | minlength: [3, "Please enter a name atleast 3 characters"], 12 | maxlength: [15, "Name can not big than 15 characters"], 13 | }, 14 | email: { 15 | type: String, 16 | required: [true, "Please enter your email"], 17 | validate: [validator.isEmail, "Please enter a valid email"], 18 | unique: true, 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 | resetPasswordToken: String, 45 | resetPasswordTime: Date, 46 | }); 47 | 48 | // Hash password 49 | userSchema.pre("save", async function (next) { 50 | if (!this.isModified("password")) { 51 | next(); 52 | } 53 | this.password = await bcrypt.hash(this.password, 10); 54 | }); 55 | 56 | // jwt token 57 | userSchema.methods.getJwtToken = function () { 58 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET_KEY, { 59 | expiresIn: process.env.JWT_EXPIRES, 60 | }); 61 | }; 62 | 63 | // compare password 64 | userSchema.methods.comparePassword = async function (enteredPassword) { 65 | return await bcrypt.compare(enteredPassword, this.password); 66 | }; 67 | 68 | // Forgot password 69 | userSchema.methods.getResetToken = function () { 70 | // Generating token 71 | const resetToken = crypto.randomBytes(20).toString("hex"); 72 | 73 | // hashing and adding resetPasswordToken to userSchema 74 | this.resetPasswordToken = crypto 75 | .createHash("sha256") 76 | .update(resetToken) 77 | .digest("hex"); 78 | 79 | this.resetPasswordTime = Date.now() + 15 * 60 * 1000; 80 | 81 | return resetToken; 82 | }; 83 | 84 | module.exports = mongoose.model("User", userSchema); 85 | -------------------------------------------------------------------------------- /backend/models/WishListModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const wishListSchema = new mongoose.Schema({ 4 | productName: { 5 | type: String, 6 | required: [true, "Please enter your product name"], 7 | }, 8 | productPrice: { 9 | type: Number, 10 | required: [true, "Please enter your product price"], 11 | }, 12 | productImage: { 13 | type: String, 14 | required: [true, "Please enter your product image"], 15 | }, 16 | quantity: { 17 | type: Number, 18 | required: [true, "Please enter your product quantity"], 19 | }, 20 | userId: { 21 | type: String, 22 | required: [true, "Please enter your user id"], 23 | }, 24 | productId:{ 25 | type: String, 26 | required: [true, "Please enter your user id"], 27 | }, 28 | Stock: { 29 | type: Number, 30 | required: [true, "Please enter your product stock"], 31 | } 32 | }); 33 | 34 | module.exports = mongoose.model("Wishlist", wishListSchema); 35 | -------------------------------------------------------------------------------- /backend/routes/OrderRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | createOrder, 4 | getSingleOrder, 5 | getAllOrders, 6 | getAdminAllOrders, 7 | updateAdminOrder, 8 | deleteOrder, 9 | } = require("../controller/OrderController"); 10 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth"); 11 | const router = express.Router(); 12 | 13 | router.route("/order/new").post(isAuthenticatedUser, createOrder); 14 | 15 | router.route("/order/:id").get(isAuthenticatedUser, getSingleOrder); 16 | 17 | router.route("/orders/me").get(isAuthenticatedUser, getAllOrders); 18 | 19 | router 20 | .route("/admin/orders") 21 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAdminAllOrders); 22 | 23 | router 24 | .route("/admin/order/:id") 25 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateAdminOrder) 26 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteOrder); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /backend/routes/PaymentRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { Payment, sendStripeApiKey } = require("../controller/PaymentController"); 3 | const router = express.Router(); 4 | const {isAuthenticatedUser} = require("../middleware/auth"); 5 | 6 | router.route("/payment/process").post(isAuthenticatedUser, Payment); 7 | 8 | router.route("/stripeapikey").get(isAuthenticatedUser, sendStripeApiKey); 9 | 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /backend/routes/ProductRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | getAllProducts, 4 | createProduct, 5 | updateProduct, 6 | deleteProduct, 7 | getSingleProduct, 8 | createProductReview, 9 | getSingleProductReviews, 10 | deleteReview, 11 | getAdminProducts, 12 | } = require("../controller/ProductController"); 13 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth"); 14 | const router = express.Router(); 15 | 16 | router.route("/products").get(getAllProducts); 17 | 18 | router 19 | .route("/admin/products") 20 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAdminProducts); 21 | 22 | router 23 | .route("/product/new") 24 | .post(isAuthenticatedUser, authorizeRoles("admin"), createProduct); 25 | 26 | router 27 | .route("/product/:id") 28 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateProduct) 29 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProduct) 30 | .get(getSingleProduct); 31 | 32 | router.route("/product/review").post(isAuthenticatedUser, createProductReview); 33 | 34 | router 35 | .route("/reviews") 36 | .get(getSingleProductReviews) 37 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteReview); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /backend/routes/UserRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | createUser, 4 | loginUser, 5 | logoutUser, 6 | forgotPassword, 7 | resetPassword, 8 | userDetails, 9 | updatePassword, 10 | updateProfile, 11 | getAllUsers, 12 | getSingleUser, 13 | updateUserRole, 14 | deleteUser, 15 | } = require("../controller/UserController"); 16 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth"); 17 | const router = express.Router(); 18 | 19 | router.route("/registration").post(createUser); 20 | 21 | router.route("/login").post(loginUser); 22 | 23 | router.route("/logout").get(logoutUser); 24 | 25 | router.route("/password/forgot").post(forgotPassword); 26 | 27 | router.route("/password/reset/:token").put(resetPassword); 28 | 29 | router.route("/me/update").put(isAuthenticatedUser, updatePassword); 30 | 31 | router.route("/me/update/info").put(isAuthenticatedUser, updateProfile); 32 | 33 | router.route("/me").get(isAuthenticatedUser, userDetails); 34 | 35 | router 36 | .route("/admin/users") 37 | .get(isAuthenticatedUser, authorizeRoles("admin"), getAllUsers); 38 | 39 | router 40 | .route("/admin/user/:id") 41 | .get(isAuthenticatedUser, authorizeRoles("admin"), getSingleUser) 42 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateUserRole) 43 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteUser); 44 | 45 | module.exports = router; 46 | -------------------------------------------------------------------------------- /backend/routes/WishListRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | addToWishlist, 4 | getWishlistData, 5 | removeWishlistData, 6 | addToCart, 7 | getCartData, 8 | updateCart, 9 | removeCartData, 10 | } = require("../controller/CartController"); 11 | const { isAuthenticatedUser } = require("../middleware/auth"); 12 | const router = express.Router(); 13 | 14 | router.route("/wishlist").get(isAuthenticatedUser, getWishlistData); 15 | 16 | router.route("/addToWishlist").post(isAuthenticatedUser, addToWishlist); 17 | 18 | router 19 | .route("/removeWishlist/:id") 20 | .delete(isAuthenticatedUser, removeWishlistData); 21 | 22 | router.route("/addToCart").post(isAuthenticatedUser, addToCart); 23 | 24 | router.route("/cart").get(isAuthenticatedUser, getCartData); 25 | 26 | router.route("/cart/update/:id").put(isAuthenticatedUser, updateCart); 27 | 28 | router.route("/removeCart/:id").delete(isAuthenticatedUser, removeCartData); 29 | 30 | module.exports = router; 31 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | const connectDatabase = require("./db/Database.js"); 3 | const cloudinary = require("cloudinary"); 4 | 5 | // Handling uncaught Exception 6 | process.on("uncaughtException",(err) =>{ 7 | console.log(`Error: ${err.message}`); 8 | console.log(`Shutting down the server for Handling uncaught Exception`); 9 | }) 10 | 11 | // config 12 | if(process.env.NODE_ENV!=="PRODUCTION"){ 13 | require("dotenv").config({ 14 | path:"backend/config/.env" 15 | })} 16 | // connect database 17 | connectDatabase(); 18 | 19 | cloudinary.config({ 20 | cloud_name: process.env.CLOUDINARY_NAME, 21 | api_key: process.env.CLOUDINARY_API_KEY, 22 | api_secret: process.env.CLOUDINARY_API_SECRET 23 | }) 24 | 25 | // create server 26 | const server = app.listen(process.env.PORT,() =>{ 27 | console.log(`Server is working on http://localhost:${process.env.PORT}`) 28 | }) 29 | 30 | 31 | // Unhandled promise rejection 32 | process.on("unhandledRejection", (err) =>{ 33 | console.log(`Shutting down server for ${err.message}`); 34 | console.log(`Shutting down the server due to Unhandled promise rejection`); 35 | server.close(() =>{ 36 | process.exit(1); 37 | }); 38 | }); -------------------------------------------------------------------------------- /backend/utils/ErrorHandler.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 | module.exports = ErrorHandler -------------------------------------------------------------------------------- /backend/utils/Features.js: -------------------------------------------------------------------------------- 1 | class Features { 2 | constructor(query,queryStr){ 3 | this.query = query; 4 | this.queryStr = queryStr; 5 | } 6 | 7 | search() { 8 | const keyword = this.queryStr.keyword ? { 9 | name:{ 10 | $regex: this.queryStr.keyword, 11 | $options: "i" 12 | } 13 | } 14 | :{ 15 | 16 | } 17 | this.query = this.query.find({...keyword}); 18 | return this; 19 | } 20 | 21 | filter(){ 22 | const queryCopy = { ...this.queryStr }; 23 | 24 | // Removing some field for category 25 | const removeFields = ["keyword","page","limit"]; 26 | 27 | removeFields.forEach((key) => delete queryCopy[key]); 28 | 29 | this.query = this.query.find(queryCopy); 30 | return this; 31 | } 32 | 33 | pagination(resultPerPage){ 34 | const currentPage = Number(this.queryStr.page) || 1; 35 | const skip = resultPerPage *(currentPage - 1); 36 | 37 | this.query= this.query.limit(resultPerPage).skip(skip); 38 | 39 | return this; 40 | } 41 | 42 | } 43 | 44 | module.exports = Features; -------------------------------------------------------------------------------- /backend/utils/jwtToken.js: -------------------------------------------------------------------------------- 1 | // create token and saving that in cookies 2 | const sendToken = (user,statusCode,res) =>{ 3 | 4 | const token = user.getJwtToken(); 5 | 6 | // Options for cookies 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; -------------------------------------------------------------------------------- /backend/utils/sendMail.js: -------------------------------------------------------------------------------- 1 | const nodeMailer = require("nodemailer"); 2 | 3 | const sendMail = 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 = sendMail; -------------------------------------------------------------------------------- /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* -------------------------------------------------------------------------------- /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 your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may 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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emailjs/browser": "^3.5.0", 7 | "@material-ui/core": "^4.12.3", 8 | "@material-ui/data-grid": "^4.0.0-alpha.37", 9 | "@material-ui/icons": "^4.11.2", 10 | "@material-ui/lab": "^4.0.0-alpha.60", 11 | "@mui/icons-material": "^5.5.0", 12 | "@stripe/react-stripe-js": "^1.7.0", 13 | "@stripe/stripe-js": "^1.26.0", 14 | "@testing-library/jest-dom": "^5.16.2", 15 | "@testing-library/react": "^12.1.2", 16 | "@testing-library/user-event": "^13.5.0", 17 | "axios": "0.21.1", 18 | "chart.js": "^3.6.2", 19 | "country-state-city": "^3.0.1", 20 | "npm": "^8.7.0", 21 | "react": "^17.0.2", 22 | "react-alert": "^7.0.3", 23 | "react-chartjs-2": "^4.0.0", 24 | "react-dom": "^17.0.2", 25 | "react-helmet": "^6.1.0", 26 | "react-js-pagination": "^3.0.3", 27 | "react-material-ui-carousel": "^2.3.1", 28 | "react-rating-stars-component": "^2.2.0", 29 | "react-redux": "^7.2.6", 30 | "react-router-dom": "5.2.0", 31 | "react-scripts": "5.0.0", 32 | "react-toastify": "^8.2.0", 33 | "redux": "^4.1.2", 34 | "redux-devtools-extension": "^2.13.9", 35 | "redux-thunk": "^2.4.1", 36 | "web-vitals": "^2.1.4", 37 | "webfontloader": "^1.6.28" 38 | }, 39 | "scripts": { 40 | "start": "react-scripts start", 41 | "build": "react-scripts build", 42 | "test": "react-scripts test", 43 | "eject": "react-scripts eject" 44 | }, 45 | "eslintConfig": { 46 | "extends": [ 47 | "react-app", 48 | "react-app/jest" 49 | ] 50 | }, 51 | "browserslist": { 52 | "production": [ 53 | ">0.2%", 54 | "not dead", 55 | "not op_mini all" 56 | ], 57 | "development": [ 58 | "last 1 chrome version", 59 | "last 1 firefox version", 60 | "last 1 safari version" 61 | ] 62 | }, 63 | "proxy": "http://localhost:4000" 64 | } 65 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | React App 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/public/profile.png -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/Assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/background.jpg -------------------------------------------------------------------------------- /frontend/src/Assets/background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/background2.jpg -------------------------------------------------------------------------------- /frontend/src/Assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/bg.jpg -------------------------------------------------------------------------------- /frontend/src/Assets/icons/bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/bag.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/home.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/homeActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/homeActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/logout.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/logoutActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/logoutActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/search.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/searchActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/searchActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/shopping-bagActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/shopping-bagActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/shopping-cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/shopping-cart.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/shopping-cartActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/shopping-cartActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/user.png -------------------------------------------------------------------------------- /frontend/src/Assets/icons/userActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/icons/userActive.png -------------------------------------------------------------------------------- /frontend/src/Assets/slider-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/Assets/slider-2.png -------------------------------------------------------------------------------- /frontend/src/actions/CartAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_TO_CART, 3 | REMOVE_CART_ITEM, 4 | SAVE_SHIPPING_INFO, 5 | } from "../constans/CartConstans"; 6 | import axios from "axios"; 7 | 8 | // Add to Cart ---Product 9 | export const addItemsToCart = (id, quantity) => async (dispatch, getState) => { 10 | const { data } = await axios.get(`/api/v2/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 ---Product 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 | 38 | // SAVE SHIPPING INFO 39 | export const saveShippingInfo = (data) => async (dispatch) => { 40 | dispatch({ 41 | type: SAVE_SHIPPING_INFO, 42 | payload: data, 43 | }); 44 | 45 | localStorage.setItem("shippingInfo", JSON.stringify(data)); 46 | }; -------------------------------------------------------------------------------- /frontend/src/actions/FavouriteAction.js: -------------------------------------------------------------------------------- 1 | import { ADD_TO_FAVOURITE, ADD_TO_FAVOURITE_OFFER, REMOVE_FROM_FAVOURITE, REMOVE_FROM_FAVOURITE_OFFER} 2 | from "../constans/FavouriteConstans"; 3 | import axios from "axios"; 4 | 5 | // Add to favourites 6 | export const addFavouriteItemsToCart = (id,quantity) => async (dispatch, getState) =>{ 7 | const {data} = await axios.get(`/api/v2/product/${id}`); 8 | 9 | dispatch({ 10 | type: ADD_TO_FAVOURITE, 11 | payload: { 12 | product: data.product._id, 13 | name: data.product.name, 14 | price: data.product.price, 15 | image: data.product.images[0].url, 16 | stock: data.product.Stock, 17 | quantity, 18 | } 19 | }) 20 | 21 | localStorage.setItem("favouriteItems", JSON.stringify(getState().favourite.favouriteItems)); 22 | } 23 | 24 | // Delete from favourites 25 | export const deleteFavouriteItemsToCart = (id) => async (dispatch, getState) => { 26 | dispatch({ 27 | type: REMOVE_FROM_FAVOURITE, 28 | payload: id, 29 | }); 30 | 31 | localStorage.setItem("favouriteItems", JSON.stringify(getState().favourite.favouriteItems)); 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/src/actions/OrderAction.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | ALL_ORDERS_FAIL, 4 | ALL_ORDERS_REQUEST, 5 | ALL_ORDERS_SUCCESS, 6 | CREATE_ORDER_FAIL, 7 | CREATE_ORDER_REQUEST, 8 | CREATE_ORDER_SUCCESS, 9 | DELETE_ORDER_FAIL, 10 | DELETE_ORDER_REQUEST, 11 | DELETE_ORDER_SUCCESS, 12 | MY_ORDERS_FAIL, 13 | MY_ORDERS_REQUEST, 14 | MY_ORDERS_SUCCESS, 15 | ORDER_DETAILS_FAIL, 16 | ORDER_DETAILS_REQUEST, 17 | ORDER_DETAILS_SUCCESS, 18 | UPDATE_ORDER_FAIL, 19 | UPDATE_ORDER_REQUEST, 20 | UPDATE_ORDER_SUCCESS, 21 | } from "../constans/OrderConstans"; 22 | import { CLEAR_ERRORS } from "../constans/userContans"; 23 | 24 | // Create Order 25 | export const createOrder = (order) => async (dispatch) => { 26 | try { 27 | dispatch({ type: CREATE_ORDER_REQUEST }); 28 | 29 | const config = { 30 | headers: { 31 | "Content-Type": "application/json", 32 | }, 33 | }; 34 | const { data } = await axios.post("/api/v2/order/new", order, config); 35 | 36 | dispatch({ type: CREATE_ORDER_SUCCESS, payload: data }); 37 | } catch (error) { 38 | dispatch({ 39 | type: CREATE_ORDER_FAIL, 40 | payload: error.response.data.message, 41 | }); 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/v2/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 Order Details 63 | export const getOrderDetails = (id) => async (dispatch) => { 64 | try { 65 | dispatch({ type: ORDER_DETAILS_REQUEST }); 66 | 67 | const { data } = await axios.get(`/api/v2/order/${id}`); 68 | 69 | dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data.order }); 70 | } catch (error) { 71 | dispatch({ 72 | type: ORDER_DETAILS_FAIL, 73 | payload: error.response.data.message, 74 | }); 75 | } 76 | }; 77 | 78 | 79 | // All order -----Admin 80 | export const getAllOrders = () => async (dispatch) => { 81 | try { 82 | dispatch({ type: ALL_ORDERS_REQUEST }); 83 | 84 | const { data } = await axios.get("/api/v2/admin/orders"); 85 | 86 | dispatch({ type: ALL_ORDERS_SUCCESS, payload: data.orders }); 87 | } catch (error) { 88 | dispatch({ 89 | type: ALL_ORDERS_FAIL, 90 | payload: error.response.data.message, 91 | }); 92 | } 93 | }; 94 | 95 | // Update Order 96 | export const updateOrder = (id, order) => async (dispatch) => { 97 | try { 98 | dispatch({ type: UPDATE_ORDER_REQUEST }); 99 | 100 | const config = { 101 | headers: { 102 | "Content-Type": "application/json", 103 | }, 104 | }; 105 | const { data } = await axios.put( 106 | `/api/v2/admin/order/${id}`, 107 | order, 108 | config 109 | ); 110 | 111 | dispatch({ type: UPDATE_ORDER_SUCCESS, payload: data.success }); 112 | } catch (error) { 113 | dispatch({ 114 | type: UPDATE_ORDER_FAIL, 115 | payload: error.response.data.message, 116 | }); 117 | } 118 | }; 119 | 120 | // Delete Order 121 | export const deleteOrder = (id) => async (dispatch) => { 122 | try { 123 | dispatch({ type: DELETE_ORDER_REQUEST }); 124 | 125 | const { data } = await axios.delete(`/api/v2/admin/order/${id}`); 126 | 127 | dispatch({ type: DELETE_ORDER_SUCCESS, payload: data.success }); 128 | } catch (error) { 129 | dispatch({ 130 | type: DELETE_ORDER_FAIL, 131 | payload: error.response.data.message, 132 | }); 133 | } 134 | }; 135 | 136 | 137 | 138 | 139 | // Clearing Errors 140 | export const clearErrors = () => async (dispatch) => { 141 | dispatch({ type: CLEAR_ERRORS }); 142 | }; 143 | -------------------------------------------------------------------------------- /frontend/src/component/Admin/AllOrder.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from "react"; 2 | import { DataGrid } from "@material-ui/data-grid"; 3 | import "./newProduct.css"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { Link } from "react-router-dom"; 6 | import { Button } from "@material-ui/core"; 7 | import MetaData from "../../more/Metadata"; 8 | import EditIcon from "@material-ui/icons/Edit"; 9 | import DeleteIcon from "@material-ui/icons/Delete"; 10 | import SideBar from "./Sidebar"; 11 | import { 12 | getAllOrders, 13 | clearErrors, 14 | deleteOrder, 15 | } from "../../actions/OrderAction"; 16 | import { DELETE_ORDER_RESET } from "../../constans/OrderConstans"; 17 | import { ToastContainer, toast } from 'react-toastify'; 18 | 19 | 20 | const AllOrder = ({ history }) => { 21 | const dispatch = useDispatch(); 22 | 23 | const { error, orders } = useSelector((state) => state.AllOrders); 24 | 25 | const { error: deleteError, isDeleted } = useSelector((state) => state.deleteOrder); 26 | 27 | const deleteOrderHandler = (id) => { 28 | dispatch(deleteOrder(id)); 29 | }; 30 | 31 | useEffect(() => { 32 | if (error) { 33 | toast.error(error); 34 | dispatch(clearErrors()); 35 | } 36 | 37 | if (deleteError) { 38 | toast.error(deleteError); 39 | dispatch(clearErrors()); 40 | } 41 | 42 | if (isDeleted) { 43 | toast.success("Order Deleted Successfully"); 44 | history.push("/admin/orders"); 45 | dispatch({ type: DELETE_ORDER_RESET }); 46 | } 47 | 48 | dispatch(getAllOrders()); 49 | }, [dispatch, error, deleteError, history, isDeleted]); 50 | 51 | const columns = [ 52 | { field: "id", headerName: "Order ID", minWidth: 300, flex: 1 }, 53 | 54 | { 55 | field: "status", 56 | headerName: "Status", 57 | minWidth: 150, 58 | flex: 0.5, 59 | cellClassName: (params) => { 60 | return params.getValue(params.id, "status") === "Delivered" 61 | ? "greenColor" 62 | : "redColor"; 63 | }, 64 | }, 65 | { 66 | field: "itemsQty", 67 | headerName: "Items Qty", 68 | type: "number", 69 | minWidth: 150, 70 | flex: 0.4, 71 | }, 72 | 73 | { 74 | field: "amount", 75 | headerName: "Amount", 76 | type: "number", 77 | minWidth: 270, 78 | flex: 0.5, 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 | orders && 111 | orders.forEach((item) => { 112 | rows.push({ 113 | id: item._id, 114 | itemsQty: item.orderItems.length, 115 | amount: item.totalPrice, 116 | status: item.orderStatus, 117 | }); 118 | }); 119 | 120 | return ( 121 | 122 | 123 | 124 |
125 | 126 |
127 |

ALL ORDERS

128 | 129 | 137 |
138 |
139 | 150 |
151 | ); 152 | }; 153 | 154 | export default AllOrder; -------------------------------------------------------------------------------- /frontend/src/component/Admin/AllProducts.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 | .MuiDataGrid-columnHeader{ 11 | background: #3BB77E; 12 | } 13 | 14 | #productListHeading { 15 | font: 400 2rem "Roboto"; 16 | padding: 0.5vmax; 17 | box-sizing: border-box; 18 | color: rgba(0, 0, 0, 0.637); 19 | transition: all 0.5s; 20 | margin: 2rem; 21 | text-align: center; 22 | } 23 | 24 | .productListTable { 25 | background-color: white; 26 | border: none !important; 27 | } 28 | 29 | .productListTable div { 30 | font: 300 1vmax "Roboto"; 31 | color: rgba(0, 0, 0, 0.678); 32 | border: none !important; 33 | } 34 | 35 | .productListTable a, 36 | .productListTable button { 37 | color: rgba(0, 0, 0, 0.527); 38 | transition: all 0.5s; 39 | } 40 | 41 | .productListTable a:hover { 42 | color: tomato; 43 | } 44 | 45 | .productListTable button:hover { 46 | color: rgb(236, 30, 30); 47 | } 48 | 49 | .MuiDataGrid-columnHeader div { 50 | color: rgb(255, 255, 255); 51 | } 52 | 53 | @media screen and (max-width: 600px) { 54 | .productListTable div { 55 | font: 300 4vw "Roboto"; 56 | } 57 | } -------------------------------------------------------------------------------- /frontend/src/component/Admin/AllProducts.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from "react"; 2 | import { DataGrid } from "@material-ui/data-grid"; 3 | import "./AllProducts.css"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { 6 | clearErrors, 7 | deleteProduct, 8 | getAdminProduct, 9 | } from "../../actions/ProductActions"; 10 | import { Link } from "react-router-dom"; 11 | import { Button } from "@material-ui/core"; 12 | import MetaData from "../../more/Metadata"; 13 | import EditIcon from "@material-ui/icons/Edit"; 14 | import DeleteIcon from "@material-ui/icons/Delete"; 15 | import SideBar from "./Sidebar"; 16 | import { ToastContainer, toast } from 'react-toastify'; 17 | import { DELETE_PRODUCT_RESET } from "../../constans/ProductConstans"; 18 | 19 | 20 | const AllProducts = ({history}) => { 21 | 22 | const dispatch = useDispatch(); 23 | 24 | const { error, products } = useSelector((state) => state.products); 25 | 26 | const { error: deleteError, isDeleted } = useSelector( 27 | (state) => state.deleteProduct 28 | ); 29 | 30 | const deleteProductHandler = (id) => { 31 | dispatch(deleteProduct(id)); 32 | }; 33 | 34 | useEffect(() => { 35 | if (error) { 36 | alert(error); 37 | dispatch(clearErrors()); 38 | } 39 | if (deleteError) { 40 | toast.error(deleteError); 41 | dispatch(clearErrors()); 42 | } 43 | 44 | if (isDeleted) { 45 | toast.success("Product Deleted Successfully"); 46 | history.push("/dashboard"); 47 | dispatch({ type: DELETE_PRODUCT_RESET }); 48 | } 49 | dispatch(getAdminProduct()); 50 | }, [dispatch, alert, error, history]); 51 | 52 | const columns = [ 53 | { field: "id", headerName: "Product ID", minWidth: 200, flex: 0.5 }, 54 | 55 | { 56 | field: "name", 57 | headerName: "Name", 58 | minWidth: 350, 59 | flex: 1, 60 | }, 61 | { 62 | field: "stock", 63 | headerName: "Stock", 64 | type: "number", 65 | minWidth: 150, 66 | flex: 0.3, 67 | }, 68 | 69 | { 70 | field: "price", 71 | headerName: "Price", 72 | type: "number", 73 | minWidth: 270, 74 | flex: 0.5, 75 | }, 76 | 77 | { 78 | field: "actions", 79 | flex: 0.3, 80 | headerName: "Actions", 81 | minWidth: 150, 82 | type: "number", 83 | sortable: false, 84 | renderCell: (params) => { 85 | return ( 86 | 87 | 88 | 89 | 90 | 91 | 98 | 99 | ); 100 | }, 101 | }, 102 | ]; 103 | 104 | const rows = []; 105 | 106 | products && 107 | products.forEach((item) => { 108 | rows.push({ 109 | id: item._id, 110 | stock: item.Stock, 111 | price: item.price, 112 | name: item.name, 113 | }); 114 | }); 115 | 116 | return ( 117 | 118 | 119 | 120 |
121 | 122 |
123 |

ALL PRODUCTS

124 | 125 | 133 |
134 |
135 | 146 |
147 | ) 148 | } 149 | 150 | export default AllProducts 151 | -------------------------------------------------------------------------------- /frontend/src/component/Admin/AllUsers.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from "react"; 2 | import { DataGrid } from "@material-ui/data-grid"; 3 | import "./newProduct.css"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { Link } from "react-router-dom"; 6 | import { Button } from "@material-ui/core"; 7 | import MetaData from "../../more/Metadata"; 8 | import EditIcon from "@material-ui/icons/Edit"; 9 | import DeleteIcon from "@material-ui/icons/Delete"; 10 | import SideBar from "./Sidebar"; 11 | import { getAllUsers, clearErrors, deleteUser } from "../../actions/userAction"; 12 | import { DELETE_USER_RESET } from "../../constans/userContans"; 13 | import { ToastContainer, toast } from 'react-toastify'; 14 | 15 | const AllUsers = ({ history }) => { 16 | 17 | const dispatch = useDispatch(); 18 | 19 | const { error, users } = useSelector((state) => state.allUsers); 20 | 21 | const { 22 | error: deleteError, 23 | isDeleted, 24 | message, 25 | } = useSelector((state) => state.profile); 26 | 27 | const deleteUserHandler = (id) => { 28 | dispatch(deleteUser(id)); 29 | }; 30 | 31 | useEffect(() => { 32 | if (error) { 33 | toast.error(error); 34 | dispatch(clearErrors()); 35 | } 36 | 37 | if (deleteError) { 38 | toast.error(deleteError); 39 | dispatch(clearErrors()); 40 | } 41 | 42 | if (isDeleted) { 43 | toast.success(message); 44 | history.push("/admin/users"); 45 | dispatch({ type: DELETE_USER_RESET }); 46 | } 47 | 48 | dispatch(getAllUsers()); 49 | }, [dispatch, alert, error, deleteError, history, isDeleted, message]); 50 | 51 | const columns = [ 52 | { field: "id", headerName: "User ID", minWidth: 180, flex: 0.8 }, 53 | 54 | { 55 | field: "email", 56 | headerName: "Email", 57 | minWidth: 200, 58 | flex: 1, 59 | }, 60 | { 61 | field: "name", 62 | headerName: "Name", 63 | minWidth: 150, 64 | flex: 0.5, 65 | }, 66 | 67 | { 68 | field: "role", 69 | headerName: "Role", 70 | type: "number", 71 | minWidth: 150, 72 | flex: 0.3, 73 | cellClassName: (params) => { 74 | return params.getValue(params.id, "role") === ("admin") 75 | ? "greenColor" 76 | : "redColor"; 77 | }, 78 | }, 79 | 80 | { 81 | field: "actions", 82 | flex: 0.3, 83 | headerName: "Actions", 84 | minWidth: 150, 85 | type: "number", 86 | sortable: false, 87 | renderCell: (params) => { 88 | return ( 89 | 90 | 91 | 92 | 93 | 94 | 101 | 102 | ); 103 | }, 104 | }, 105 | ]; 106 | 107 | const rows = []; 108 | 109 | users && 110 | users.forEach((item) => { 111 | rows.push({ 112 | id: item._id, 113 | role: item.role, 114 | email: item.email, 115 | name: item.name, 116 | }); 117 | }); 118 | 119 | return ( 120 | 121 | 122 | 123 |
124 | 125 |
126 |

ALL USERS

127 | 128 | 136 |
137 |
138 | 149 |
150 | ); 151 | }; 152 | 153 | export default AllUsers; -------------------------------------------------------------------------------- /frontend/src/component/Admin/Dashboard.jsx: -------------------------------------------------------------------------------- 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 | // eslint-disable-next-line 8 | import Chart from 'chart.js/auto'; 9 | import { useSelector, useDispatch } from "react-redux"; 10 | import MetaData from "../../more/Metadata.js"; 11 | import Loading from "../../more/Loader.js"; 12 | import { getAdminProduct } from "../../actions/ProductActions.js"; 13 | import { getAllOrders } from "../../actions/OrderAction.js"; 14 | import { getAllUsers } from "../../actions/userAction.js"; 15 | 16 | const Dashboard = () => { 17 | 18 | const dispatch = useDispatch(); 19 | 20 | const { products,loading } = useSelector((state) => state.products); 21 | 22 | const { orders } = useSelector((state) => state.AllOrders); 23 | 24 | const { users } = useSelector((state) => state.allUsers); 25 | 26 | let outOfStock = 0; 27 | 28 | products && 29 | products.forEach((item) => { 30 | if (item.Stock === 0) { 31 | outOfStock += 1; 32 | } 33 | }); 34 | 35 | useEffect(() => { 36 | dispatch(getAdminProduct()); 37 | dispatch(getAllOrders()); 38 | dispatch(getAllUsers()); 39 | }, [dispatch]); 40 | 41 | let totalAmount = 0; 42 | orders && 43 | orders.forEach((item) => { 44 | totalAmount += item.totalPrice; 45 | }); 46 | 47 | const lineState = { 48 | labels: ["Initial Amount", "Amount Earned"], 49 | datasets: [ 50 | { 51 | label: "TOTAL AMOUNT", 52 | backgroundColor: ["#3BB77E"], 53 | hoverBackgroundColor: ["#3BB77E"], 54 | data: [0, totalAmount], 55 | }, 56 | ], 57 | }; 58 | 59 | const doughnutState = { 60 | labels: ["Out of Stock", "InStock"], 61 | datasets: [ 62 | { 63 | backgroundColor: ["#00A6B4", "#6800B4"], 64 | hoverBackgroundColor: ["#4B5000", "#35014F"], 65 | data: [outOfStock, products.length - outOfStock], 66 | }, 67 | ], 68 | }; 69 | 70 | return ( 71 | <> 72 | {loading ? 73 | 74 | :( 75 |
76 | 77 | 78 | 79 |
80 | Dashboard 81 | 82 |
83 |
84 |

85 | Total Amount
${totalAmount} 86 |

87 |
88 |
89 | 90 |

Product

91 |

{products && products.length}

92 | 93 | 94 |

Orders

95 |

{orders && orders.length}

96 | 97 | 98 |

Users

99 |

{users && users.length}

100 | 101 |
102 |
103 | 104 |
105 | 106 |
107 | 108 |
109 | 110 |
111 |
112 |
113 | ) 114 | } 115 | 116 | ); 117 | }; 118 | export default Dashboard 119 | -------------------------------------------------------------------------------- /frontend/src/component/Admin/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background-color: rgb(255, 255, 255); 3 | position: sticky; 4 | top: 0; 5 | left: 0; 6 | display: flex; 7 | flex-direction: column; 8 | overflow: hidden; 9 | height: 100vh; 10 | } 11 | 12 | .sidebar > a:first-child { 13 | padding: 0; 14 | } 15 | .sidebar > a > img { 16 | width: 100%; 17 | transition: all 0.5s; 18 | padding: 1vmax 0; 19 | } 20 | 21 | .sidebar a { 22 | text-decoration: none; 23 | color: rgba(0, 0, 0, 0.493); 24 | font: 200 1rem "Roboto"; 25 | padding: 1.8rem 1.2rem; 26 | transition: all 0.5s; 27 | } 28 | .sidebar a > P { 29 | display: flex; 30 | align-items: center; 31 | } 32 | .sidebar a > p > svg { 33 | margin-right: 0.5rem; 34 | } 35 | 36 | .MuiTypography-root { 37 | background-color: #fff !important; 38 | } 39 | .MuiTypography-root.MuiTreeItem-label.MuiTypography-body1 { 40 | color: #999; 41 | } -------------------------------------------------------------------------------- /frontend/src/component/Admin/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Sidebar.css"; 3 | import { Link } from "react-router-dom"; 4 | import PostAddIcon from "@material-ui/icons/PostAdd"; 5 | import AddIcon from "@material-ui/icons/Add"; 6 | import LocalOffer from "@material-ui/icons/LocalOffer"; 7 | import ListAltIcon from "@material-ui/icons/ListAlt"; 8 | import DashboardIcon from "@material-ui/icons/Dashboard"; 9 | import PeopleIcon from "@material-ui/icons/People"; 10 | import RateReviewIcon from "@material-ui/icons/RateReview"; 11 | 12 | const Sidebar = () => { 13 | 14 | const button = () =>{ 15 | let items = document.querySelectorAll(".Dashboard__item"); 16 | 17 | } 18 | 19 | return ( 20 |
21 | 22 | Ecommerce 24 | 25 | 26 |

27 | Dashboard 28 |

29 | 30 | 31 |

All Products

32 | 33 | 34 | 35 |

Create Product

36 | 37 | 38 | 39 | 40 |

41 | 42 | Orders 43 |

44 | 45 | 46 |

47 | Users 48 |

49 | 50 | 51 |

52 | 53 | Reviews 54 |

55 | 56 |
57 | ); 58 | }; 59 | 60 | export default Sidebar; -------------------------------------------------------------------------------- /frontend/src/component/Admin/UpdateOrder.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 | } -------------------------------------------------------------------------------- /frontend/src/component/Admin/UpdateUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect, useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { Button } from "@material-ui/core"; 4 | import MetaData from "../../more/Metadata"; 5 | import MailOutlineIcon from "@material-ui/icons/MailOutline"; 6 | import PersonIcon from "@material-ui/icons/Person"; 7 | import VerifiedUserIcon from "@material-ui/icons/VerifiedUser"; 8 | import SideBar from "./Sidebar"; 9 | import { UPDATE_USER_RESET } from "../../constans/userContans"; 10 | import { 11 | getUserDetails, 12 | updateUser, 13 | clearErrors, 14 | } from "../../actions/userAction"; 15 | import Loading from "../../more/Loader"; 16 | import { ToastContainer, toast } from 'react-toastify'; 17 | 18 | 19 | const UpdateUser = ({ history, match }) => { 20 | const dispatch = useDispatch(); 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 | toast.error(error); 46 | dispatch(clearErrors()); 47 | } 48 | 49 | if (updateError) { 50 | toast.error(updateError); 51 | dispatch(clearErrors()); 52 | } 53 | 54 | if (isUpdated) { 55 | toast.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 | 142 |
143 | ); 144 | }; 145 | 146 | export default UpdateUser; -------------------------------------------------------------------------------- /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: 1rem 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: #3BB77E; 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 | } -------------------------------------------------------------------------------- /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 | text-align: start; 39 | padding-right: 1vmax; 40 | width: 100%; 41 | box-sizing: border-box; 42 | border: 1px solid rgba(0, 0, 0, 0.267); 43 | border-radius: 4px; 44 | font: 300 0.9vmax cursive; 45 | outline: none; 46 | } 47 | 48 | .createProductForm > div > svg { 49 | position: absolute; 50 | transform: translateX(1vmax); 51 | font-size: 1.6vmax; 52 | color: rgba(0, 0, 0, 0.623); 53 | } 54 | 55 | #createProductFormFile > input { 56 | display: flex; 57 | padding: 0%; 58 | } 59 | 60 | #createProductFormFile > input::file-selector-button { 61 | cursor: pointer; 62 | width: 100%; 63 | z-index: 2; 64 | height: 5vh; 65 | border: none; 66 | margin: 0%; 67 | font: 400 0.8vmax cursive; 68 | transition: all 0.5s; 69 | padding: 0 1vmax; 70 | color: rgba(0, 0, 0, 0.623); 71 | background-color: rgb(255, 255, 255); 72 | } 73 | 74 | #createProductFormFile > input::file-selector-button:hover { 75 | background-color: rgb(235, 235, 235); 76 | } 77 | 78 | #createProductFormImage { 79 | width: 100%; 80 | overflow: auto; 81 | } 82 | 83 | #createProductFormImage > img { 84 | width: 3vmax; 85 | margin: 0 0.5vmax; 86 | } 87 | #createProductBtn { 88 | border: none; 89 | background-color: #3BB77E;; 90 | color: white; 91 | font: 300 0.9vmax "Roboto"; 92 | width: 100%; 93 | padding: 0.8vmax; 94 | cursor: pointer; 95 | transition: all 0.5s; 96 | border-radius: 4px; 97 | outline: none; 98 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.219); 99 | } 100 | 101 | #createProductBtn:hover { 102 | background-color: #3BB77E; 103 | } 104 | 105 | @media screen and (max-width: 600px) { 106 | .newProductContainer { 107 | background-color: rgb(255, 255, 255); 108 | } 109 | .createProductForm { 110 | padding: 5vmax; 111 | } 112 | 113 | .createProductForm > div > input, 114 | .createProductForm > div > select, 115 | .createProductForm > div > textarea { 116 | padding: 2.5vmax 5vmax; 117 | font: 300 1.7vmax cursive; 118 | } 119 | 120 | .createProductForm > div > svg { 121 | font-size: 2.8vmax; 122 | } 123 | 124 | #createProductFormFile > img { 125 | width: 8vmax; 126 | border-radius: 100%; 127 | } 128 | 129 | #createProductFormFile > input::file-selector-button { 130 | height: 7vh; 131 | font: 400 1.8vmax cursive; 132 | } 133 | 134 | #createProductBtn { 135 | font: 300 1.9vmax "Roboto"; 136 | padding: 1.8vmax; 137 | } 138 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /frontend/src/component/Home/Header.css: -------------------------------------------------------------------------------- 1 | .inputBox > span{ 2 | font-family: "Roboto"; 3 | animation: sliding 14s linear infinite; 4 | display: block; 5 | } 6 | @keyframes sliding{ 7 | 0%{ 8 | transform: translateX(-70%); 9 | } 10 | 100%{ 11 | transform: translateX(130%); 12 | } 13 | } -------------------------------------------------------------------------------- /frontend/src/component/Products/Product.css: -------------------------------------------------------------------------------- 1 | ul.pagination{ 2 | display: flex; 3 | align-items: center; 4 | padding: 0; 5 | } 6 | .page-item{ 7 | background-color: #fff; 8 | list-style: none; 9 | border: 1px solid rgba(0,0,0,0.178); 10 | padding: 1vmax 1.5vmax; 11 | transition: all 0.3s; 12 | cursor: pointer; 13 | } 14 | .page-item:first-child{ 15 | border-radius: 5px 0 0 5px; 16 | } 17 | .page-item:last-child{ 18 | border-top-right-radius: 5px; 19 | border-bottom-right-radius: 5px; 20 | 21 | } 22 | .page-link{ 23 | font: 300 0.7vmax "Roboto"; 24 | color: rgb(80,80,80); 25 | transition: all 0.3s; 26 | } 27 | .page-item:hover{ 28 | background-color: rgb(230,230,230); 29 | } 30 | .page-item:hover .page-link{ 31 | color: rgb(37, 37, 37); 32 | } 33 | .pageItemActive{ 34 | background-color: #197EF3; 35 | } 36 | .pageLinkActive{ 37 | color: white; 38 | } 39 | li.category-link{ 40 | list-style: none; 41 | font-family: Roboto; 42 | cursor: pointer; 43 | border-top: 1px solid #999; 44 | padding: 10px 10px; 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/component/Products/ProductCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { Rating } from "@material-ui/lab"; 4 | const ProductCard = ({ product }) => { 5 | const options = { 6 | value: product.ratings, 7 | readOnly: true, 8 | precision: 0.5, 9 | }; 10 | 11 | return ( 12 | <> 13 | 14 | {product.name} 19 |

{product.name}

20 |
21 | 22 | ({product.numOfReviews} Reviews) 23 |
24 |
30 |
31 |

39 | {product.offerPrice > 0 ? `$${product.offerPrice}` : ""} 40 |

41 | {`$${product.price}`} 42 |
43 |
44 | 45 | 46 | ); 47 | }; 48 | 49 | export default ProductCard; 50 | -------------------------------------------------------------------------------- /frontend/src/component/Products/Productdetails.css: -------------------------------------------------------------------------------- 1 | .ProductDetails{ 2 | display: flex; 3 | width: 100%; 4 | margin: 20px 0; 5 | } 6 | input:checked ~ label{ 7 | display: none; 8 | } 9 | input{ 10 | outline: none; 11 | } 12 | img.CarouselImage{ 13 | width: 350px; 14 | height: 350px; 15 | object-fit: contain; 16 | } 17 | .first__varse {margin: auto;} 18 | .varse__2{ 19 | width: 50%; 20 | overflow: hidden; 21 | } 22 | .css-14j5k7k{ 23 | justify-content: center!important; 24 | align-items: center!important; 25 | display: flex!important; 26 | } 27 | .detailsBlock-1 > h2 { 28 | padding: 5px 0px; 29 | } 30 | .detailsBlock-2 > span { 31 | padding: 5px 0px; 32 | } 33 | .detailsBlock-3-1 { 34 | display: flex; 35 | align-items: center; 36 | } 37 | 38 | .detailsBlock-3-1-1 { 39 | width: 60px; 40 | overflow: hidden; 41 | display: flex; 42 | margin: 10px; 43 | } 44 | p.noReviews { 45 | text-align: center; 46 | } 47 | .detailsBlock-3-1-1 > button { 48 | width: 100%; 49 | background: #3BB77E; 50 | border: none; 51 | color: #fff; 52 | height: 20px; 53 | cursor: pointer; 54 | } 55 | input[type="number"] { 56 | border: none; 57 | width: 100%; 58 | text-align: center; 59 | } 60 | 61 | @media screen and (max-width: 600px) { 62 | .varse__2 { 63 | width: 100%; 64 | overflow: hidden; 65 | text-align: center; 66 | } 67 | .detailsBlock-3-1{ 68 | flex-direction: column; 69 | } 70 | .Description{ 71 | padding: 0 3vmax; 72 | } 73 | .Description p{ 74 | padding-left: 0!important; 75 | } 76 | .detailsBlock > div > h1 { 77 | font-size: 3vmax; 78 | color: #000!important; 79 | opacity: 1; 80 | padding: 1vmax 0; 81 | } 82 | .home__content{ 83 | width: 100%!important; 84 | left: 0!important; 85 | } 86 | .home__content > div > h2 {font-size: 3vmax!important; 87 | line-height: 1.9;} 88 | .home__content > div > span{ 89 | height: 30px!important; 90 | width: 135px!important; 91 | } 92 | button.Home__button {font-size: 2.1vmax!important;} 93 | .CarouselItem { 94 | width: 100%; 95 | height: 100%; 96 | display: flex!important; 97 | align-items: center!important; 98 | justify-content: center!important; 99 | } 100 | .first__varse{ 101 | margin: unset!important; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /frontend/src/component/Products/ReviewCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from "react-redux"; 3 | import { Rating } from "@material-ui/lab"; 4 | import Loading from "../../more/Loader" 5 | 6 | const ReviewCard = ({review}) => { 7 | // eslint-disable-next-line 8 | 9 | const { product,loading } = useSelector( 10 | (state) => state.productDetails 11 | ); 12 | 13 | const options = { 14 | value: review.rating, 15 | readOnly: true, 16 | precision: 0.5, 17 | color:"#3BB77E" 18 | }; 19 | 20 | return ( 21 | <> 22 | {loading ? ( 23 | 24 | ) :( 25 | <> 26 |
31 |

{review.name}

32 |

{String(review.time).substr(0,10)}

33 |
34 |
35 |

{review.comment}

36 | 37 |
38 | 39 | )} 40 | 41 | ) 42 | } 43 | 44 | export default ReviewCard 45 | -------------------------------------------------------------------------------- /frontend/src/component/Products/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 | color: rgba(0, 0, 0, 0.637); 18 | padding: 1vmax 2vmax; 19 | width: 50%; 20 | outline: none; 21 | border: 2px solid #197EF3; 22 | border-radius: 8px; 23 | font: 300 1.1vmax cursive; 24 | box-sizing: border-box; 25 | height: 8%; 26 | } 27 | @media screen and (max-width: 600px) { 28 | .searchBox > input[type="text"] { 29 | width: 100%; 30 | font: 300 4vw cursive; 31 | height: 10%; 32 | } 33 | 34 | .searchBox > input[type="submit"] { 35 | height: 10%; 36 | width: 30%; 37 | font: 300 4vw "Roboto"; 38 | } 39 | } 40 | @media screen and (max-width: 600px) { 41 | .searchBox > input[type="text"]{ 42 | margin: 0 5%; 43 | } 44 | svg.bi.bi-search.pointer{ 45 | right: 9%!important; 46 | } 47 | } -------------------------------------------------------------------------------- /frontend/src/component/Products/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment } from "react"; 2 | import BottomTab from "../../more/BottomTab"; 3 | import MetaData from "../../more/Metadata"; 4 | import "./Search.css"; 5 | 6 | const Search = ({ history }) => { 7 | const [keyword, setKeyword] = useState(""); 8 | 9 | const searchSubmitHandler = (e) => { 10 | e.preventDefault(); 11 | if (keyword.trim()) { 12 | history.push(`/products/${keyword}`); 13 | } else { 14 | history.push("/products"); 15 | } 16 | }; 17 | 18 | return ( 19 | 20 | 21 |
22 | setKeyword(e.target.value)} 26 | /> 27 | 40 | 41 | 42 |
43 | 44 |
45 | ); 46 | }; 47 | 48 | export default Search; 49 | -------------------------------------------------------------------------------- /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: #3BB77E; 14 | } 15 | .emptyCart > p { 16 | font-size: 2vmax; 17 | } 18 | .emptyCart > a { 19 | background-color: #3BB77E; 20 | color: white; 21 | border: none; 22 | padding: 1vmax 3vmax; 23 | cursor: pointer; 24 | font: 400 1vmax "Roboto"; 25 | margin-top: 1%; 26 | text-decoration: none; 27 | } 28 | 29 | .cartPage { 30 | padding: 5vmax; 31 | } 32 | 33 | .cartHeader { 34 | background-color: #3BB77E; 35 | width: 90%; 36 | box-sizing: border-box; 37 | margin: auto; 38 | color: white; 39 | display: grid; 40 | grid-template-columns: 4fr 1fr 1fr; 41 | font: 300 0.7vmax "Roboto"; 42 | } 43 | .cartHeader > p { 44 | margin: 10px; 45 | } 46 | .cartHeader > p:last-child { 47 | text-align: end; 48 | } 49 | 50 | .cartContainer { 51 | width: 90%; 52 | margin: auto; 53 | border-bottom: 1px solid #99999942; 54 | display: grid; 55 | grid-template-columns: 4fr 1fr 1fr; 56 | } 57 | 58 | .cartInput { 59 | display: flex; 60 | align-items: center; 61 | height: 8vmax; 62 | } 63 | 64 | .cartInput > button { 65 | border: none; 66 | background-color: #3BB77E; 67 | padding: 0.5vmax; 68 | cursor: pointer; 69 | color: white; 70 | transition: all 0.5s; 71 | } 72 | .cartInput > button:hover { 73 | background-color:#3BB77E; 74 | } 75 | 76 | .cartInput > input { 77 | border: none; 78 | padding: 0.5vmax; 79 | width: 1vmax; 80 | text-align: center; 81 | outline: none; 82 | font: 400 0.8vmax "Roboto"; 83 | color: rgba(0, 0, 0, 0.74); 84 | } 85 | 86 | .cartSubtotal { 87 | display: flex; 88 | padding: 0.5vmax; 89 | height: 8vmax; 90 | align-items: center; 91 | box-sizing: border-box; 92 | font: 300 1vmax cursive; 93 | justify-content: flex-end; 94 | color: rgba(0, 0, 0, 0.753); 95 | } 96 | 97 | .cartGrossProfit { 98 | display: grid; 99 | grid-template-columns: 2fr 1.2fr; 100 | } 101 | 102 | .cartGrossProfitBox { 103 | border-top: 3px solid #3BB77E; 104 | margin: 1vmax 4vmax; 105 | box-sizing: border-box; 106 | padding: 2vmax 0; 107 | font: 300 1vmax "Roboto"; 108 | display: flex; 109 | justify-content: space-between; 110 | } 111 | 112 | .checkOutBtn { 113 | display: flex; 114 | justify-content: flex-end; 115 | } 116 | .checkOutBtn > button { 117 | background-color: #3BB77E; 118 | color: white; 119 | border: none; 120 | padding: 0.8vmax 3vmax; 121 | font: 300 0.8vmax "Roboto"; 122 | margin: 1vmax 4vmax; 123 | cursor: pointer; 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 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Cart.css"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { addItemsToCart, removeItemsFromCart } from "../../actions/CartAction"; 5 | import { Typography } from "@material-ui/core"; 6 | import RemoveShoppingCartIcon from "@material-ui/icons/RemoveShoppingCart"; 7 | import { Link } from "react-router-dom"; 8 | import CartItemCard from "./CartItemCard.js"; 9 | import BottomTab from "../../more/BottomTab"; 10 | import { ToastContainer, toast } from 'react-toastify'; 11 | import 'react-toastify/dist/ReactToastify.css'; 12 | 13 | const Cart = ({ history }) => { 14 | const dispatch = useDispatch(); 15 | 16 | const { cartItems } = useSelector((state) => state.cart); 17 | 18 | let Price = cartItems.reduce( 19 | (acc, item) => acc + item.quantity * item.price, 20 | 0 21 | ); 22 | 23 | let totalPrice = Price; 24 | 25 | const increaseQuantity = (id, quantity, stock) => { 26 | const newQty = quantity + 1; 27 | if (stock <= quantity) { 28 | return toast.error("Product Stock Limited"); 29 | } 30 | dispatch(addItemsToCart(id, newQty)); 31 | }; 32 | 33 | const decreaseQuantity = (id, quantity) => { 34 | const newQty = quantity - 1; 35 | if (1 >= quantity) { 36 | return; 37 | } 38 | dispatch(addItemsToCart(id, newQty)); 39 | }; 40 | 41 | const deleteCartItems = (id) => { 42 | dispatch(removeItemsFromCart(id)); 43 | }; 44 | 45 | const checkoutHandler = () => { 46 | history.push("/login?redirect=shipping"); 47 | }; 48 | 49 | return ( 50 | <> 51 | {cartItems.length === 0 ? ( 52 |
53 | 54 | No Items In Cart 55 | View Products 56 | 57 |
58 | ) : ( 59 | <> 60 |
61 |
62 |

Product

63 |

Quantity

64 |

Subtotal

65 |
66 | 67 | {cartItems && 68 | cartItems.map((item) => ( 69 |
70 | 71 |
72 | 79 | 80 | 91 |
92 |

{`$${ 93 | item.price * item.quantity 94 | }`}

95 |
96 | ))} 97 | 98 |
99 |
100 |
101 |

Price Total

102 |

$ {totalPrice}

103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 | 121 | 122 | 123 | )} 124 | 125 | ); 126 | }; 127 | 128 | export default Cart; 129 | -------------------------------------------------------------------------------- /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: #3BB77E; 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 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/CartItemCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import "./CartItemCard.css"; 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/src/component/cart/CheckoutSteps.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: #3BB77E; 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: #3BB77E; 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 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/CheckoutSteps.jsx: -------------------------------------------------------------------------------- 1 | import React 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 | import BottomTab from "../../more/BottomTab"; 8 | 9 | const CheckoutSteps = ({ activeStep }) => { 10 | const steps = [ 11 | { 12 | label: Shipping Details, 13 | icon: , 14 | }, 15 | { 16 | label: Confirm Order, 17 | icon: , 18 | }, 19 | { 20 | label: Payment, 21 | icon: , 22 | }, 23 | ]; 24 | 25 | const stepStyles = { 26 | boxSizing: "border-box", 27 | }; 28 | 29 | return ( 30 | <> 31 | 32 | {steps.map((item, index) => ( 33 | = index ? true : false} 37 | > 38 | = index ? "#3BB77E" : "rgba(0, 0, 0, 0.649)", 41 | }} 42 | icon={item.icon} 43 | > 44 | {item.label} 45 | 46 | 47 | ))} 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default CheckoutSteps; -------------------------------------------------------------------------------- /frontend/src/component/cart/ConfirmOrder.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/component/cart/ConfirmOrder.css -------------------------------------------------------------------------------- /frontend/src/component/cart/ConfirmOrder.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./ConfirmOrder.css"; 3 | import { useSelector } from "react-redux"; 4 | import CheckoutSteps from "./CheckoutSteps"; 5 | import MetaData from "../../more/Metadata"; 6 | import { Link } from "react-router-dom"; 7 | import { Typography } from "@material-ui/core"; 8 | import BottomTab from "../../more/BottomTab"; 9 | 10 | 11 | 12 | const ConfirmOrder = ({ history }) => { 13 | const { shippingInfo, cartItems } = useSelector((state) => state.cart); 14 | 15 | const { user } = useSelector((state) => state.user); 16 | 17 | let productPrice = cartItems.reduce( 18 | (acc, item) => acc + item.quantity * item.price, 19 | 0 20 | ); 21 | 22 | const subtotal = productPrice 23 | // eslint-disable-next-line 24 | const shippingCharges = productPrice > 99 ? 0 : 50; 25 | 26 | const totalPrice = subtotal + shippingCharges; 27 | 28 | const address = `${shippingInfo.address}, ${shippingInfo.state}, ${shippingInfo.country}`; 29 | 30 | const proceedToPayment = () => { 31 | const data = { 32 | subtotal, 33 | shippingCharges, 34 | totalPrice, 35 | }; 36 | 37 | sessionStorage.setItem("orderInfo", JSON.stringify(data)); 38 | 39 | history.push("/process/payment"); 40 | }; 41 | 42 | return ( 43 | <> 44 | 45 | 46 |
47 |
48 |
49 | Shipping Info 50 |
51 |
52 |

Name:

53 | {user.name} 54 |
55 |
56 |

Phone:

57 | {shippingInfo.phoneNo} 58 |
59 |
60 |

Address:

61 | {address} 62 |
63 |
64 |
65 |
66 | Your Cart Items: 67 | 68 | 69 | {cartItems.length === 0 ? 70 |
71 | "" 72 |
73 | : 74 |
75 | {cartItems.map((item) => ( 76 |
77 | Product 78 | 79 | {item.name} 80 | {" "} 81 | 82 | {item.quantity} X ${item.price} ={" "} 83 | ${item.price * item.quantity} 84 | 85 |
86 | )) 87 | } 88 |
89 | } 90 | 91 |
92 |
93 | {/* */} 94 |
95 |
96 | Order Summery 97 |
98 |
99 |

Subtotal:

100 | ${subtotal} 101 |
102 |
103 |

Shipping Charges:

104 | ${shippingCharges} 105 |
106 |
107 |
108 |
109 | 110 |
111 |

112 | Total: 113 |

114 | ${totalPrice} 115 |
116 | 117 | 118 |
119 |
120 |
121 | 122 | 123 | ); 124 | }; 125 | 126 | export default ConfirmOrder; 127 | -------------------------------------------------------------------------------- /frontend/src/component/cart/Favourite.css: -------------------------------------------------------------------------------- 1 | .emptyfavourites { 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 | .emptyfavourites > svg { 12 | font-size: 5vmax; 13 | color: #3BB77E; 14 | } 15 | .emptyfavourites > p { 16 | font-size: 2vmax; 17 | } 18 | .emptyfavourites > 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 | .favouritesPage { 29 | padding: 5vmax; 30 | } 31 | 32 | .favouritesHeader { 33 | background-color: #3BB77E; 34 | width: 100%; 35 | box-sizing: border-box; 36 | margin: auto; 37 | color: white; 38 | justify-content: space-between; 39 | display: flex; 40 | font: 300 0.7vmax "Roboto"; 41 | } 42 | .favouritesHeader > p { 43 | margin: 10px; 44 | } 45 | .favouritesHeader > p:last-child { 46 | text-align: end; 47 | } 48 | 49 | .favouritesContainer { 50 | width: 100%; 51 | margin: auto; 52 | border-bottom: 1px solid #99999942; 53 | display: flex; 54 | justify-content: space-between; 55 | } 56 | 57 | .favouritesInput { 58 | display: flex; 59 | align-items: center; 60 | height: 8vmax; 61 | } 62 | 63 | .favouritesInput > button { 64 | border: none; 65 | background-color: #3BB77E; 66 | padding: 0.5vmax; 67 | cursor: pointer; 68 | color: white; 69 | transition: all 0.5s; 70 | } 71 | .favouritesInput > button:hover { 72 | background-color: #3BB77E; 73 | } 74 | 75 | .favouritesInput > input { 76 | border: none; 77 | padding: 0.5vmax; 78 | width: 1vmax; 79 | text-align: center; 80 | outline: none; 81 | font: 400 0.8vmax "Roboto"; 82 | color: rgba(0, 0, 0, 0.74); 83 | } 84 | 85 | .favouritesSubtotal { 86 | display: flex; 87 | padding: 0.5vmax; 88 | height: 8vmax; 89 | align-items: center; 90 | box-sizing: border-box; 91 | font: 300 1vmax cursive; 92 | justify-content: flex-end; 93 | color: rgba(0, 0, 0, 0.753); 94 | } 95 | 96 | .favouritesGrossProfit { 97 | display: grid; 98 | grid-template-columns: 2fr 1.2fr; 99 | } 100 | 101 | .favouritesGrossProfitBox { 102 | border-top: 3px solid #3BB77E; 103 | margin: 1vmax 4vmax; 104 | box-sizing: border-box; 105 | padding: 2vmax 0; 106 | font: 300 1vmax "Roboto"; 107 | display: flex; 108 | justify-content: space-between; 109 | } 110 | 111 | .checkOutBtn { 112 | display: flex; 113 | justify-content: flex-end; 114 | } 115 | .checkOutBtn > button { 116 | background-color: #3BB77E; 117 | color: white; 118 | border: none; 119 | padding: 0.8vmax 3vmax; 120 | font: 300 0.8vmax "Roboto"; 121 | margin: 1vmax 4vmax; 122 | cursor: pointer; 123 | } 124 | 125 | @media screen and (max-width: 600px) { 126 | .favouritesPage { 127 | padding: 0; 128 | min-height: 60vh; 129 | } 130 | 131 | .favouritesHeader { 132 | width: 100%; 133 | font: 300 1.7vmax "Roboto"; 134 | grid-template-columns: 3fr 1fr 1fr; 135 | } 136 | 137 | .favouritesContainer { 138 | width: 100%; 139 | grid-template-columns: 3fr 1fr 1fr; 140 | } 141 | 142 | .favouritesInput { 143 | height: 20vmax; 144 | } 145 | 146 | .favouritesInput > button { 147 | padding: 1.5vmax; 148 | } 149 | 150 | .favouritesInput > input { 151 | width: 2vmax; 152 | padding: 1.5vmax; 153 | font: 400 1.8vmax "Roboto"; 154 | } 155 | 156 | .favouritesSubtotal { 157 | padding: 1.5vmax; 158 | height: 20vmax; 159 | font: 300 2vmax "Roboto"; 160 | } 161 | 162 | .favouritesGrossProfit { 163 | display: grid; 164 | grid-template-columns: 0fr 2fr; 165 | } 166 | 167 | .favouritesGrossProfitBox { 168 | padding: 2vmax; 169 | font: 300 2vmax "Roboto"; 170 | } 171 | 172 | .checkOutBtn > button { 173 | padding: 2vmax 4vmax; 174 | width: 100%; 175 | font: 300 2vmax "Roboto"; 176 | } 177 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/FavouriteItemsCard: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriarsajeeb/MERN-Ecommerce-store/a5ffa1e3215ee6592fed2f8392140ce938226391/frontend/src/component/cart/FavouriteItemsCard -------------------------------------------------------------------------------- /frontend/src/component/cart/FavouriteItemsCard.css: -------------------------------------------------------------------------------- 1 | .FavouriteItemsCard { 2 | display: flex; 3 | padding: 1vmax; 4 | width: 100%; 5 | justify-content: space-between; 6 | box-sizing: border-box; 7 | } 8 | .FavouriteItemsCard > div > img { 9 | width: 5vmax; 10 | } 11 | .favouritesButton{ 12 | padding: 12px 25px; 13 | border: none; 14 | background-color: #3BB77E; 15 | color: #fff; 16 | cursor: pointer; 17 | } 18 | 19 | .FavouriteItemsCard > div { 20 | display: flex; 21 | flex-direction: column; 22 | } 23 | 24 | .FavouriteItemsCard > div > a { 25 | font: 300 0.9vmax cursive; 26 | color: rgba(24, 24, 24, 0.815); 27 | text-decoration: none; 28 | } 29 | 30 | .FavouriteItemsCard > div > span { 31 | font: 300 0.9vmax "Roboto"; 32 | color: rgba(24, 24, 24, 0.815); 33 | } 34 | 35 | .FavouriteItemsCard > div > p { 36 | color: #3BB77E; 37 | font: 100 0.8vmax "Roboto"; 38 | cursor: pointer; 39 | } 40 | 41 | @media screen and (max-width: 600px) { 42 | .FavouriteItemsCard { 43 | padding: 3vmax; 44 | height: 25vmax; 45 | } 46 | .FavouriteItemsCard > img { 47 | width: 10vmax; 48 | } 49 | 50 | .FavouriteItemsCard > div { 51 | margin: 1vmax 2vmax; 52 | } 53 | 54 | .FavouriteItemsCard > div > a { 55 | font: 300 2vmax cursive; 56 | } 57 | 58 | .FavouriteItemsCard > div > span { 59 | font: 300 1.9vmax "Roboto"; 60 | } 61 | 62 | .FavouriteItemsCard > div > p { 63 | font: 100 1.8vmax "Roboto"; 64 | } 65 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/FavouriteItemsCard.jsx: -------------------------------------------------------------------------------- 1 | import React,{useState,useEffect} from 'react'; 2 | import { Link} from 'react-router-dom'; 3 | import "./FavouriteItemsCard.css"; 4 | import { useSelector,useDispatch } from "react-redux"; 5 | 6 | const FavouriteItemsCard = ({item, deleteFavouriteItems}) => { 7 | const dispatch = useDispatch(); 8 | const { product} = useSelector( 9 | (state) => state.productDetails 10 | ); 11 | 12 | return ( 13 |
14 |
15 | ssa 16 |

deleteFavouriteItems(item.product)}>Remove

17 | {item.name} 21 |
22 | 23 |
24 | {`$ ${item.price}`} 25 |
26 | 27 |
28 |

29 | 30 | {product.Stock < 1 ? "OutOfStock" : "InStock"} 31 | 32 |

33 |
34 | 35 |
36 | 37 | 38 | 39 |
40 | 41 |
42 | ) 43 | } 44 | 45 | export default FavouriteItemsCard 46 | -------------------------------------------------------------------------------- /frontend/src/component/cart/Favourites.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./Favourite.css"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import {deleteFavouriteItemsToCart, deleteOfferFavouriteItemsToCart} from "../../actions/FavouriteAction" 5 | import { Typography } from "@material-ui/core"; 6 | import RemoveShoppingCartIcon from "@material-ui/icons/FavoriteBorder"; 7 | import { Link } from "react-router-dom"; 8 | import FavouriteItemsCard from './FavouriteItemsCard.jsx'; 9 | import MetaData from '../../more/Metadata'; 10 | import Loading from '../../more/Loader'; 11 | import { useState } from "react"; 12 | import BottomTab from '../../more/BottomTab'; 13 | 14 | const Favourite = ({history}) => { 15 | const dispatch = useDispatch(); 16 | 17 | const {loading} = useSelector( 18 | (state) => state.productDetails 19 | ); 20 | const { favouriteItems } = useSelector((state) => state.favourite); 21 | 22 | const deleteFavouriteItems = (id) => { 23 | dispatch(deleteFavouriteItemsToCart(id)); 24 | }; 25 | 26 | return ( 27 | <> 28 | {loading ? ( 29 | 30 | ) : ( 31 | <> 32 | 33 | {favouriteItems.length === 0 ? ( 34 |
35 | 36 | No Items In Favourites 37 | View Products 38 | 39 |
40 | ): ( 41 | <> 42 |
43 |
44 |

Product

45 |

Price

46 |

Stock Status

47 |

Action

48 |
49 | {favouriteItems && 50 | favouriteItems.map((item) => ( 51 |
52 | 53 |
54 | )) 55 | } 56 | 57 |
58 | 59 | )} 60 | 61 | )} 62 | 63 | ) 64 | } 65 | 66 | export default Favourite 67 | -------------------------------------------------------------------------------- /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 | text-align: start; 50 | width: 100%; 51 | box-sizing: border-box; 52 | border: 1px solid rgba(0, 0, 0, 0.267); 53 | border-radius: 4px; 54 | font: 300 0.9vmax cursive; 55 | outline: none; 56 | } 57 | 58 | .shippingForm > div > svg { 59 | position: absolute; 60 | transform: translateX(1vmax); 61 | font-size: 1.6vmax; 62 | color: rgba(0, 0, 0, 0.623); 63 | } 64 | 65 | .shippingBtn { 66 | border: none; 67 | background-color: #3BB77E; 68 | color: white; 69 | font: 300 1vmax "Roboto"; 70 | width: 100%; 71 | padding: 1vmax; 72 | cursor: pointer; 73 | transition: all 0.5s; 74 | outline: none; 75 | margin: 2vmax; 76 | } 77 | 78 | .shippingBtn:hover { 79 | background-color: #3BB77E; 80 | } 81 | 82 | @media screen and (max-width: 600px) { 83 | .shippingBox { 84 | width: 100vw; 85 | height: 95vh; 86 | } 87 | 88 | .shippingHeading { 89 | font: 400 6vw "Roboto"; 90 | padding: 5vw; 91 | } 92 | 93 | .shippingForm { 94 | padding: 11vw; 95 | } 96 | 97 | .shippingForm > div > input, 98 | .shippingForm > div > select { 99 | padding: 5vw 10vw; 100 | font: 300 4vw cursive; 101 | } 102 | 103 | .shippingForm > div > svg { 104 | font-size: 6vw; 105 | transform: translateX(3vw); 106 | } 107 | 108 | .shippingBtn { 109 | font: 300 4vw "Roboto"; 110 | padding: 4vw; 111 | } 112 | } -------------------------------------------------------------------------------- /frontend/src/component/cart/Shipping.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import "./Shipping.css"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import CheckoutSteps from "../cart/CheckoutSteps.jsx"; 5 | import MetaData from "../../more/Metadata"; 6 | import HomeIcon from "@material-ui/icons/Home"; 7 | import PublicIcon from "@material-ui/icons/Public"; 8 | import PhoneIcon from "@material-ui/icons/Phone"; 9 | import TransferWithinAStationIcon from "@material-ui/icons/TransferWithinAStation"; 10 | import { Country, State } from "country-state-city"; 11 | import { saveShippingInfo } from "../../actions/CartAction"; 12 | import BottomTab from "../../more/BottomTab"; 13 | import { ToastContainer, toast } from "react-toastify"; 14 | import "react-toastify/dist/ReactToastify.css"; 15 | 16 | const Shipping = ({ history }) => { 17 | const dispatch = useDispatch(); 18 | 19 | const { shippingInfo } = useSelector((state) => state.cart); 20 | 21 | const [address, setAddress] = useState(shippingInfo.address); 22 | // eslint-disable-next-line 23 | const [state, setState] = useState(shippingInfo.state); 24 | const [country, setCountry] = useState(shippingInfo.country); 25 | // eslint-disable-next-line 26 | const [phoneNo, setPhoneNo] = useState(shippingInfo.phoneNo); 27 | 28 | const shippingSubmit = (e) => { 29 | e.preventDefault(); 30 | 31 | if (phoneNo.length < 11 || phoneNo.length > 11) { 32 | toast.error("Phone Number should be 11digits"); 33 | return; 34 | } 35 | dispatch(saveShippingInfo({ address, state, country, phoneNo })); 36 | history.push("/order/confirm"); 37 | }; 38 | 39 | return ( 40 | <> 41 | 42 | 43 | 44 | 45 |
46 |
47 |

Shipping Details

48 | 49 |
54 |
55 | 56 | setAddress(e.target.value)} 62 | /> 63 |
64 | 65 |
66 | 67 | setPhoneNo(e.target.value)} 73 | size="10" 74 | /> 75 |
76 | 77 |
78 | 79 | 80 | 93 |
94 | 95 | {country && ( 96 |
97 | 98 | 99 | 112 |
113 | )} 114 | 115 | 121 |
122 |
123 |
124 | 135 | 136 | 137 | ); 138 | }; 139 | 140 | export default Shipping; 141 | -------------------------------------------------------------------------------- /frontend/src/component/cart/Success.jsx: -------------------------------------------------------------------------------- 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 Success = () => { 8 | return ( 9 |
10 | 11 | 12 | Your Order has been Placed successfully 13 | View Orders 14 |
15 | ); 16 | }; 17 | 18 | export default Success; -------------------------------------------------------------------------------- /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: #3BB77E; 14 | } 15 | .orderSuccess > p { 16 | font-size: 2vmax; 17 | } 18 | .orderSuccess > a { 19 | background-color: #3BB77E; 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 | } -------------------------------------------------------------------------------- /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: #3BB77E; 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: #3BB77E; 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 | } -------------------------------------------------------------------------------- /frontend/src/component/user/EditProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect,Fragment } from "react"; 2 | import "./EditProfile.css"; 3 | import MailOutlineIcon from "@material-ui/icons/MailOutline"; 4 | import FaceIcon from "@material-ui/icons/Face"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { clearErrors, loadUser, updateProfile } from "../../actions/userAction"; 7 | import Loading from "../../more/Loader"; 8 | import MetaData from "../../more/Metadata"; 9 | import { UPDATE_PROFILE_RESET } from "../../constans/userContans"; 10 | import { ToastContainer, toast } from 'react-toastify'; 11 | 12 | const EditProfile = ({history}) => { 13 | const dispatch = useDispatch(); 14 | 15 | const { user } = useSelector( 16 | (state) => state.user 17 | ); 18 | 19 | const {error, isUpdated, loading } = useSelector((state) => state.profile); 20 | 21 | const [name, setName] = useState(""); 22 | const [email, setEmail] = useState(""); 23 | const [avatar, setAvatar] = useState(); 24 | const [avatarPreview, setAvatarPreview] = useState("/profile.png"); 25 | 26 | 27 | const updateProfileSubmit = (e) => { 28 | e.preventDefault(); 29 | 30 | const myForm = new FormData(); 31 | 32 | myForm.set("name", name); 33 | myForm.set("email", email); 34 | myForm.set("avatar", avatar); 35 | dispatch(updateProfile(myForm)); 36 | }; 37 | 38 | console.log(avatar); 39 | 40 | const updateProfileDataChange = (e) => { 41 | const reader = new FileReader(); 42 | 43 | reader.onload = () => { 44 | if (reader.readyState === 2) { 45 | setAvatarPreview(reader.result); 46 | setAvatar(reader.result); 47 | } 48 | } 49 | reader.readAsDataURL(e.target.files[0]); 50 | }; 51 | 52 | useEffect(() => { 53 | if(user){ 54 | setName(user.name); 55 | setEmail(user.email); 56 | setAvatarPreview(user.avatar.url) 57 | } 58 | 59 | if (error) { 60 | toast.error(error); 61 | dispatch(clearErrors()); 62 | } 63 | 64 | if (isUpdated) { 65 | toast.success("Profile updated successfully"); 66 | dispatch(loadUser()); 67 | 68 | history.push("/me"); 69 | 70 | dispatch({ 71 | type: UPDATE_PROFILE_RESET, 72 | }) 73 | } 74 | }, [dispatch, error, alert, history, isUpdated,user]); 75 | 76 | 77 | return ( 78 | <> 79 | {loading ? () : ( 80 | <> 81 | 82 |
83 |
84 |

Update Profile

85 | 86 |
91 |
92 | 93 | setName(e.target.value)} 100 | /> 101 |
102 |
103 | 104 | setEmail(e.target.value)} 111 | /> 112 |
113 | 114 |
115 | Avatar Preview 116 | 122 |
123 | 128 |
129 |
130 |
131 | 132 | )} 133 | 144 | 145 | ) 146 | } 147 | 148 | export default EditProfile 149 | -------------------------------------------------------------------------------- /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: #4CABDB; 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: #4CABDB; 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 | } -------------------------------------------------------------------------------- /frontend/src/component/user/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useEffect } from "react"; 2 | import "./ForgotPassword.css"; 3 | import Loading from "../../more/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 { ToastContainer, toast } from 'react-toastify'; 8 | import MetaData from "../../more/Metadata"; 9 | 10 | const ForgotPassword = () => { 11 | const dispatch = useDispatch(); 12 | 13 | const { error, message, loading } = useSelector( 14 | (state) => state.forgotPassword 15 | ); 16 | 17 | const [email, setEmail] = useState(""); 18 | 19 | const forgotPasswordSubmit = (e) => { 20 | e.preventDefault(); 21 | 22 | const myForm = new FormData(); 23 | 24 | myForm.set("email", email); 25 | dispatch(forgotPassword(myForm)); 26 | }; 27 | 28 | useEffect(() => { 29 | if (error) { 30 | toast.error(error); 31 | dispatch(clearErrors()); 32 | } 33 | 34 | if (message) { 35 | toast.success(message); 36 | } 37 | }, [dispatch, error, message]); 38 | 39 | return ( 40 | 41 | {loading ? ( 42 | 43 | ) : ( 44 | 45 | 46 |
47 |
48 |

Forgot Password

49 | 50 |
54 |
55 | 56 | setEmail(e.target.value)} 63 | /> 64 |
65 | 66 | 71 |
72 |
73 |
74 |
75 | )} 76 | 87 |
88 | ); 89 | }; 90 | 91 | export default ForgotPassword 92 | -------------------------------------------------------------------------------- /frontend/src/component/user/MyOrder.jsx: -------------------------------------------------------------------------------- 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 { Link } from "react-router-dom"; 7 | import MetaData from "../../more/Metadata"; 8 | import LaunchIcon from "@material-ui/icons/Launch"; 9 | import Loading from "../../more/Loader"; 10 | import BottomTab from "../../more/BottomTab"; 11 | import { ToastContainer, toast } from "react-toastify"; 12 | import "react-toastify/dist/ReactToastify.css"; 13 | 14 | const MyOrder = () => { 15 | const dispatch = useDispatch(); 16 | 17 | const { loading, error, orders } = useSelector((state) => state.myOrder); 18 | 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 === 0 ? 1 : item.orderItems.length, 73 | id: item._id, 74 | status: item.orderStatus, 75 | amount: item.totalPrice, 76 | }); 77 | }); 78 | 79 | useEffect(() => { 80 | if (error) { 81 | toast.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 | )} 106 | 107 | 118 |
119 | ); 120 | }; 121 | 122 | export default MyOrder; 123 | -------------------------------------------------------------------------------- /frontend/src/component/user/MyOrderDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import "./orderDetails.css"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import MetaData from "../../more/Metadata"; 5 | import { Link } from "react-router-dom"; 6 | import { Typography } from "@material-ui/core"; 7 | import { getOrderDetails, clearErrors } from "../../actions/OrderAction"; 8 | import { useAlert } from "react-alert"; 9 | import Loading from "../../more/Loader"; 10 | import BottomTab from "../../more/BottomTab"; 11 | 12 | const MyOrderDetails = ({ match }) => { 13 | const { order, error, loading } = useSelector((state) => state.myOrderDetails); 14 | 15 | const dispatch = useDispatch(); 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.state}`} 54 | 55 |
56 |
57 | Payment 58 |
59 |
60 |

67 |

68 |

71 | 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 | 101 | {order.orderItems && 102 | order.orderItems.map((item) => ( 103 |
104 | Product 105 | 106 | {item.name} 107 | {" "} 108 | 109 | {item.quantity} X ${item.price} ={" "} 110 | ${item.price * item.quantity} 111 | 112 |
113 | ))} 114 | 115 | 116 |
117 |
118 |
119 | 120 | )} 121 | 122 | 123 | ); 124 | }; 125 | 126 | export default MyOrderDetails; -------------------------------------------------------------------------------- /frontend/src/component/user/Profile.css: -------------------------------------------------------------------------------- 1 | .profileContainer { 2 | width: 100%; 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | padding: 1vmax 0px; 7 | } 8 | 9 | img.profile__img { 10 | border-radius: 100%; 11 | height: 150px; 12 | width: 150px; 13 | object-fit: cover; 14 | border: 4px solid #2f6ddf; 15 | } 16 | .edit__profile{ 17 | color: #0a8ef7; 18 | font-family: "Poppins"; 19 | width: 100%; 20 | text-align: end; 21 | padding: .5vmax 0px; 22 | } 23 | .information { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | justify-content: center; 28 | } 29 | .info { 30 | display: flex; 31 | padding: 5px 0px; 32 | } 33 | .change__info { 34 | display: flex; 35 | flex-direction: column; 36 | padding: 2vmax 0; 37 | } 38 | a.settings { 39 | color: #1c6068; 40 | font-family: 'Poppins'; 41 | font-weight: 600; 42 | } -------------------------------------------------------------------------------- /frontend/src/component/user/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Link } from 'react-router-dom' 4 | import Footer from "../../Footer"; 5 | import Header from "../Home/Header"; 6 | import MetaData from "../../more/Metadata"; 7 | import Loading from "../../more/Loader"; 8 | import "./Profile.css"; 9 | import BottomTab from "../../more/BottomTab"; 10 | 11 | const Profile = ({history }) => { 12 | 13 | const { user, loading, isAuthenticated } = useSelector((state) => state.user); 14 | 15 | useEffect(() => { 16 | if (isAuthenticated === false) { 17 | history.push("/login"); 18 | } 19 | }, [history, isAuthenticated]); 20 | 21 | return ( 22 | <> 23 | {loading ? ():( 24 | <> 25 |
26 |
27 | 28 |
29 |
35 |

My Profile

39 | {user.name} 40 | Edit Profile 41 |
42 |
43 |
44 |
45 |
46 |

Full Name:

49 |

{user.name}

50 |
51 |
52 |

Email:

55 |

{user.email}

56 |
57 |
58 |

Joined On:

61 |

{String(user.createdAt).substr(0,10)}

62 |
63 | 64 |
65 | My Orders 66 | Change Password 67 |
68 |
69 |
70 |
71 |