├── Procfile ├── frontend ├── public │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── Empty_Cart.png │ ├── ShoppiKart.png │ ├── images │ │ ├── sample.jpg │ │ ├── Asus_Rog.jpeg │ │ ├── Realme_X7_5G.jpeg │ │ ├── Unifactor_M_Running_Shoes.jpeg │ │ └── Fully_Automatic_Front_Load_with_In-built_Heater.jpeg │ ├── manifest.json │ └── index.html ├── src │ ├── constants │ │ ├── cartConstants.js │ │ ├── orderConstants.js │ │ ├── userConstants.js │ │ └── productConstants.js │ ├── components │ │ ├── Message.js │ │ ├── Loader.js │ │ ├── FormContainer.js │ │ ├── Meta.js │ │ ├── Paginate.js │ │ ├── CustomerRating.js │ │ ├── SearchBox.js │ │ ├── Footer.js │ │ ├── CheckoutSteps.js │ │ ├── Rating.js │ │ ├── Product.js │ │ ├── ProductCarousel.js │ │ └── Header.js │ ├── reportWebVitals.js │ ├── index.js │ ├── index.css │ ├── reducers │ │ ├── cartReducers.js │ │ ├── orderReducers.js │ │ ├── userReducers.js │ │ └── productReducers.js │ ├── actions │ │ ├── cartActions.js │ │ ├── orderActions.js │ │ ├── productActions.js │ │ └── userActions.js │ ├── screens │ │ ├── PaymentScreen.js │ │ ├── HomeScreen.js │ │ ├── LoginScreen.js │ │ ├── ShippingScreen.js │ │ ├── UserListScreen.js │ │ ├── OrderListScreen.js │ │ ├── UserEditScreen.js │ │ ├── RegisterScreen.js │ │ ├── ProductListScreen.js │ │ ├── CartScreen.js │ │ ├── ProductEditScreen.js │ │ ├── ProfileScreen.js │ │ ├── PlaceOrderScreen.js │ │ ├── ProductScreen.js │ │ └── OrderScreen.js │ ├── store.js │ └── App.js ├── package.json └── README.md ├── uploads ├── image-1625072571493.jpg └── image-1625072655121.jpg ├── backend ├── utils │ └── generateTokens.js ├── config │ └── db.js ├── middleware │ ├── errorMiddleware.js │ └── authMiddleware.js ├── routes │ ├── productRoutes.js │ ├── orderRoutes.js │ ├── userRoutes.js │ └── uploadRoutes.js ├── data │ ├── users.js │ └── products.js ├── models │ ├── userModel.js │ ├── productModel.js │ └── orderModel.js ├── email │ └── account.js ├── seeder.js ├── server.js └── controllers │ ├── orderController.js │ ├── productController.js │ └── userController.js ├── .gitignore ├── LICENSE ├── package.json └── README.md /Procfile: -------------------------------------------------------------------------------- 1 | web: node backend/server.js -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/Empty_Cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/Empty_Cart.png -------------------------------------------------------------------------------- /frontend/public/ShoppiKart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/ShoppiKart.png -------------------------------------------------------------------------------- /frontend/public/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/images/sample.jpg -------------------------------------------------------------------------------- /uploads/image-1625072571493.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/uploads/image-1625072571493.jpg -------------------------------------------------------------------------------- /uploads/image-1625072655121.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/uploads/image-1625072655121.jpg -------------------------------------------------------------------------------- /frontend/public/images/Asus_Rog.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/images/Asus_Rog.jpeg -------------------------------------------------------------------------------- /frontend/public/images/Realme_X7_5G.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/images/Realme_X7_5G.jpeg -------------------------------------------------------------------------------- /frontend/public/images/Unifactor_M_Running_Shoes.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/images/Unifactor_M_Running_Shoes.jpeg -------------------------------------------------------------------------------- /frontend/public/images/Fully_Automatic_Front_Load_with_In-built_Heater.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artiam99/MERN-eCommerce-Website/HEAD/frontend/public/images/Fully_Automatic_Front_Load_with_In-built_Heater.jpeg -------------------------------------------------------------------------------- /backend/utils/generateTokens.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | const generateToken = (id) => 4 | { 5 | return jwt.sign({ id } , process.env.JWT_SECRET , { expiresIn: '30d'}) // payload is ID , JWT_SECRET from .env , Token expiresin 30 days 6 | } 7 | 8 | module.exports = generateToken -------------------------------------------------------------------------------- /frontend/src/constants/cartConstants.js: -------------------------------------------------------------------------------- 1 | export const CART_ADD_ITEM = 'CART_ADD_ITEM' 2 | export const CART_REMOVE_ITEM = 'CART_REMOVE_ITEM' 3 | export const CART_SAVE_SHIPPING_ADDRESS = 'CART_SAVE_SHIPPING_ADDRESS' 4 | export const CART_SAVE_PAYMENT_METHOD = 'CART_SAVE_PAYMENT_METHOD' 5 | export const CART_MAKE_EMPTY = 'CART_MAKE_EMPTY' -------------------------------------------------------------------------------- /frontend/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Alert } from 'react-bootstrap' 3 | 4 | const Message = ({variant , children}) => { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | 12 | Message.defaultProps = { variant: 'info' } 13 | 14 | export default Message 15 | -------------------------------------------------------------------------------- /frontend/src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Spinner } from 'react-bootstrap' 3 | 4 | const Loader = () => { 5 | return ( 6 | 7 | Loading... 8 | 9 | ) 10 | } 11 | 12 | export default Loader -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | node_modules/ 6 | 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /frontend/build 15 | 16 | # misc 17 | .DS_Store 18 | .env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './bootstrap.min.css' 4 | import './index.css'; 5 | import App from './App'; 6 | import reportWebVitals from './reportWebVitals'; 7 | import { Provider } from 'react-redux' 8 | import store from './store' 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /frontend/src/components/FormContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container , Row , Col } from 'react-bootstrap' 3 | 4 | const FormContainer = ({ children }) => 5 | { 6 | return ( 7 | 8 | 9 | 10 | {children} 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default FormContainer -------------------------------------------------------------------------------- /backend/config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const connectDB = async () => 4 | { 5 | try 6 | { 7 | const conn = await mongoose.connect(process.env.MONGO_URI , { useUnifiedTopology: true , useNewUrlParser: true , useCreateIndex: true }) 8 | 9 | console.log(`MongoDB is connected: ${conn.connection.host}`) 10 | } 11 | catch(error) 12 | { 13 | console.log(`Error : ${error}`) 14 | 15 | process.exit(1) 16 | } 17 | } 18 | 19 | module.exports = connectDB -------------------------------------------------------------------------------- /backend/middleware/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | const notFound = (req , res , next) => 2 | { 3 | const error = new Error(`Not Found - ${req.originalUrl}`) 4 | res.status(404) 5 | next(error) 6 | } 7 | 8 | 9 | const errorHandler = (error , req , res , next) => 10 | { 11 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode 12 | 13 | res.status(statusCode) 14 | 15 | res.json({ message: error.message , stack: process.env.NODE_ENV === 'production' ? null : error.stack }) 16 | } 17 | 18 | module.exports = { notFound , errorHandler } -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /backend/routes/productRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { getProducts , getProductById, deleteProduct , 3 | createProduct , updateProduct , createProductReview , getTopProducts } = require('../controllers/productController') 4 | const { protect , admin } = require('../middleware/authMiddleware.js') 5 | 6 | const router = express.Router() 7 | 8 | router.route('/').get(getProducts).post(protect , admin , createProduct) 9 | router.route('/:id/reviews').post(protect , createProductReview) 10 | router.get('/top' , getTopProducts) 11 | router.route('/:id').get(getProductById).delete(protect , admin , deleteProduct).put(protect , admin , updateProduct) 12 | 13 | module.exports = router -------------------------------------------------------------------------------- /backend/routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { addOrderItems , getOrderById, updateOrderToPaid, 3 | updateOrderToDelivered , getMyOrders , getOrders } = require('../controllers/orderController') 4 | const { protect , admin } = require('../middleware/authMiddleware') 5 | 6 | const router = express.Router() 7 | 8 | 9 | router.route('/').post(protect , addOrderItems).get(protect , admin , getOrders) 10 | router.route('/myorders').get(protect , getMyOrders) 11 | router.route('/:id').get(protect , getOrderById) 12 | router.route('/:id/pay').put(protect , updateOrderToPaid) 13 | router.route('/:id/deliver').put(protect , updateOrderToDelivered) 14 | 15 | 16 | module.exports = router -------------------------------------------------------------------------------- /backend/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { authUser , registerUser , getUserProfile , updateUserProfile , getUsers, 3 | deleteUser, getUserById, updateUser } = require('../controllers/userController') 4 | const { protect , admin } = require('../middleware/authMiddleware') 5 | 6 | const router = express.Router() 7 | 8 | router.route('/').post(registerUser).get(protect , admin , getUsers) 9 | router.post('/login' , authUser) 10 | router.route('/profile').get(protect , getUserProfile).put(protect , updateUserProfile) 11 | router.route('/:id').delete(protect , admin , deleteUser).get(protect , admin , getUserById).put(protect , admin , updateUser ) 12 | 13 | module.exports = router -------------------------------------------------------------------------------- /backend/data/users.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs') 2 | 3 | const users = 4 | [ 5 | { 6 | name: 'Debarshi Maitra', 7 | email: 'tuhin.dm1999@gmail.com', 8 | password: bcrypt.hashSync('123456' , 10), 9 | isAdmin: true 10 | }, 11 | { 12 | name: 'John Mayer', 13 | email: 'skywalker.luke.dm@gmail.com', 14 | password: bcrypt.hashSync('123456' , 10), 15 | }, 16 | { 17 | name: 'Jimi Hendrix', 18 | email: 'mayer.debarshi@gmail.com', 19 | password: bcrypt.hashSync('123456' , 10), 20 | }, 21 | { 22 | name: 'Eric Clapton', 23 | email: 'yoda99.dm@gmail.com', 24 | password: bcrypt.hashSync('123456' , 10), 25 | } 26 | ] 27 | 28 | module.exports = users -------------------------------------------------------------------------------- /frontend/src/components/Meta.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | 4 | const Meta = ({ title, description, keywords }) => 5 | { 6 | return ( 7 | 8 | {title} 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | Meta.defaultProps = { 18 | title: 'Welcome To ShoppiKart', 19 | description: 'We sell the best products for cheap', 20 | keywords: 'electronics, buy electronics, cheap electroincs, men fashion, women fashion, Home furnishing', 21 | } 22 | 23 | export default Meta -------------------------------------------------------------------------------- /frontend/src/components/Paginate.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Pagination } from 'react-bootstrap' 3 | import { LinkContainer } from 'react-router-bootstrap' 4 | 5 | const Paginate = ({ pages , page , isAdmin = false , keyword = '' }) => 6 | { 7 | return ( 8 | pages > 1 && 9 | ( 10 | {[...Array(pages).keys()].map((x) => 11 | ( 12 | 14 | {x + 1} 15 | 16 | ))} 17 | ) 18 | ) 19 | } 20 | 21 | export default Paginate -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ShoppiKart 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const bcrypt = require('bcryptjs') 3 | 4 | const userSchema = mongoose.Schema({ 5 | 6 | name: { type: String , required: true }, 7 | email: { type: String , required: true , unique: true }, 8 | password: { type: String , required: true}, 9 | isAdmin: { type: Boolean , required: true , default: false }, 10 | }, 11 | { 12 | timestamp: true, 13 | }) 14 | 15 | userSchema.methods.matchPassword = async function(enteredPassword) 16 | { 17 | return await bcrypt.compare(enteredPassword , this.password) 18 | } 19 | 20 | // Middleware to change password before Save 21 | userSchema.pre('save' , async function(next) 22 | { 23 | if(!this.isModified('password')) 24 | { 25 | next() 26 | } 27 | 28 | const salt = await bcrypt.genSalt(10) 29 | 30 | this.password = await bcrypt.hash(this.password , salt) 31 | }) 32 | 33 | const User = mongoose.model('User' , userSchema) 34 | 35 | module.exports = User -------------------------------------------------------------------------------- /frontend/src/components/CustomerRating.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const CustomerRating = ({ rating }) => { 4 | 5 | const color = rating >= 4 ? '#228B22' : rating >= 2 ? 'Orange' : '#db0000' 6 | 7 | return ( 8 |
9 | 10 | {rating} 11 | =1 ? 'fas fa-star' : 'far fa-star'}> 12 | =2 ? 'fas fa-star' : 'far fa-star'}> 13 | =3 ? 'fas fa-star' : 'far fa-star'}> 14 | =4 ? 'fas fa-star' : 'far fa-star'}> 15 | =5 ? 'fas fa-star' : 'far fa-star'}> 16 |
17 | ) 18 | } 19 | 20 | CustomerRating.defaultProps = { color: '#f8e825' } 21 | 22 | export default CustomerRating 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Debarshi Maitra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/src/components/SearchBox.js: -------------------------------------------------------------------------------- 1 | import React , { useState } from 'react' 2 | import { Form , Button , InputGroup } from 'react-bootstrap' 3 | 4 | const SearchBox = ({ history }) => 5 | { 6 | const [keyword , setKeyword] = useState('') 7 | 8 | const submitHandler = (e) => 9 | { 10 | e.preventDefault() 11 | 12 | if(keyword.trim()) 13 | { 14 | history.push(`/search/${keyword}`) 15 | } 16 | else 17 | { 18 | history.push('/') 19 | } 20 | } 21 | 22 | return ( 23 |
24 | 25 | setKeyword(e.target.value)} 26 | placeholder='Search Products...' className='mr-sm-2 ml-sm-5'> 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | ) 35 | } 36 | 37 | export default SearchBox -------------------------------------------------------------------------------- /backend/routes/uploadRoutes.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const multer = require('multer') 4 | 5 | const router = express.Router() 6 | 7 | const storage = multer.diskStorage( 8 | { 9 | destination(req , file , cb) { cb(null , 'uploads/') }, 10 | 11 | filename(req , file , cb) 12 | { 13 | cb(null , `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`) 14 | }, 15 | }) 16 | 17 | function checkFileType(file , cb) 18 | { 19 | const filetypes = /jpg|jpeg|png/ 20 | const extname = filetypes.test(path.extname(file.originalname).toLowerCase()) 21 | const mimetype = filetypes.test(file.mimetype) 22 | 23 | if(extname && mimetype) 24 | { 25 | return cb(null, true) 26 | } 27 | else 28 | { 29 | cb('Images only!') 30 | } 31 | } 32 | 33 | const upload = multer( 34 | { 35 | storage, 36 | 37 | fileFilter: function (req , file , cb) 38 | { 39 | checkFileType(file, cb) 40 | }, 41 | }) 42 | 43 | router.post('/' , upload.single('image') , (req , res) => 44 | { 45 | res.send(`/${req.file.path}`) 46 | }) 47 | 48 | module.exports = router -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | main 2 | { 3 | min-height: 80vh; 4 | } 5 | 6 | h3 7 | { 8 | padding: 1rem 0; 9 | } 10 | 11 | h1 12 | { 13 | font-size: 1.8rem; 14 | padding: 1rem 0; 15 | } 16 | 17 | h2 18 | { 19 | font-size: 1.4rem; 20 | padding: 0.5rem 0; 21 | } 22 | 23 | 24 | .rating span 25 | { 26 | margin: 0.1rem; 27 | } 28 | 29 | a 30 | { 31 | text-decoration: none; 32 | color: #101010; 33 | } 34 | 35 | a:hover 36 | { 37 | font-weight: 900; 38 | color: #101010; 39 | } 40 | 41 | .carousel-item-next, 42 | .carousel-item-prev, 43 | .carousel-item.active { 44 | display: flex; 45 | background-color: white; 46 | box-shadow: 47 | inset 200px 2px 70px -10px #CCC, 48 | inset -200px -2px 70px -10px #CCC; 49 | height: 270px; 50 | } 51 | .carousel-caption { 52 | position: absolute; 53 | top: 0; 54 | } 55 | 56 | .carousel-caption h2 { 57 | color: black; 58 | font-weight: 900; 59 | } 60 | 61 | .carousel img { 62 | height: 200px; 63 | padding: 30px; 64 | margin: 40px; 65 | margin-left: auto; 66 | margin-right: auto; 67 | } 68 | .carousel a { 69 | margin: 0 auto; 70 | } 71 | 72 | @media (max-width: 900px) { 73 | .carousel-caption h2 { 74 | font-size: 2.5vw; 75 | } 76 | } -------------------------------------------------------------------------------- /backend/email/account.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | 3 | const transporter = nodemailer.createTransport( 4 | { 5 | service: 'gmail' , 6 | auth: 7 | { 8 | user: 'shoppikart.maitra@gmail.com' , 9 | pass: 'maytheforcebewithyou' 10 | } 11 | }) 12 | 13 | const sendWelcomeMail = (email , name) => 14 | { 15 | transporter.sendMail({ 16 | to: email , 17 | from: process.env.EMAIL , 18 | subject: 'Thanks for joining ShoppiKart!' , 19 | text: `Welcome : ${name} ... This is Debarshi .. Thank you for visiting my project` 20 | 21 | }) 22 | } 23 | 24 | const sendCancelationMail = (email , name) => 25 | { 26 | transporter.sendMail({ 27 | to: email, 28 | from: process.env.EMAIL , 29 | subject: 'Sorry to see you go!' , 30 | text: `Accout deleted : ${name}` 31 | 32 | }) 33 | } 34 | 35 | module.exports = { 36 | sendWelcomeMail , 37 | sendCancelationMail 38 | } -------------------------------------------------------------------------------- /backend/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const User = require('../models/userModel') 3 | const asyncHandler = require('express-async-handler') 4 | 5 | const protect = asyncHandler(async (req , res , next) => 6 | { 7 | let token 8 | 9 | if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')) 10 | { 11 | try 12 | { 13 | token = req.headers.authorization.split(' ')[1] 14 | 15 | const decoded = jwt.verify(token , process.env.JWT_SECRET) 16 | 17 | req.user = await User.findById(decoded.id).select('-password') 18 | 19 | next() 20 | } 21 | catch(error) 22 | { 23 | res.status(401) 24 | 25 | throw new Error('Not authorized, token failed') 26 | } 27 | } 28 | 29 | if(!token) 30 | { 31 | res.status(401) 32 | 33 | throw new Error('Not authorized, no token') 34 | } 35 | }) 36 | 37 | const admin = (req , res , next) => 38 | { 39 | if(req.user && req.user.isAdmin) 40 | { 41 | next() 42 | } 43 | else 44 | { 45 | res.status(401) 46 | 47 | throw new Error('Not authorized as an admin') 48 | } 49 | } 50 | 51 | module.exports = { protect , admin } -------------------------------------------------------------------------------- /backend/models/productModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const reviewSchema = mongoose.Schema({ 4 | 5 | name: { type: String , required: true }, 6 | rating: { type: Number , required: true }, 7 | comment: { type: String , required: true }, 8 | user: { type: mongoose.Schema.Types.ObjectId , required: true , ref: 'User' }, 9 | 10 | } , 11 | { 12 | timestamp: true, 13 | }) 14 | 15 | const productSchema = mongoose.Schema({ 16 | 17 | user: { type: mongoose.Schema.Types.ObjectId , required: true , ref: 'User' }, // Admins can sell products 18 | name: { type: String , required: true }, 19 | image: { type: String , required: true }, 20 | brand: { type: String , required: true}, 21 | category: { type: String , required: true }, 22 | description: { type: String , required: true }, 23 | reviews: [reviewSchema], 24 | rating: { type: Number , required: true , default: 0 }, 25 | numReviews: { type: Number , required: true , default: 0 }, 26 | price: { type: Number , required: true , default: 0 }, 27 | countInStock: { type: Number , required: true , default: 0 }, 28 | }, 29 | { 30 | timestamp: true, 31 | }) 32 | 33 | const Product = mongoose.model('Product' , productSchema) 34 | 35 | module.exports = Product -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "1.0.0", 4 | "description": "mern shopping app", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node backend/server", 8 | "server": "nodemon backend/server", 9 | "client": "npm start --prefix frontend", 10 | "dev": "concurrently \"npm run server\" \"npm run client\"", 11 | "data:import": "node backend/seeder", 12 | "data:destroy": "node backend/seeder -d", 13 | "build": "cd frontend && npm run build", 14 | "install-frontend": "cd frontend && npm install", 15 | "heroku-postbuild": "npm run install-frontend && npm run build" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "bcryptjs": "^2.4.3", 21 | "body-parser": "^1.19.0", 22 | "cors": "^2.8.5", 23 | "dotenv": "^10.0.0", 24 | "express": "^4.17.1", 25 | "express-async-handler": "^1.1.4", 26 | "formidable": "^1.2.2", 27 | "jsonwebtoken": "^8.5.1", 28 | "mongoose": "^5.12.14", 29 | "morgan": "^1.10.0", 30 | "multer": "^1.4.2", 31 | "nodemailer": "^6.6.2", 32 | "razorpay": "^2.0.6", 33 | "request": "^2.88.2", 34 | "uuid": "^8.3.2" 35 | }, 36 | "devDependencies": { 37 | "concurrently": "^6.2.0", 38 | "nodemon": "^2.0.7" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container , Row , Col } from 'react-bootstrap' 3 | 4 | const Footer = () => { 5 | return ( 6 | 28 | ) 29 | } 30 | 31 | export default Footer 32 | -------------------------------------------------------------------------------- /frontend/src/constants/orderConstants.js: -------------------------------------------------------------------------------- 1 | export const ORDER_CREATE_REQUEST = 'ORDER_CREATE_REQUEST' 2 | export const ORDER_CREATE_SUCCESS = 'ORDER_CREATE_SUCCESS' 3 | export const ORDER_CREATE_FAILURE = 'ORDER_CREATE_FAILURE' 4 | 5 | export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST' 6 | export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS' 7 | export const ORDER_DETAILS_FAILURE = 'ORDER_DETAILS_FAILURE' 8 | 9 | export const ORDER_PAY_REQUEST = 'ORDER_PAY_REQUEST' 10 | export const ORDER_PAY_SUCCESS = 'ORDER_PAY_SUCCESS' 11 | export const ORDER_PAY_FAILURE = 'ORDER_PAY_FAILURE' 12 | export const ORDER_PAY_RESET = 'ORDER_PAY_RESET' 13 | 14 | export const ORDER_LIST_MY_REQUEST = 'ORDER_LIST_MY_REQUEST' 15 | export const ORDER_LIST_MY_SUCCESS = 'ORDER_LIST_MY_SUCCESS' 16 | export const ORDER_LIST_MY_FAILURE = 'ORDER_LIST_MY_FAILURE' 17 | export const ORDER_LIST_MY_RESET = 'ORDER_LIST_MY_RESET' 18 | 19 | export const ORDER_LIST_REQUEST = 'ORDER_LIST_REQUEST' 20 | export const ORDER_LIST_SUCCESS = 'ORDER_LIST_SUCCESS' 21 | export const ORDER_LIST_FAILURE = 'ORDER_LIST_FAILURE' 22 | 23 | export const ORDER_DELIVER_REQUEST = 'ORDER_DELIVER_REQUEST' 24 | export const ORDER_DELIVER_SUCCESS = 'ORDER_DELIVER_SUCCESS' 25 | export const ORDER_DELIVER_FAILURE = 'ORDER_DELIVER_FAILURE' 26 | export const ORDER_DELIVER_RESET = 'ORDER_DELIVER_RESET' -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "proxy": "http://127.0.0.1:5000", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "axios": "^0.21.1", 11 | "react": "^17.0.2", 12 | "react-bootstrap": "^1.6.1", 13 | "react-dom": "^17.0.2", 14 | "react-helmet": "^6.1.0", 15 | "react-paypal-button-v2": "^2.6.3", 16 | "react-redux": "^7.2.4", 17 | "react-router-bootstrap": "^0.25.0", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "4.0.3", 20 | "redux": "^4.1.0", 21 | "redux-devtools-extension": "^2.13.9", 22 | "redux-thunk": "^2.3.0", 23 | "web-vitals": "^1.0.1" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/reducers/cartReducers.js: -------------------------------------------------------------------------------- 1 | import { CART_ADD_ITEM , CART_REMOVE_ITEM , CART_SAVE_SHIPPING_ADDRESS , CART_SAVE_PAYMENT_METHOD , 2 | CART_MAKE_EMPTY } from '../constants/cartConstants' 3 | 4 | export const cartReducer = (state = { cartItems: [] , shippingAddress: {} } , action) => 5 | { 6 | switch (action.type) 7 | { 8 | case CART_ADD_ITEM: 9 | const item = action.payload 10 | 11 | const existItem = state.cartItems.find(x => x.product === item.product) 12 | 13 | if(existItem) 14 | { 15 | return { ...state , cartItems: state.cartItems.map(x => x.product === existItem.product ? item : x) , } 16 | } 17 | else 18 | { 19 | return { ...state , cartItems: [...state.cartItems, item] , } 20 | } 21 | 22 | case CART_REMOVE_ITEM: 23 | return { ...state , cartItems: state.cartItems.filter(x => x.product !== action.payload) , } 24 | 25 | case CART_MAKE_EMPTY: 26 | return { ...state , cartItems: [] } 27 | 28 | case CART_SAVE_SHIPPING_ADDRESS: 29 | return { ...state , shippingAddress: action.payload } 30 | 31 | case CART_SAVE_PAYMENT_METHOD: 32 | return { ...state , paymentMethod: action.payload } 33 | 34 | default: 35 | return state 36 | } 37 | } -------------------------------------------------------------------------------- /frontend/src/components/CheckoutSteps.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Nav } from 'react-bootstrap' 3 | import { LinkContainer } from 'react-router-bootstrap' 4 | 5 | const CheckoutSteps = ({ step1 , step2 , step3 , step4 }) => 6 | { 7 | return ( 8 | 29 | ) 30 | } 31 | 32 | export default CheckoutSteps 33 | -------------------------------------------------------------------------------- /frontend/src/components/Rating.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Rating = ({ rating , numReviews}) => { 4 | 5 | const color = rating >= 4 ? '#228B22' : rating >= 2 ? 'Orange' : rating !== 0 ? '#db0000' : 'Gray' 6 | 7 | const ratingNumber = Number(Number(rating).toFixed(0)).toFixed(1) === Number(rating).toFixed(1) ? 8 | Number(rating).toFixed(0) : Number(rating).toFixed(1) 9 | 10 | return ( 11 |
12 | 13 | {ratingNumber} 14 | =1 ? 'fas fa-star' : rating >= 0.5 ? 'fas fa-star-half-alt' : 'far fa-star'}> 15 | =2 ? 'fas fa-star' : rating >= 1.5 ? 'fas fa-star-half-alt' : 'far fa-star'}> 16 | =3 ? 'fas fa-star' : rating >= 2.5 ? 'fas fa-star-half-alt' : 'far fa-star'}> 17 | =4 ? 'fas fa-star' : rating >= 3.5 ? 'fas fa-star-half-alt' : 'far fa-star'}> 18 | =5 ? 'fas fa-star' : rating >= 4.5 ? 'fas fa-star-half-alt' : 'far fa-star'}> 19 | {` (${numReviews} reviews)`} 20 | 21 |
22 | ) 23 | } 24 | 25 | Rating.defaultProps = { color: '#f8e825' } 26 | 27 | export default Rating 28 | -------------------------------------------------------------------------------- /frontend/src/components/Product.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Card } from 'react-bootstrap' 4 | import Rating from './Rating' 5 | 6 | const format = num => { 7 | const n = String(num), 8 | p = n.indexOf('.') 9 | return n.replace( 10 | /\d(?=(?:\d{3})+(?:\.|$))/g, 11 | (m, i) => p < 0 || i < p ? `${m},` : m 12 | ) 13 | } 14 | 15 | const Product = (props) => { 16 | return ( 17 | 18 | 19 | 20 | 21 | {props.product.name} 23 | 24 | 25 | 26 | 27 | 28 | 29 | {props.product.name} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ₹{format(props.product.price)} 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | 47 | export default Product 48 | -------------------------------------------------------------------------------- /frontend/src/constants/userConstants.js: -------------------------------------------------------------------------------- 1 | export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST' 2 | export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS' 3 | export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE' 4 | export const USER_LOGOUT = 'USER_LOGOUT' 5 | 6 | export const USER_REGISTER_REQUEST = 'USER_REGISTER_REQUEST' 7 | export const USER_REGISTER_SUCCESS = 'USER_REGISTER_SUCCESS' 8 | export const USER_REGISTER_FAILURE = 'USER_REGISTER_FAILURE' 9 | 10 | export const USER_DETAILS_REQUEST = 'USER_DETAILS_REQUEST' 11 | export const USER_DETAILS_SUCCESS = 'USER_DETAILS_SUCCESS' 12 | export const USER_DETAILS_FAILURE = 'USER_DETAILS_FAILURE' 13 | export const USER_DETAILS_RESET = 'USER_DETAILS_RESET' 14 | 15 | export const USER_UPDATE_PROFILE_REQUEST = 'USER_UPDATE_PROFILE_REQUEST' 16 | export const USER_UPDATE_PROFILE_SUCCESS = 'USER_UPDATE_PROFILE_SUCCESS' 17 | export const USER_UPDATE_PROFILE_FAILURE = 'USER_UPDATE_PROFILE_FAILURE' 18 | export const USER_UPDATE_PROFILE_RESET = 'USER_UPDATE_PROFILE_RESET' 19 | 20 | export const USER_LIST_REQUEST = 'USER_LIST_REQUEST' 21 | export const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS' 22 | export const USER_LIST_FAILURE = 'USER_LIST_FAILURE' 23 | export const USER_LIST_RESET = 'USER_LIST_RESET' 24 | 25 | export const USER_DELETE_REQUEST = 'USER_DELETE_REQUEST' 26 | export const USER_DELETE_SUCCESS = 'USER_DELETE_SUCCESS' 27 | export const USER_DELETE_FAILURE = 'USER_DELETE_FAILURE' 28 | 29 | export const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST' 30 | export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS' 31 | export const USER_UPDATE_FAILURE = 'USER_UPDATE_FAILURE' 32 | export const USER_UPDATE_RESET = 'USER_UPDATE_RESET' -------------------------------------------------------------------------------- /backend/seeder.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const dotenv = require('dotenv') 3 | const users = require('./data/users') 4 | const products = require('./data/products') 5 | const User = require('./models/userModel') 6 | const Product = require('./models/productModel') 7 | const Order = require('./models/orderModel') 8 | const connectDB = require('./config/db') 9 | 10 | dotenv.config() 11 | 12 | connectDB() 13 | 14 | const importData = async () => 15 | { 16 | try 17 | { 18 | await User.deleteMany() 19 | await Product.deleteMany() 20 | await Order.deleteMany() 21 | 22 | const createdUsers = await User.insertMany(users) 23 | 24 | const adminUser = createdUsers[0]._id 25 | 26 | const sampleProducts = products.map(product => 27 | { 28 | return { ...product , user: adminUser } 29 | }) 30 | 31 | await Product.insertMany(sampleProducts) 32 | 33 | console.log('Data Imported') 34 | 35 | process.exit() 36 | } 37 | catch (error) 38 | { 39 | console.log(`Error: ${error}`) 40 | process.exit() 41 | } 42 | } 43 | 44 | const destroyData = async () => 45 | { 46 | try 47 | { 48 | await User.deleteMany() 49 | await Product.deleteMany() 50 | await Order.deleteMany() 51 | 52 | console.log('Data Destroyed') 53 | 54 | process.exit() 55 | } 56 | catch (error) 57 | { 58 | console.log(`Error: ${error}`) 59 | 60 | process.exit() 61 | } 62 | } 63 | 64 | if(process.argv[2] === '-d') 65 | { 66 | destroyData() 67 | } 68 | else 69 | { 70 | importData() 71 | } -------------------------------------------------------------------------------- /frontend/src/constants/productConstants.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = 'PRODUCT_LIST_REQUEST' 2 | export const PRODUCT_LIST_SUCCESS = 'PRODUCT_LIST_SUCCESS' 3 | export const PRODUCT_LIST_FAILURE = 'PRODUCT_LIST_FAILURE' 4 | 5 | export const PRODUCT_DETAILS_REQUEST = 'PRODUCT_DETAILS_REQUEST' 6 | export const PRODUCT_DETAILS_SUCCESS = 'PRODUCT_DETAILS_SUCCESS' 7 | export const PRODUCT_DETAILS_FAILURE = 'PRODUCT_DETAILS_FAILURE' 8 | 9 | export const PRODUCT_DELETE_REQUEST = 'PRODUCT_DELETE_REQUEST' 10 | export const PRODUCT_DELETE_SUCCESS = 'PRODUCT_DELETE_SUCCESS' 11 | export const PRODUCT_DELETE_FAILURE = 'PRODUCT_DELETE_FAILURE' 12 | 13 | export const PRODUCT_CREATE_REQUEST = 'PRODUCT_CREATE_REQUEST' 14 | export const PRODUCT_CREATE_SUCCESS = 'PRODUCT_CREATE_SUCCESS' 15 | export const PRODUCT_CREATE_FAILURE = 'PRODUCT_CREATE_FAILURE' 16 | export const PRODUCT_CREATE_RESET = 'PRODUCT_CREATE_RESET' 17 | 18 | export const PRODUCT_UPDATE_REQUEST = 'PRODUCT_UPDATE_REQUEST' 19 | export const PRODUCT_UPDATE_SUCCESS = 'PRODUCT_UPDATE_SUCCESS' 20 | export const PRODUCT_UPDATE_FAILURE = 'PRODUCT_UPDATE_FAILURE' 21 | export const PRODUCT_UPDATE_RESET = 'PRODUCT_UPDATE_RESET' 22 | 23 | export const PRODUCT_CREATE_REVIEW_REQUEST = 'PRODUCT_CREATE_REVIEW_REQUEST' 24 | export const PRODUCT_CREATE_REVIEW_SUCCESS = 'PRODUCT_CREATE_REVIEW_SUCCESS' 25 | export const PRODUCT_CREATE_REVIEW_FAILURE = 'PRODUCT_CREATE_REVIEW_FAILURE' 26 | export const PRODUCT_CREATE_REVIEW_RESET = 'PRODUCT_CREATE_REVIEW_RESET' 27 | 28 | export const PRODUCT_TOP_REQUEST = 'PRODUCT_TOP_REQUEST' 29 | export const PRODUCT_TOP_SUCCESS = 'PRODUCT_TOP_SUCCESS' 30 | export const PRODUCT_TOP_FAILURE = 'PRODUCT_TOP_FAILURE' -------------------------------------------------------------------------------- /backend/models/orderModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const orderSchema = mongoose.Schema({ 4 | 5 | user: { type: mongoose.Schema.Types.ObjectId , required: true , ref: 'User' }, 6 | orderItems: [{ 7 | name: { type: String , required: true }, 8 | qty: { type: Number , required: true }, 9 | image: { type: String , required: true }, 10 | price: { type: Number , required: true }, 11 | product: { type: mongoose.Schema.Types.ObjectId , required: true , ref: 'product' } 12 | }], 13 | shippingAddress: { address: { type: String , required: true}, 14 | city: { type: String , required: true}, 15 | postalCode: { type: String , required: true}, 16 | country: { type: String , required: true} 17 | }, 18 | paymentMethod: { type: String , required: true}, 19 | paymentResult: { id: { type: String } , status: { type: String } , update_time: { type: String } , email_address: { type: String } }, 20 | taxPrice: { type: String , required: true , default: 0.0 }, 21 | shippingPrice: { type: String , required: true , default: 0.0 }, 22 | totalPrice: { type: String , required: true , default: 0.0 }, 23 | isPaid: { type: Boolean , required: true , default: false }, 24 | paidAt: { type: Date}, 25 | isDelivered: { type: Boolean , required: true , default: false }, 26 | deliveredAt: { type: Date}, 27 | isAdmin: { type: Boolean , required: true , default: false }, 28 | }, 29 | { 30 | timestamps: true, 31 | }) 32 | 33 | const Order = mongoose.model('Order' , orderSchema) 34 | 35 | module.exports = Order -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const dotenv = require('dotenv') 3 | const path = require('path') 4 | const connectDB = require('./config/db') 5 | const produtcRoutes = require('./routes/productRoutes') 6 | const userRoutes = require('./routes/userRoutes') 7 | const orderRoutes = require('./routes/orderRoutes') 8 | const uploadRoutes = require('./routes/uploadRoutes') 9 | const { notFound , errorHandler } = require('./middleware/errorMiddleware') 10 | const morgan = require('morgan') 11 | 12 | dotenv.config() 13 | 14 | connectDB() 15 | 16 | const app = express() 17 | 18 | if(process.env.NODE_ENV === 'development') 19 | { 20 | app.use(morgan('dev')) 21 | } 22 | 23 | app.use(express.json()) 24 | 25 | app.use('/api/products' , produtcRoutes) // everything will be mounted to this url prefix 26 | app.use('/api/users' , userRoutes) 27 | app.use('/api/orders' , orderRoutes) 28 | app.use('/api/upload' , uploadRoutes) 29 | 30 | app.get('/api/config/paypal' , (req , res) => res.send(process.env.PAYPAL_CLIENT_ID)) 31 | 32 | const dirname = path.join(__dirname , '../') 33 | 34 | app.use('/uploads', express.static(path.join(dirname , '/uploads'))) 35 | 36 | if(process.env.NODE_ENV === 'production') 37 | { 38 | app.use(express.static(path.join(dirname , '/frontend/build'))) 39 | 40 | app.get('*' , (req , res) => res.sendFile(path.resolve(dirname , 'frontend' , 'build' , 'index.html'))) 41 | } 42 | else 43 | { 44 | app.get('/' , (req , res) => 45 | { 46 | res.send('API is running....') 47 | }) 48 | } 49 | 50 | app.use(notFound) 51 | 52 | app.use(errorHandler) 53 | 54 | const PORT = process.env.PORT || 5000 55 | 56 | app.listen(PORT , console.log(`Server is running in ${process.env.NODE_ENV} on port: ${process.env.PORT}`)) -------------------------------------------------------------------------------- /frontend/src/components/ProductCarousel.js: -------------------------------------------------------------------------------- 1 | import React , { useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Carousel , Image } from 'react-bootstrap' 4 | import { useDispatch , useSelector } from 'react-redux' 5 | import Loader from './Loader' 6 | import Message from './Message' 7 | import { listTopProducts } from '../actions/productActions' 8 | 9 | const format = num => { 10 | const n = String(num), 11 | p = n.indexOf('.') 12 | return n.replace( 13 | /\d(?=(?:\d{3})+(?:\.|$))/g, 14 | (m, i) => p < 0 || i < p ? `${m},` : m 15 | ) 16 | } 17 | 18 | const ProductCarousel = () => 19 | { 20 | const dispatch = useDispatch() 21 | 22 | const productTopRated = useSelector((state) => state.productTopRated) 23 | const { loading , error , products } = productTopRated 24 | 25 | useEffect(() => 26 | { 27 | dispatch(listTopProducts()) 28 | 29 | } , [dispatch]) 30 | 31 | return loading ? () : error ? ({error}) : 32 | ( 33 | 34 | {products.map((product) => 35 | ( 36 | 37 | 38 | {product.name} 39 | 40 | 41 | 42 |

{product.name} (₹{format(product.price)})

43 | 44 |
45 | 46 |
47 | ))} 48 |
49 | ) 50 | } 51 | 52 | export default ProductCarousel -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MERN-eCommerce-Website 2 | 3 | Live website link: https://shoppikart-maitra.herokuapp.com 4 | 5 | 6 | ### About 7 | 8 | This MERN Full stack web application is an eCommerce Platform built with __React.js__ for frontend, 9 | __Express.js__ for creating REST API, __MongoDB__ for database, __React-Bootstrap__ for the UI library and __Redux__ for managing application states. It supports authentication with JSON Web Token for admin and customer users. Customers can search products by name or brand and Admins can add new products & edit details. It is deployed on __Heroku__. 10 | 11 | 12 | ### Home Screen 13 | 14 | ![Screenshot (62)](https://user-images.githubusercontent.com/47227715/124158170-34860200-dab7-11eb-8f18-d3a7233ad606.png) 15 | 16 | ![Screenshot (63)](https://user-images.githubusercontent.com/47227715/124158240-4c5d8600-dab7-11eb-9c7a-a8d9b17ddcce.png) 17 | 18 | ### Product Screen 19 | 20 | ![Screenshot (51)](https://user-images.githubusercontent.com/47227715/124158350-6d25db80-dab7-11eb-85ae-14e3d12814d2.png) 21 | 22 | ### Cart Screen 23 | 24 | ![Screenshot (52)](https://user-images.githubusercontent.com/47227715/124158409-82026f00-dab7-11eb-8d7b-2ee82bea4c3a.png) 25 | 26 | ### User Profile Screen 27 | 28 | ![Screenshot (53)](https://user-images.githubusercontent.com/47227715/124158490-99d9f300-dab7-11eb-941d-989436862591.png) 29 | 30 | 31 | ### Customer Order Screen 32 | 33 | ![Screenshot (57)](https://user-images.githubusercontent.com/47227715/124158581-b6762b00-dab7-11eb-8308-c5665e38b8bd.png) 34 | 35 | ### Admin Order Screen 36 | 37 | ![Screenshot (58)](https://user-images.githubusercontent.com/47227715/124158607-bfff9300-dab7-11eb-96cf-e15b357b212a.png) 38 | 39 | ### Admin Product List Screen 40 | 41 | ![Screenshot (59)](https://user-images.githubusercontent.com/47227715/124158743-e58c9c80-dab7-11eb-9327-efb14ce753a7.png) 42 | 43 | -------------------------------------------------------------------------------- /frontend/src/actions/cartActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { CART_ADD_ITEM, CART_REMOVE_ITEM , CART_SAVE_SHIPPING_ADDRESS , CART_SAVE_PAYMENT_METHOD , 3 | CART_MAKE_EMPTY } from '../constants/cartConstants' 4 | 5 | export const addToCart = (id, qty) => async (dispatch , getState) => 6 | { 7 | const { data } = await axios.get(`/api/products/${id}`) 8 | 9 | dispatch({ 10 | type: CART_ADD_ITEM, 11 | payload: { 12 | product: data._id, 13 | name: data.name, 14 | image: data.image, 15 | price: data.price, 16 | countInStock: data.countInStock, 17 | qty, 18 | }, 19 | }) 20 | 21 | localStorage.setItem('cartItems' , JSON.stringify(getState().cart.cartItems)) 22 | } 23 | 24 | export const removeFromCart = (id) => async (dispatch , getState) => 25 | { 26 | dispatch({ 27 | type: CART_REMOVE_ITEM, 28 | payload: id 29 | }) 30 | 31 | localStorage.setItem('cartItems' , JSON.stringify(getState().cart.cartItems)) 32 | } 33 | 34 | export const emptyCart = () => async (dispatch , getState) => 35 | { 36 | dispatch({ type: CART_MAKE_EMPTY , }) 37 | 38 | localStorage.setItem('cartItems' , JSON.stringify(getState().cart.cartItems)) 39 | } 40 | 41 | export const saveShippingAddress = (data) => async (dispatch) => 42 | { 43 | dispatch({ 44 | type: CART_SAVE_SHIPPING_ADDRESS, 45 | payload: data 46 | }) 47 | 48 | localStorage.setItem('shippingAddress' , JSON.stringify(data)) 49 | } 50 | 51 | export const savePaymentMethod = (data) => async (dispatch) => 52 | { 53 | dispatch({ 54 | type: CART_SAVE_PAYMENT_METHOD, 55 | payload: data 56 | }) 57 | 58 | localStorage.setItem('paymentMethod' , JSON.stringify(data)) 59 | } 60 | -------------------------------------------------------------------------------- /backend/data/products.js: -------------------------------------------------------------------------------- 1 | const products = 2 | [ 3 | { 4 | name: 'ASUS ROG Zephyrus G14', 5 | image: '/images/Asus_Rog.jpeg', 6 | description: 7 | 'Ryzen 9 Octa Core 4900HS \n16 GB RAM | 512 GB SSD \nWindows 10 Home \n6 GB Graphics \nNVIDIA GeForce GTX 1660Ti/60 Hz \n14 inch | Eclipse Grey | 1.6 Kg \nWith MS Office', 8 | brand: 'ASUS', 9 | category: 'Electronics', 10 | price: 109003, 11 | countInStock: 3, 12 | rating: 4.5, 13 | numReviews: 12 14 | }, 15 | { 16 | name: 'Realme X7 5G', 17 | description: 18 | '6 GB RAM | 128 GB ROM \n16.33 cm (6.43 inch) Full HD+ Display \n64MP + 8MP + 2MP \n16MP Front Camera \n4310 mAh Battery \nMediaTek Dimensity 800U Processor \nSuper AMOLED Display \n50W Fast Charging \nLightweight (176 g) and Slim', 19 | image: '/images/Realme_X7_5G.jpeg', 20 | brand: 'Realme', 21 | category: 'Electronics', 22 | price: 17999, 23 | countInStock: 14, 24 | rating: 3, 25 | numReviews: 11 26 | }, 27 | { 28 | name: 'Unifactor M Running Shoes For Men', 29 | image: '/images/Unifactor_M_Running_Shoes.jpeg', 30 | description: 31 | 'Color: Blue , White \nOuter material: Synthetic \nModel name: Unifactor M \nIdeal for: Men \nOccasion: Sports', 32 | brand: 'ADIDAS ', 33 | category: 'Men', 34 | price: 1469, 35 | countInStock: 0, 36 | rating: 3.7, 37 | numReviews: 18 38 | }, 39 | { 40 | name: 'SAMSUNG 7 kg 5 Rating Fully Automatic Front Load with In-built Heater', 41 | image: '/images/Fully_Automatic_Front_Load_with_In-built_Heater.jpeg', 42 | description: 43 | 'The Samsung fully automatic front load washing machine offers a powerful washing experience with low energy consumption. With Digital Inverter Technology, this washing machine enables an energy-efficient performance while minimising operational noise. Moreover, with the Quick Wash program, you can do your laundry in just a few minutes and save time on busy workdays.', 44 | brand: 'Samsung', 45 | category: 'Home & Furniture', 46 | price: 32489, 47 | countInStock: 2, 48 | rating: 1.8, 49 | numReviews: 23 50 | } 51 | ] 52 | 53 | module.exports = products -------------------------------------------------------------------------------- /frontend/src/screens/PaymentScreen.js: -------------------------------------------------------------------------------- 1 | import React , { useState } from 'react' 2 | import { Form , Button , Col } from 'react-bootstrap' 3 | import { useDispatch , useSelector } from 'react-redux' 4 | import FormContainer from '../components/FormContainer' 5 | import CheckoutSetps from '../components/CheckoutSteps' 6 | import { savePaymentMethod } from '../actions/cartActions' 7 | 8 | const PaymentScreen = ({ history }) => 9 | { 10 | const cart = useSelector(state => state.cart) 11 | const { shippingAddress } = cart 12 | 13 | if(!shippingAddress) 14 | { 15 | history.push('/shipping') 16 | } 17 | 18 | const [paymentMethod , setPaymentMethod] = useState('Paypal') 19 | 20 | const dispatch = useDispatch() 21 | 22 | const submitHandler = (e) => 23 | { 24 | e.preventDefault() 25 | 26 | dispatch(savePaymentMethod(paymentMethod)) 27 | 28 | history.push('/placeorder') 29 | } 30 | 31 | return ( 32 | 33 | 34 |

Payment Method

35 | 36 |
37 |
38 | 39 | Select Method 40 | 41 |

42 | 43 | 44 | setPaymentMethod(e.target.value)} defaultChecked> 46 | 47 | 48 | {/* setPaymentMethod(e.target.value)}> 50 | */} 51 | 52 |
53 | 54 |
55 | 56 | 57 |
58 |
59 | ) 60 | } 61 | 62 | export default PaymentScreen 63 | -------------------------------------------------------------------------------- /frontend/src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React , { useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useDispatch , useSelector } from 'react-redux' 4 | import { Row , Col } from 'react-bootstrap' 5 | import Product from '../components/Product' 6 | import Message from '../components/Message' 7 | import Loader from '../components/Loader' 8 | import { listProducts } from '../actions/productActions' 9 | import Paginate from '../components/Paginate' 10 | import Meta from '../components/Meta' 11 | import ProductCarousel from '../components/ProductCarousel' 12 | 13 | const HomeScreen = ({ match }) => 14 | { 15 | const keyword = match.params.keyword 16 | 17 | const pageNumber = match.params.pageNumber || 1 18 | 19 | // Redux 20 | 21 | const dispatch = useDispatch() 22 | 23 | const productList = useSelector(state => state.productList) 24 | const { loading , error , products , page , pages } = productList 25 | 26 | useEffect(() => 27 | { 28 | dispatch(listProducts(keyword , pageNumber)) 29 | 30 | } , [dispatch , keyword , pageNumber]) 31 | 32 | 33 | // Hooks 34 | 35 | // const [products , setProducts] = useState([]) 36 | 37 | // useEffect(() => 38 | // { 39 | // const fetchProducts = async () => 40 | // { 41 | // const { data } = await axios.get('/api/products') 42 | 43 | // setProducts(data) 44 | // } 45 | 46 | // fetchProducts() 47 | 48 | // }, []) 49 | 50 | return ( 51 |
52 | 53 | 54 | {!keyword ? () : (Go Back)} 55 | 56 |

LATEST PRODUCTS

57 | {loading ? : error ? {error} : 58 | (
59 | 60 | {products.map(product => 61 | ( 62 | 63 | 64 | 65 | ))} 66 | 67 | 68 | 69 |
)} 70 | 71 |
72 | ) 73 | } 74 | 75 | export default HomeScreen 76 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore , combineReducers , applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | import { productListReducer , productDetailsReducer , productDeleteReducer , productCreateReducer , 5 | productUpdateReducer , productReviewCreateReducer , productTopRatedReducer } from './reducers/productReducers' 6 | import { cartReducer } from './reducers/cartReducers' 7 | import { userLoginReducer , userRegisterReducer , userDetailsReducer , userUpdateProfileReducer , 8 | userListReducer , userDeleteReducer , userUpdateReducer } from './reducers/userReducers' 9 | import { orderCreateReducer , orderDetailsReducer , orderPayReducer , 10 | orderDeliverReducer , orderListMyReducer , orderListReducer } from './reducers/orderReducers' 11 | 12 | const reducer = combineReducers({ 13 | productList: productListReducer, 14 | productDetails: productDetailsReducer, 15 | productDelete: productDeleteReducer, 16 | productCreate: productCreateReducer, 17 | productUpdate: productUpdateReducer, 18 | productReviewCreate: productReviewCreateReducer, 19 | productTopRated: productTopRatedReducer, 20 | cart: cartReducer, 21 | userLogin: userLoginReducer, 22 | userRegister: userRegisterReducer, 23 | userDetails: userDetailsReducer, 24 | userUpdateProfile: userUpdateProfileReducer, 25 | userList: userListReducer, 26 | userDelete: userDeleteReducer, 27 | userUpdate: userUpdateReducer, 28 | orderCreate: orderCreateReducer, 29 | orderDetails: orderDetailsReducer, 30 | orderPay: orderPayReducer, 31 | orderDeliver: orderDeliverReducer, 32 | orderListMy: orderListMyReducer, 33 | orderList: orderListReducer, 34 | }) 35 | 36 | 37 | const cartItemsFromStorage = localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : [] 38 | 39 | const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null 40 | 41 | const shippingAddressFromStorage = localStorage.getItem('shippingAddress') ? JSON.parse(localStorage.getItem('shippingAddress')) : {} 42 | 43 | const paymentMethodFromStorage = localStorage.getItem('paymentMethod') ? JSON.parse(localStorage.getItem('paymentMethod')) : '' 44 | 45 | 46 | const initialState = { 47 | cart: { cartItems: cartItemsFromStorage , shippingAddress: shippingAddressFromStorage , paymentMethod: paymentMethodFromStorage }, 48 | userLogin: { userInfo: userInfoFromStorage }, 49 | } 50 | 51 | const middleware = [thunk] 52 | 53 | const store = createStore(reducer , initialState , composeWithDevTools(applyMiddleware(...middleware))) 54 | 55 | export default store -------------------------------------------------------------------------------- /frontend/src/screens/LoginScreen.js: -------------------------------------------------------------------------------- 1 | import React , { useState , useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Form , Button , Row , Col } from 'react-bootstrap' 4 | import { useDispatch , useSelector } from 'react-redux' 5 | import Message from '../components/Message' 6 | import Loader from '../components/Loader' 7 | import { login } from '../actions/userActions' 8 | import FormContainer from '../components/FormContainer' 9 | 10 | const LoginScreen = ({ location , history }) => 11 | { 12 | const [email , setEmail] = useState('') 13 | const [password , setPassword] = useState('') 14 | 15 | const dispatch = useDispatch() 16 | 17 | const userLogin = useSelector(state => state.userLogin) 18 | const { loading , error , userInfo } = userLogin 19 | 20 | const redirect = location.search ? location.search.split('=')[1] : '/' 21 | 22 | useEffect(() => 23 | { 24 | if(userInfo) 25 | { 26 | history.push(redirect) 27 | } 28 | 29 | }, [history , userInfo , redirect]) 30 | 31 | const submitHandler = (e) => 32 | { 33 | e.preventDefault() 34 | 35 | dispatch(login(email , password)) 36 | } 37 | 38 | 39 | return ( 40 | 41 |

SIGN IN

42 | {error && {error}} 43 | {loading && } 44 |
45 | 46 | Email Address 47 | setEmail(e.target.value)}> 48 | 49 | 50 | 51 | 52 | Password 53 | setPassword(e.target.value)}> 55 | 56 | 57 | 58 |
59 | 60 | 61 |
62 | 63 | 64 | 65 | New Customer? Register 66 | 67 | 68 |
69 | ) 70 | } 71 | 72 | export default LoginScreen 73 | -------------------------------------------------------------------------------- /frontend/src/screens/ShippingScreen.js: -------------------------------------------------------------------------------- 1 | import React , { useState } from 'react' 2 | import { Form , Button } from 'react-bootstrap' 3 | import { useDispatch , useSelector } from 'react-redux' 4 | import FormContainer from '../components/FormContainer' 5 | import CheckoutSetps from '../components/CheckoutSteps' 6 | import { saveShippingAddress } from '../actions/cartActions' 7 | 8 | const ShippingScreen = ({ history }) => 9 | { 10 | const cart = useSelector(state => state.cart) 11 | const { shippingAddress } = cart 12 | 13 | const [address , setAddress] = useState(shippingAddress.address) 14 | const [city , setCity] = useState(shippingAddress.city) 15 | const [postalCode , setPostalCode] = useState(shippingAddress.postalCode) 16 | const [country , setCountry] = useState(shippingAddress.country) 17 | 18 | const dispatch = useDispatch() 19 | 20 | const submitHandler = (e) => 21 | { 22 | e.preventDefault() 23 | 24 | dispatch(saveShippingAddress({ address , city , postalCode , country })) 25 | 26 | history.push('/payment') 27 | } 28 | 29 | return ( 30 | 31 | 32 |

SHIPPING

33 |
34 | 35 | Address 36 | setAddress(e.target.value)}> 37 | 38 | 39 | 40 | 41 | City 42 | setCity(e.target.value)}> 43 | 44 | 45 | 46 | 47 | Postal Code 48 | setPostalCode(e.target.value)}> 49 | 50 | 51 | 52 | 53 | Country 54 | setCountry(e.target.value)}> 55 | 56 | 57 | 58 |
59 | 60 | 61 |
62 |
63 | ) 64 | } 65 | 66 | export default ShippingScreen 67 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router , Route } from 'react-router-dom' 3 | import { Container } from 'react-bootstrap' 4 | import Header from './components/Header' 5 | import Footer from './components/Footer' 6 | import HomeScreen from './screens/HomeScreen' 7 | import ProductScreen from './screens/ProductScreen' 8 | import CartScreen from './screens/CartScreen' 9 | import LoginScreen from './screens/LoginScreen' 10 | import RegisterScreen from './screens/RegisterScreen' 11 | import ProfileScreen from './screens/ProfileScreen' 12 | import ShippingScreen from './screens/ShippingScreen' 13 | import PaymentScreen from './screens/PaymentScreen' 14 | import PlaceOrderScreen from './screens/PlaceOrderScreen' 15 | import OrderScreen from './screens/OrderScreen' 16 | import UserListScreen from './screens/UserListScreen' 17 | import UserEditScreen from './screens/UserEditScreen' 18 | import ProductListScreen from './screens/ProductListScreen' 19 | import ProductEditScreen from './screens/ProductEditScreen' 20 | import OrderListScreen from './screens/OrderListScreen' 21 | 22 | const App = () => 23 | { 24 | return ( 25 | 26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 |