├── uploads ├── file.txt ├── image-1623865152147.jpg └── image-1623865318246.jpg ├── Procfile ├── client ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── images │ │ ├── pizza-1.jpg │ │ ├── pizza-2.jpg │ │ ├── pizza-3.jpg │ │ ├── pizza-4.jpg │ │ ├── pizza-5.png │ │ ├── pizza-6.jpg │ │ ├── pizza-7.jpg │ │ └── sample.jpg │ ├── manifest.json │ └── index.html ├── src │ ├── components │ │ ├── AwesomeFeatures.module.css │ │ ├── HeroSection.module.css │ │ ├── Message.js │ │ ├── Header.module.css │ │ ├── FormContainer.js │ │ ├── searchbox.module.css │ │ ├── Meta.js │ │ ├── Paginate.js │ │ ├── HeroSection.js │ │ ├── Slogan.js │ │ ├── SearchBox.js │ │ ├── Product.module.css │ │ ├── ProductCarousel.js │ │ ├── CheckoutSteps.js │ │ ├── Product.js │ │ ├── Rating.js │ │ ├── AwesomeFeatures.js │ │ ├── Footer.js │ │ ├── Header.js │ │ └── Loader.js │ ├── assets │ │ ├── slogan.png │ │ ├── empty-cart.png │ │ ├── logo │ │ │ └── logo.png │ │ └── icons │ │ │ ├── order-success.png │ │ │ ├── courier-services.png │ │ │ ├── fast-delivery-icon.png │ │ │ ├── worldwide-delivery.png │ │ │ ├── Lazy container and multi-container _ React-Toastify_files │ │ │ ├── algolia.ddd40373.js.download │ │ │ ├── styles.81a83465.js.download │ │ │ ├── 17896441.18c45255.js.download │ │ │ ├── 66551e45.d0728d00.js.download │ │ │ ├── c4a783b7.42f0f495.js.download │ │ │ ├── runtime_main.44451698.js.download │ │ │ └── 4ae66d7c.fed4a6df.js.download │ │ │ ├── arrow-left.svg │ │ │ ├── mail.svg │ │ │ ├── shopping-cart-white.svg │ │ │ ├── shopping-cart.svg │ │ │ ├── cross.svg │ │ │ └── user-profile.svg │ ├── constants │ │ ├── cartConstants.js │ │ ├── userConstants.js │ │ ├── orderConstants.js │ │ └── productConstants.js │ ├── reportWebVitals.js │ ├── index.js │ ├── actions │ │ ├── cartActions.js │ │ ├── productActions.js │ │ └── orderActions.js │ ├── reducers │ │ ├── cartReducers.js │ │ ├── userReducers.js │ │ ├── productReducers.js │ │ └── orderReducers.js │ ├── screens │ │ ├── PaymentScreen.js │ │ ├── HomeScreen.js │ │ ├── OrderListScreen.js │ │ ├── UserListScreen.js │ │ ├── ShippingScreen.js │ │ ├── UserEditScreen.js │ │ ├── LoginScreen.js │ │ ├── ProductListScreen.js │ │ ├── RegisterScreen.js │ │ ├── CartScreen.js │ │ ├── PlaceOrderScreen.js │ │ ├── ProductEditScreen.js │ │ └── ProfileScreen.js │ ├── store.js │ ├── App.js │ └── index.css ├── package.json └── README.md ├── server ├── utils │ └── generateToken.js ├── config │ └── db.js ├── middleware │ ├── errorMiddleware.js │ └── authMiddleware.js ├── data │ ├── users.js │ └── products.js ├── routes │ ├── productRoutes.js │ ├── userRoutes.js │ ├── orderRoutes.js │ └── uploadRoutes.js ├── models │ ├── userModel.js │ ├── productModel.js │ └── orderModel.js ├── seeder.js ├── server.js └── controller │ ├── orderController.js │ ├── productController.js │ └── userController.js ├── .gitignore ├── package.json └── README.md /uploads/file.txt: -------------------------------------------------------------------------------- 1 | upload to git -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server/server.js -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/components/AwesomeFeatures.module.css: -------------------------------------------------------------------------------- 1 | .feature_image img { 2 | width: 146px; 3 | height: 131px; 4 | } -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/src/assets/slogan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/slogan.png -------------------------------------------------------------------------------- /client/public/images/pizza-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-1.jpg -------------------------------------------------------------------------------- /client/public/images/pizza-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-2.jpg -------------------------------------------------------------------------------- /client/public/images/pizza-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-3.jpg -------------------------------------------------------------------------------- /client/public/images/pizza-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-4.jpg -------------------------------------------------------------------------------- /client/public/images/pizza-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-5.png -------------------------------------------------------------------------------- /client/public/images/pizza-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-6.jpg -------------------------------------------------------------------------------- /client/public/images/pizza-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/pizza-7.jpg -------------------------------------------------------------------------------- /client/public/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/public/images/sample.jpg -------------------------------------------------------------------------------- /client/src/assets/empty-cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/empty-cart.png -------------------------------------------------------------------------------- /client/src/assets/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/logo/logo.png -------------------------------------------------------------------------------- /uploads/image-1623865152147.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/uploads/image-1623865152147.jpg -------------------------------------------------------------------------------- /uploads/image-1623865318246.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/uploads/image-1623865318246.jpg -------------------------------------------------------------------------------- /client/src/assets/icons/order-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/icons/order-success.png -------------------------------------------------------------------------------- /client/src/assets/icons/courier-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/icons/courier-services.png -------------------------------------------------------------------------------- /client/src/assets/icons/fast-delivery-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/icons/fast-delivery-icon.png -------------------------------------------------------------------------------- /client/src/assets/icons/worldwide-delivery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahnewaztameem/firehouse-pizza-shop-mern/HEAD/client/src/assets/icons/worldwide-delivery.png -------------------------------------------------------------------------------- /client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/algolia.ddd40373.js.download: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[30],{244:function(n,w,o){}}]); -------------------------------------------------------------------------------- /client/src/components/HeroSection.module.css: -------------------------------------------------------------------------------- 1 | .hero_bg { 2 | background-image: url('../assets/images/giveaway.svg'); 3 | background-repeat: no-repeat; 4 | background-position: right; 5 | height: 55vh; 6 | } 7 | -------------------------------------------------------------------------------- /server/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | const generateToken = (id) => { 4 | return jwt.sign({ id }, process.env.JWT_SECRET, { 5 | expiresIn: '30d' 6 | }) 7 | } 8 | 9 | export default generateToken -------------------------------------------------------------------------------- /client/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Alert } from 'react-bootstrap' 3 | 4 | const Message = ({ variant, children }) => { 5 | return {children} 6 | } 7 | 8 | Message.defaultProps = { 9 | variant: 'info', 10 | } 11 | export default Message 12 | -------------------------------------------------------------------------------- /client/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_CLEAR_ITEMS = 'CART_CLEAR_ITEMS' -------------------------------------------------------------------------------- /client/src/assets/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/icons/mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/Header.module.css: -------------------------------------------------------------------------------- 1 | .cartCount { 2 | background-color: #FF4555; 3 | color:#fff; 4 | font-weight: normal; 5 | height: 20px; 6 | width: 20px; 7 | border-radius: 50%; 8 | text-align: center; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | position: absolute; 13 | top: -10px; 14 | right: -15px; 15 | } -------------------------------------------------------------------------------- /client/src/assets/icons/shopping-cart-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/icons/shopping-cart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/FormContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Col, Container, Row } from 'react-bootstrap' 3 | 4 | const FormContainer = ({ children }) => { 5 | return ( 6 | 7 | 8 | 9 | {children} 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default FormContainer 17 | -------------------------------------------------------------------------------- /client/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 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /client/build 14 | 15 | # misc 16 | .DS_Store 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /server/config/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const connectDB = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URI, { 6 | useUnifiedTopology: true, 7 | useNewUrlParser: true, 8 | useCreateIndex: true, 9 | }) 10 | console.log(`mongodb connected: ${conn.connection.host}`) 11 | } catch (error) { 12 | console.error(`Error: ${error.message}`) 13 | process.exit(1) 14 | } 15 | } 16 | 17 | export default connectDB; -------------------------------------------------------------------------------- /client/src/components/searchbox.module.css: -------------------------------------------------------------------------------- 1 | .searchbox { 2 | background-color: transparent; 3 | border-radius: 40px; 4 | border: 2px solid #ff4555; 5 | } 6 | 7 | .searchBtn { 8 | position: absolute; 9 | right: 6px; 10 | border-radius: 40px; 11 | background: white; 12 | background-color: #ff4555; 13 | color: #fff; 14 | border-color: #ff4555; 15 | } 16 | 17 | .form_control:focus { 18 | background-color: transparent; 19 | border-color: none; 20 | outline: 0; 21 | box-shadow: none; 22 | } 23 | -------------------------------------------------------------------------------- /server/middleware/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | const notFound = (req, res, next) => { 2 | const error = new Error(`Not Found - ${req.originalUrl}`) 3 | res.status(404) 4 | next(error) 5 | } 6 | 7 | const errorHandler = (err, req, res, next) => { 8 | const error = res.statusCode === 200 ? 500 : res.statusCode 9 | res.status(error) 10 | res.json({ 11 | message: err.message, 12 | stack: process.env.NODE_ENV === 'production' ? null : err.stack, 13 | }) 14 | } 15 | 16 | export { notFound, errorHandler } 17 | -------------------------------------------------------------------------------- /server/data/users.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | 3 | const users = [ 4 | { 5 | name: 'Shahnewaz Tameem', 6 | email: 'shahnewaztamim@gmail.com', 7 | password: bcrypt.hashSync('123456', 10), 8 | isAdmin: true, 9 | }, 10 | { 11 | name: 'Faruque', 12 | email: 'faruque@gmail.com', 13 | password: bcrypt.hashSync('123456', 10), 14 | }, 15 | { 16 | name: 'Tameem', 17 | email: 'tameemcse@gmail.com', 18 | password: bcrypt.hashSync('123456', 10), 19 | }, 20 | ] 21 | 22 | export default users 23 | -------------------------------------------------------------------------------- /client/src/components/Meta.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | const Meta = ({ title, description, keywords }) => { 4 | return ( 5 | 6 | {title} 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | Meta.defaultProps = { 14 | title: 'Firehouse Pizza Shop', 15 | keywords: 'we sell best pizzas, burger online', 16 | description: 'we sell best best pizzas, burger online', 17 | } 18 | export default Meta 19 | -------------------------------------------------------------------------------- /client/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 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import store from './store' 5 | import './bootstrap.min.css' 6 | import './index.css' 7 | import App from './App' 8 | import reportWebVitals from './reportWebVitals' 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals() 21 | -------------------------------------------------------------------------------- /server/routes/productRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | import { 4 | getProducts, 5 | getProductById, 6 | deleteProduct, 7 | updateProduct, 8 | createProduct, 9 | createProductReview, 10 | getTopProducts, 11 | } from '../controller/productController.js' 12 | import { protect, admin } from '../middleware/authMiddleware.js' 13 | 14 | router.route('/').get(getProducts).post(protect, admin, createProduct) 15 | router.route('/:id/reviews').post(protect, createProductReview) 16 | router.get('/top', getTopProducts) 17 | router 18 | .route('/:id') 19 | .get(getProductById) 20 | .delete(protect, admin, deleteProduct) 21 | .put(protect, admin, updateProduct) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /server/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | import { 4 | authUser, 5 | registerUser, 6 | getUserProfile, 7 | updateUserProfile, 8 | getUsers, 9 | deleteUser, 10 | getUserById, 11 | updateUser, 12 | } from '../controller/userController.js' 13 | import { protect, admin } from '../middleware/authMiddleware.js' 14 | 15 | router.route('/').post(registerUser).get(protect, admin, getUsers) 16 | router.post('/login', authUser) 17 | router 18 | .route('/profile') 19 | .get(protect, getUserProfile) 20 | .put(protect, updateUserProfile) 21 | 22 | router 23 | .route('/:id') 24 | .delete(protect, admin, deleteUser) 25 | .get(protect, admin, getUserById) 26 | .put(protect, admin, updateUser) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /server/routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | import { 4 | addOrderItems, 5 | getMyOrders, 6 | getOrderById, 7 | getOrders, 8 | updateOrderToPaid, 9 | updateOrderToDelivered, 10 | updateOrderToPending, 11 | } from '../controller/orderController.js' 12 | import { protect, admin } from '../middleware/authMiddleware.js' 13 | 14 | router.route('/').post(protect, addOrderItems).get(protect, admin, getOrders) 15 | router.route('/myOrders').get(protect, getMyOrders) 16 | router.route('/:id').get(protect, getOrderById) 17 | router.route('/:id/pay').put(protect, updateOrderToPaid) 18 | router.route('/:id/deliver').put(protect, admin, updateOrderToDelivered) 19 | router.route('/:id/pending').put(protect, admin, updateOrderToPending) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /client/src/assets/icons/cross.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/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 | return ( 7 | pages > 1 && ( 8 | 9 | {[...Array(pages).keys()].map((x) => ( 10 | 20 | {x + 1} 21 | 22 | ))} 23 | 24 | ) 25 | ) 26 | } 27 | 28 | export default Paginate 29 | -------------------------------------------------------------------------------- /server/routes/uploadRoutes.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import express from 'express' 3 | import multer from 'multer' 4 | 5 | const router = express.Router() 6 | 7 | const storage = multer.diskStorage({ 8 | destination(req, file, cb) { 9 | cb(null, 'uploads/') 10 | }, 11 | filename(req, file, cb) { 12 | cb(null, `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`) 13 | }, 14 | }) 15 | 16 | function checkFileType(file, cb) { 17 | const filetypes = /jpg|jpeg|png/ 18 | const extname = filetypes.test(path.extname(file.originalname).toLowerCase()) 19 | const mimetype = filetypes.test(file.mimetype) 20 | 21 | if(extname && mimetype) { 22 | return cb(null, true) 23 | } else { 24 | cb('Images only!') 25 | } 26 | } 27 | 28 | const upload = multer({ 29 | storage, 30 | fileFilter: function(req, file, cb) { 31 | checkFileType(file, cb) 32 | } 33 | }) 34 | 35 | router.post('/', upload.single('image'), (req, res) => { 36 | res.send(`/${req.file.path}`) 37 | }) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /client/src/components/HeroSection.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Col, Button } from 'react-bootstrap' 3 | import styles from './HeroSection.module.css' 4 | 5 | const HeroSection = () => { 6 | return ( 7 |
8 | 9 | 10 |

11 | Hungry? You're
in the right place 12 |

13 | 14 |

15 | voluptatum possimus amet pariatur sequi molestiae id. Ea quia fugit 16 | quam exercitationem voluptas hic! Nesciunt, neque laudantium nisi 17 | aliquid rerum eos temporibus explicabo et, tenetur saepe 18 | voluptatibus quibusdam quas? 19 |

20 | 21 | 22 | 23 | 24 |
25 |
26 | ) 27 | } 28 | 29 | export default HeroSection 30 | -------------------------------------------------------------------------------- /client/src/components/Slogan.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Col, Button, Image } from 'react-bootstrap' 3 | import sloganImg from '../assets/slogan.png' 4 | const Slogan = () => { 5 | return ( 6 |
7 | 8 | 9 |

10 | Real Delicious food Straight to your door 11 |

12 |

13 | {' '} 14 | Ea consequatur corrupti ullam cumque! Magni officiis dolor qui 15 | laudantium hic fuga quod assumenda deleniti, praesentium voluptatem, 16 | vel animi quia ab non harum corporis commodi? 17 |

18 | 19 | 20 | 21 | slogan 22 | 23 |
24 |
25 | ) 26 | } 27 | 28 | export default Slogan 29 | -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import bcrypt from 'bcryptjs' 3 | 4 | const userSchema = mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: [true, 'Name is required'], 9 | }, 10 | email: { 11 | type: String, 12 | required: [true, 'Email is required'], 13 | unique: true, 14 | }, 15 | password: { 16 | type: String, 17 | required: [true, 'Password is required'], 18 | }, 19 | isAdmin: { 20 | type: Boolean, 21 | required: true, 22 | default: false, 23 | }, 24 | }, 25 | { 26 | timestamps: true, 27 | } 28 | ) 29 | 30 | userSchema.methods.matchPassword = async function (enteredPassword) { 31 | return await bcrypt.compare(enteredPassword, this.password) 32 | } 33 | 34 | userSchema.pre('save', async function(next) { 35 | if(!this.isModified('password')) { 36 | next() 37 | } 38 | const salt = await bcrypt.genSalt(10) 39 | this.password = await bcrypt.hash(this.password, salt) 40 | }) 41 | 42 | const User = mongoose.model('User', userSchema) 43 | 44 | export default User 45 | -------------------------------------------------------------------------------- /server/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import asyncHandler from 'express-async-handler' 3 | import User from '../models/userModel.js' 4 | 5 | const protect = asyncHandler(async (req, res, next) => { 6 | let token 7 | 8 | if ( 9 | req.headers.authorization && 10 | req.headers.authorization.startsWith('Bearer') 11 | ) { 12 | try { 13 | token = req.headers.authorization.split(' ')[1] 14 | const decoded = jwt.verify(token, process.env.JWT_SECRET) 15 | 16 | req.user = await User.findById(decoded.id).select('-password') 17 | 18 | next() 19 | } catch (error) { 20 | console.error(error) 21 | res.status(401) 22 | throw new Error('Not Authorized, token failed') 23 | } 24 | } 25 | if (!token) { 26 | res.status(401) 27 | throw new Error('Not Authorized, no token found') 28 | } 29 | }) 30 | 31 | const admin = (req, res, next) => { 32 | if (req.user && req.user.isAdmin) { 33 | next() 34 | } else { 35 | res.status(401) 36 | throw new Error('Not Authorized as an admin') 37 | } 38 | } 39 | 40 | export { protect, admin } 41 | -------------------------------------------------------------------------------- /client/src/components/SearchBox.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Form, Button } from 'react-bootstrap' 3 | import styles from './searchbox.module.css' 4 | 5 | const SearchBox = ({ history }) => { 6 | const [keyword, setKeyword] = useState('') 7 | 8 | const submitHandler = (e) => { 9 | e.preventDefault() 10 | if (keyword.trim()) { 11 | history.push(`/search/${keyword}`) 12 | } else { 13 | history.push('/') 14 | } 15 | } 16 | 17 | return ( 18 |
19 |
20 | setKeyword(e.target.value)} 24 | placeholder='Search Product' 25 | className={`mr-sm-2 ml-sm-5 ${styles.searchbox} ${styles.form_control}`} 26 | > 27 | 34 |
35 |
36 | ) 37 | } 38 | 39 | export default SearchBox 40 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Firehouse Pizza Shop 16 | 17 | 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/src/components/Product.module.css: -------------------------------------------------------------------------------- 1 | .product_cart { 2 | border-radius: 50px; 3 | box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.008), 4 | 0 6.7px 5.3px rgba(0, 0, 0, 0.012), 0 12.5px 10px rgba(0, 0, 0, 0.015), 5 | 0 22.3px 17.9px rgba(0, 0, 0, 0.018), 0 41.8px 33.4px rgba(0, 0, 0, 0.022), 6 | 0 100px 80px rgba(0, 0, 0, 0.03); 7 | transition: 0.4s all; 8 | height: 80%; 9 | } 10 | 11 | .product_cart:hover { 12 | box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.022), 13 | 0 6.7px 5.3px rgba(0, 0, 0, 0.032), 0 12.5px 10px rgba(0, 0, 0, 0.04), 14 | 0 22.3px 17.9px rgba(0, 0, 0, 0.048), 0 41.8px 33.4px rgba(0, 0, 0, 0.058), 15 | 0 100px 80px rgba(0, 0, 0, 0.08); 16 | } 17 | .product_image_top { 18 | margin-top: -25%; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .product_image { 25 | width: 150px; 26 | height: 150px; 27 | border-radius: 50%; 28 | } 29 | 30 | .product_name { 31 | transition: 0.4s; 32 | } 33 | 34 | .product_name:hover { 35 | color: #ff4555; 36 | } 37 | 38 | .product_category { 39 | padding: 8px 16px; 40 | background-color: #ffebed; 41 | color: #ff4a59; 42 | border-radius: 30px; 43 | font-weight: bold; 44 | display: inline-block; 45 | font-size: 16px; 46 | } 47 | 48 | .product_price { 49 | color: #ff4a59; 50 | } 51 | -------------------------------------------------------------------------------- /client/src/actions/cartActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | CART_ADD_ITEM, 4 | CART_REMOVE_ITEM, 5 | CART_SAVE_SHIPPING_ADDRESS, 6 | CART_SAVE_PAYMENT_METHOD, 7 | } from '../constants/cartConstants' 8 | 9 | export const addToCart = (id, qty) => async (dispatch, getState) => { 10 | const { data } = await axios.get(`/api/products/${id}`) 11 | 12 | dispatch({ 13 | type: CART_ADD_ITEM, 14 | payload: { 15 | product: data._id, 16 | name: data.name, 17 | image: data.image, 18 | price: data.price, 19 | countInStock: data.countInStock, 20 | qty, 21 | }, 22 | }) 23 | 24 | localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems)) 25 | } 26 | 27 | export const removeFromCart = (id) => async (dispatch, getState) => { 28 | dispatch({ type: CART_REMOVE_ITEM, payload: id }) 29 | localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems)) 30 | } 31 | 32 | export const saveShippingAddress = (data) => async (dispatch) => { 33 | dispatch({ type: CART_SAVE_SHIPPING_ADDRESS, payload: data }) 34 | 35 | localStorage.setItem('shippingAddress', JSON.stringify(data)) 36 | } 37 | 38 | export const savePaymentMethod = (data) => async (dispatch) => { 39 | dispatch({ type: CART_SAVE_PAYMENT_METHOD, payload: data }) 40 | 41 | localStorage.setItem('paymentMethod', JSON.stringify(data)) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electro-shop-mern", 3 | "version": "1.0.0", 4 | "description": "MERN Simple Ecommerce", 5 | "main": "server.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node server/server", 9 | "server": "nodemon server/server", 10 | "client": "npm start --prefix client", 11 | "dev": "concurrently \"npm run server\" \"npm run client\"", 12 | "data:import": "node server/seeder", 13 | "data:destroy": "node server/seeder -d", 14 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/shahnewaztameem/firehouse-pizza-shop-mern.git" 19 | }, 20 | "author": "Shahnewaz", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/shahnewaztameem/firehouse-pizza-shop-mern/issues" 24 | }, 25 | "homepage": "https://github.com/shahnewaztameem/firehouse-pizza-shop-mern#readme", 26 | "dependencies": { 27 | "bcryptjs": "^2.4.3", 28 | "dotenv": "^8.2.0", 29 | "express": "^4.17.1", 30 | "express-async-handler": "^1.1.4", 31 | "jsonwebtoken": "^8.5.1", 32 | "mongoose": "^5.12.3", 33 | "morgan": "^1.10.0", 34 | "multer": "^1.4.2" 35 | }, 36 | "devDependencies": { 37 | "concurrently": "^6.0.1", 38 | "nodemon": "^2.0.7" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/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 Loader from './Loader' 5 | import Message from './Message' 6 | import { listTopProducts } from '../actions/productActions' 7 | import { useDispatch, useSelector } from 'react-redux' 8 | 9 | const ProductCarousel = () => { 10 | const dispatch = useDispatch() 11 | const productTopRated = useSelector((state) => state.productTopRated) 12 | const { loading, error, products } = productTopRated 13 | 14 | useEffect(() => { 15 | dispatch(listTopProducts()) 16 | }, [dispatch]) 17 | return loading ? ( 18 | 19 | ) : error ? ( 20 | {error} 21 | ) : ( 22 | 23 | {products.map((product) => ( 24 | 25 | 26 | {product.name} 27 | 28 |

29 | {product.name} ({product.price}) 30 |

31 |
32 | 33 |
34 | ))} 35 |
36 | ) 37 | } 38 | 39 | export default ProductCarousel 40 | -------------------------------------------------------------------------------- /server/seeder.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import dotenv from 'dotenv' 3 | import users from './data/users.js' 4 | import products from './data/products.js' 5 | import User from './models/userModel.js' 6 | import Product from './models/productModel.js' 7 | import Order from './models/orderModel.js' 8 | import connectDB from './config/db.js' 9 | 10 | dotenv.config() 11 | 12 | connectDB() 13 | 14 | const importData = async () => { 15 | try { 16 | await Order.deleteMany() 17 | await Product.deleteMany() 18 | await User.deleteMany() 19 | 20 | const createdUsers = await User.insertMany(users) 21 | 22 | const adminUser = createdUsers[0]._id 23 | 24 | const sampleProducts = products.map((product) => { 25 | return { ...product, user: adminUser } 26 | }) 27 | 28 | await Product.insertMany(sampleProducts) 29 | 30 | console.log('Data Imported!') 31 | process.exit() 32 | } catch (error) { 33 | console.error(`${error}`) 34 | process.exit(1) 35 | } 36 | } 37 | 38 | const destroyData = async () => { 39 | try { 40 | await Order.deleteMany() 41 | await Product.deleteMany() 42 | await User.deleteMany() 43 | 44 | console.log('Data Destroyed!') 45 | process.exit() 46 | } catch (error) { 47 | console.error(`${error}`) 48 | process.exit(1) 49 | } 50 | } 51 | 52 | if (process.argv[2] === '-d') { 53 | destroyData() 54 | } else { 55 | importData() 56 | } 57 | -------------------------------------------------------------------------------- /client/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 | return ( 7 | 48 | ) 49 | } 50 | 51 | export default CheckoutSteps 52 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 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 | "framer-motion": "^4.1.17", 12 | "react": "^17.0.2", 13 | "react-bootstrap": "^1.5.2", 14 | "react-content-loader": "^6.0.3", 15 | "react-dom": "^17.0.2", 16 | "react-helmet": "^6.1.0", 17 | "react-paypal-button-v2": "^2.6.3", 18 | "react-redux": "^7.2.3", 19 | "react-router-bootstrap": "^0.25.0", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "4.0.3", 22 | "react-toastify": "^7.0.4", 23 | "redux": "^4.0.5", 24 | "redux-devtools-extension": "^2.13.9", 25 | "redux-thunk": "^2.3.0", 26 | "web-vitals": "^1.0.1" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/src/reducers/cartReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CART_ADD_ITEM, 3 | CART_REMOVE_ITEM, 4 | CART_SAVE_SHIPPING_ADDRESS, 5 | CART_SAVE_PAYMENT_METHOD, 6 | CART_CLEAR_ITEMS, 7 | } from '../constants/cartConstants' 8 | 9 | export const cartReducer = ( 10 | state = { cartItems: [], shippingAddress: {} }, 11 | action 12 | ) => { 13 | switch (action.type) { 14 | case CART_ADD_ITEM: 15 | const item = action.payload 16 | const existItem = state.cartItems.find((x) => x.product === item.product) 17 | 18 | if (existItem) { 19 | return { 20 | ...state, 21 | cartItems: state.cartItems.map((x) => 22 | x.product === existItem.product ? item : x 23 | ), 24 | } 25 | } else { 26 | return { 27 | ...state, 28 | cartItems: [...state.cartItems, item], 29 | } 30 | } 31 | 32 | case CART_REMOVE_ITEM: 33 | return { 34 | ...state, 35 | cartItems: state.cartItems.filter((x) => x.product !== action.payload), 36 | } 37 | 38 | case CART_SAVE_SHIPPING_ADDRESS: 39 | return { 40 | ...state, 41 | shippingAddress: action.payload, 42 | } 43 | 44 | case CART_SAVE_PAYMENT_METHOD: 45 | return { 46 | ...state, 47 | paymentMethod: action.payload, 48 | } 49 | case CART_CLEAR_ITEMS: 50 | return { 51 | ...state, 52 | cartItems: [], 53 | } 54 | default: 55 | return state 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/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_FAIL = 'USER_LOGIN_FAIL' 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_FAIL = 'USER_REGISTER_FAIL' 9 | 10 | export const USER_DETAILS_REQUEST = 'USER_DETAILS_REQUEST' 11 | export const USER_DETAILS_SUCCESS = 'USER_DETAILS_SUCCESS' 12 | export const USER_DETAILS_FAIL = 'USER_DETAILS_FAIL' 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_FAIL = 'USER_UPDATE_PROFILE_FAIL' 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_FAIL = 'USER_LIST_FAIL' 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_FAIL = 'USER_DELETE_FAIL' 28 | 29 | export const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST' 30 | export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS' 31 | export const USER_UPDATE_FAIL = 'USER_UPDATE_FAIL' 32 | export const USER_UPDATE_RESET = 'USER_UPDATE_RESET' -------------------------------------------------------------------------------- /client/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_FAIL = 'ORDER_CREATE_FAIL' 4 | export const ORDER_CREATE_RESET = 'ORDER_CREATE_RESET' 5 | 6 | export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST' 7 | export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS' 8 | export const ORDER_DETAILS_FAIL = 'ORDER_DETAILS_FAIL' 9 | 10 | export const ORDER_PAY_REQUEST = 'ORDER_PAY_REQUEST' 11 | export const ORDER_PAY_SUCCESS = 'ORDER_PAY_SUCCESS' 12 | export const ORDER_PAY_FAIL = 'ORDER_PAY_FAIL' 13 | export const ORDER_PAY_RESET = 'ORDER_PAY_RESET' 14 | 15 | export const ORDER_LIST_MY_REQUEST = 'ORDER_LIST_MY_REQUEST' 16 | export const ORDER_LIST_MY_SUCCESS = 'ORDER_LIST_MY_SUCCESS' 17 | export const ORDER_LIST_MY_FAIL = 'ORDER_LIST_MY_FAIL' 18 | export const ORDER_LIST_MY_RESET = 'ORDER_LIST_MY_RESET' 19 | 20 | export const ORDER_LIST_REQUEST = 'ORDER_LIST_REQUEST' 21 | export const ORDER_LIST_SUCCESS = 'ORDER_LIST_SUCCESS' 22 | export const ORDER_LIST_FAIL = 'ORDER_LIST_FAIL' 23 | 24 | export const ORDER_DELIVER_REQUEST = 'ORDER_DELIVER_REQUEST' 25 | export const ORDER_DELIVER_SUCCESS = 'ORDER_DELIVER_SUCCESS' 26 | export const ORDER_DELIVER_FAIL = 'ORDER_DELIVER_FAIL' 27 | export const ORDER_DELIVER_RESET = 'ORDER_DELIVER_RESET' 28 | 29 | export const ORDER_PENDING_REQUEST = 'ORDER_PENDING_REQUEST' 30 | export const ORDER_PENDING_SUCCESS = 'ORDER_PENDING_SUCCESS' 31 | export const ORDER_PENDING_FAIL = 'ORDER_PENDING_FAIL' 32 | export const ORDER_PENDING_RESET = 'ORDER_PENDING_RESET' 33 | -------------------------------------------------------------------------------- /client/src/constants/productConstants.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = 'PRODUCT_LIST_REQUEST' 2 | export const PRODUCT_LIST_SUCCESS = 'PRODUCT_LIST_SUCCESS' 3 | export const PRODUCT_LIST_FAIL = 'PRODUCT_LIST_FAIL' 4 | 5 | 6 | export const PRODUCT_DETAILS_REQUEST = 'PRODUCT_DETAILS_REQUEST' 7 | export const PRODUCT_DETAILS_SUCCESS = 'PRODUCT_DETAILS_SUCCESS' 8 | export const PRODUCT_DETAILS_FAIL = 'PRODUCT_DETAILS_FAIL' 9 | 10 | export const PRODUCT_DELETE_REQUEST = 'PRODUCT_DELETE_REQUEST' 11 | export const PRODUCT_DELETE_SUCCESS = 'PRODUCT_DELETE_SUCCESS' 12 | export const PRODUCT_DELETE_FAIL = 'PRODUCT_DELETE_FAIL' 13 | 14 | export const PRODUCT_CREATE_REQUEST = 'PRODUCT_CREATE_REQUEST' 15 | export const PRODUCT_CREATE_SUCCESS = 'PRODUCT_CREATE_SUCCESS' 16 | export const PRODUCT_CREATE_FAIL = 'PRODUCT_CREATE_FAIL' 17 | export const PRODUCT_CREATE_RESET = 'PRODUCT_CREATE_RESET' 18 | 19 | export const PRODUCT_UPDATE_REQUEST = 'PRODUCT_UPDATE_REQUEST' 20 | export const PRODUCT_UPDATE_SUCCESS = 'PRODUCT_UPDATE_SUCCESS' 21 | export const PRODUCT_UPDATE_FAIL = 'PRODUCT_UPDATE_FAIL' 22 | export const PRODUCT_UPDATE_RESET = 'PRODUCT_UPDATE_RESET' 23 | 24 | export const PRODUCT_CREATE_REVIEW_REQUEST = 'PRODUCT_CREATE_REVIEW_REQUEST' 25 | export const PRODUCT_CREATE_REVIEW_SUCCESS = 'PRODUCT_CREATE_REVIEW_SUCCESS' 26 | export const PRODUCT_CREATE_REVIEW_FAIL = 'PRODUCT_CREATE_REVIEW_FAIL' 27 | export const PRODUCT_CREATE_REVIEW_RESET = 'PRODUCT_CREATE_REVIEW_RESET' 28 | 29 | export const PRODUCT_TOP_REQUEST = 'PRODUCT_TOP_REQUEST' 30 | export const PRODUCT_TOP_SUCCESS = 'PRODUCT_TOP_SUCCESS' 31 | export const PRODUCT_TOP_FAIL = 'PRODUCT_TOP_FAIL' -------------------------------------------------------------------------------- /server/models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from '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: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | required: true, 11 | ref: 'User', 12 | }, 13 | }, 14 | 15 | { 16 | timestamps: true, 17 | } 18 | ) 19 | 20 | const productSchema = mongoose.Schema( 21 | { 22 | user: { 23 | type: mongoose.Schema.Types.ObjectId, 24 | required: true, 25 | ref: 'User', 26 | }, 27 | name: { 28 | type: String, 29 | required: true, 30 | }, 31 | image: { 32 | type: String, 33 | required: true, 34 | }, 35 | brand: { 36 | type: String, 37 | required: true, 38 | }, 39 | category: { 40 | type: String, 41 | required: true, 42 | }, 43 | description: { 44 | type: String, 45 | required: true, 46 | }, 47 | reviews: [reviewSchema], 48 | rating: { 49 | type: Number, 50 | required: true, 51 | default: 0, 52 | }, 53 | numReviews: { 54 | type: Number, 55 | required: true, 56 | default: 0, 57 | }, 58 | price: { 59 | type: Number, 60 | required: true, 61 | default: 0, 62 | }, 63 | countInStock: { 64 | type: Number, 65 | required: true, 66 | default: 0, 67 | }, 68 | }, 69 | { 70 | timestamps: true, 71 | } 72 | ) 73 | 74 | const Product = mongoose.model('Product', productSchema) 75 | 76 | export default Product 77 | -------------------------------------------------------------------------------- /client/src/components/Product.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Card, Button } from 'react-bootstrap' 3 | import { Link } from 'react-router-dom' 4 | import Rating from './Rating' 5 | import styles from './Product.module.css' 6 | 7 | const Product = ({ product }) => { 8 | console.log(product) 9 | return ( 10 | 11 |
12 |
13 | 14 | 19 | 20 |
21 |
22 | 23 | 24 | 25 |

{product.name}

26 |
27 | 28 | 29 | 30 |
{product.category}
31 |
32 | 36 |
37 |
38 | 39 | 40 | ${product.price} 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 | ) 49 | } 50 | 51 | export default Product 52 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | import path from 'path' 3 | import morgan from 'morgan' 4 | import express from 'express' 5 | import connectDB from './config/db.js' 6 | import { notFound, errorHandler } from './middleware/errorMiddleware.js' 7 | import productRoutes from './routes/productRoutes.js' 8 | import userRoutes from './routes/userRoutes.js' 9 | import orderRoutes from './routes/orderRoutes.js' 10 | import uploadRoutes from './routes/uploadRoutes.js' 11 | 12 | dotenv.config() 13 | 14 | //db connection 15 | connectDB() 16 | 17 | const app = express() 18 | 19 | app.use(express.json()) 20 | 21 | if (process.env.NODE_ENV === 'development') { 22 | app.use(morgan('dev')) 23 | } 24 | 25 | app.use('/api/products', productRoutes) 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) => 31 | res.send(process.env.PAYPAL_CLIENT_ID) 32 | ) 33 | 34 | const __dirname = path.resolve() 35 | 36 | app.use('/uploads', express.static(path.join(__dirname, '/uploads'))) 37 | 38 | if (process.env.NODE_ENV === 'production') { 39 | app.use(express.static(path.join(__dirname, '/client/build'))) 40 | 41 | app.get('*', (req, res) => 42 | res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')) 43 | ) 44 | } else { 45 | app.get('/', (req, res) => { 46 | res.send('Hello') 47 | }) 48 | } 49 | 50 | // 404 routing error handler 51 | app.use(notFound) 52 | 53 | // custom error handler 54 | app.use(errorHandler) 55 | 56 | const PORT = process.env.PORT || 5000 57 | 58 | app.listen(PORT, () => { 59 | console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}!`) 60 | }) 61 | -------------------------------------------------------------------------------- /client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/styles.81a83465.js.download: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{104:function(o,n,e){o.exports={docTitle:"docTitle_1vWb",docItemContainer:"docItemContainer_2cwg",docItemCol:"docItemCol_2GOA",tableOfContents:"tableOfContents_TbNY",docLastUpdatedAt:"docLastUpdatedAt_1sqk"}},106:function(o,n,e){o.exports={sidebar:"sidebar_1kLs",sidebarLogo:"sidebarLogo_3ono",menu:"menu_w2sC",sidebarMenuIcon:"sidebarMenuIcon_2vk4",sidebarMenuCloseIcon:"sidebarMenuCloseIcon_1JRa"}},107:function(o,n,e){o.exports={codeBlockContent:"codeBlockContent_32p_",codeBlockTitle:"codeBlockTitle_U1PS",codeBlock:"codeBlock_19pQ",codeBlockWithTitle:"codeBlockWithTitle_2fj3",copyButton:"copyButton_1BYj",copyButtonWithTitle:"copyButtonWithTitle_3q_P",codeBlockLines:"codeBlockLines_2n9r"}},108:function(o,n,e){},109:function(o,n,e){o.exports={enhancedAnchor:"enhancedAnchor_ZqCz"}},110:function(o,n,e){o.exports={mdxCodeBlock:"mdxCodeBlock_iHAB"}},111:function(o,n,e){o.exports={docPage:"docPage_1kjD",docSidebarContainer:"docSidebarContainer_1cYp",docMainContainer:"docMainContainer_FFX1"}},84:function(o,n,e){},85:function(o,n,e){},86:function(o,n,e){},91:function(o,n,e){},93:function(o,n,e){o.exports={announcementBar:"announcementBar_HGgX",announcementBarClose:"announcementBarClose_2f8D",announcementBarContent:"announcementBarContent_GM1p"}},94:function(o,n,e){},95:function(o,n,e){o.exports={toggle:"toggle_keGJ",moon:"moon_1gwN",sun:"sun_3CPA"}},96:function(o,n,e){o.exports={displayOnlyInLargeViewport:"displayOnlyInLargeViewport_1gtM",hideLogoText:"hideLogoText_1pX_",navbarHideable:"navbarHideable_OaSq",navbarHidden:"navbarHidden_3XKp"}},97:function(o,n,e){o.exports={footerLogoLink:"footerLogoLink_1Wg7"}},98:function(o,n,e){}}]); -------------------------------------------------------------------------------- /client/src/assets/icons/user-profile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firehouse Pizza Shop 2 | ## An eCommerce platform built with the MERN stack & Redux. 3 | ## [Live Application](https://firehousepizza.herokuapp.com/) 4 | ![image](https://user-images.githubusercontent.com/19238216/123558702-824df200-d7b9-11eb-9722-82f50116b94e.png) 5 | 6 | 7 | ## ✨Technology Used 8 | 9 | ### Front-end: 10 | - React Js 11 | - Bootstrap 12 | - React Router 13 | - Redux 14 | - Framer Motion 15 | 16 | ### Back-end: 17 | - Express JS 18 | - MongoDB 19 | - JWT Authentication 20 | 21 | ## ✨Features 22 | - Fully functional Cart 23 | - Product Details, user review and ratings 24 | - JWT authentication 25 | - Product Search 26 | - User profile create/update 27 | - User order management 28 | - Admin role with authorization 29 | - Admin product management 30 | - Admin Order management 31 | - Admin user management 32 | - Admin Order details page 33 | - Mark order as delivered option 34 | - Checkout feature 35 | - Payment gateway integration 36 | 37 | ## ✨Usage 38 | I have used ECMAScript Modules in the backend. Make sure to have at least Node v14.6+ or you will need to add the "--experimental-modules" flag. Also, when importing a file (not a package), be sure to add .js at the end or you will get a "module not found" error. 39 | 40 | 41 | ## ✨Project Setup 42 | ## Env Variables 43 | Create a .env file in then root and add the following 44 | 45 | ``` 46 | NODE_ENV = development 47 | PORT = 5500 48 | MONGO_URI = your mongodb uri 49 | JWT_SECRET = 'xpiredbrain' 50 | PAYPAL_CLIENT_ID = your paypal client id 51 | ``` 52 | ## Install Dependencies 53 | ``` 54 | npm i 55 | cd client 56 | npm i 57 | ``` 58 | 59 | ## Run 60 | ``` 61 | # Run frontend (:3000) & backend (:5500) 62 | npm run dev 63 | 64 | # Run backend only 65 | npm run server 66 | ``` 67 | 68 | ## Seed Database 69 | ``` 70 | # Import data 71 | npm run data:import 72 | 73 | # Destroy data 74 | npm run data:destroy 75 | ``` 76 | ``` 77 | Sample login credentials: 78 | 79 | shahnewaztamim@gmail.com (Admin) 80 | 123456 81 | 82 | faruque@gmail.com 83 | 123456 84 | ``` 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /client/src/components/Rating.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const Rating = ({ value, text, color }) => { 5 | return ( 6 |
7 | 8 | = 1 12 | ? 'fas fa-star' 13 | : value >= 0.5 14 | ? 'fas fa-star-half-alt' 15 | : 'far fa-star' 16 | } 17 | > 18 | 19 | 20 | = 2 24 | ? 'fas fa-star' 25 | : value >= 1.5 26 | ? 'fas fa-star-half-alt' 27 | : 'far fa-star' 28 | } 29 | > 30 | 31 | 32 | = 3 36 | ? 'fas fa-star' 37 | : value >= 2.5 38 | ? 'fas fa-star-half-alt' 39 | : 'far fa-star' 40 | } 41 | > 42 | 43 | 44 | = 4 48 | ? 'fas fa-star' 49 | : value >= 3.5 50 | ? 'fas fa-star-half-alt' 51 | : 'far fa-star' 52 | } 53 | > 54 | 55 | 56 | = 5 60 | ? 'fas fa-star' 61 | : value >= 4.5 62 | ? 'fas fa-star-half-alt' 63 | : 'far fa-star' 64 | } 65 | > 66 | 67 | — {text && text} 68 |
69 | ) 70 | } 71 | 72 | Rating.defaultProps = { 73 | color: '#FE8530', 74 | } 75 | 76 | Rating.prototype = { 77 | value: PropTypes.number.isRequired, 78 | text: PropTypes.string.isRequired, 79 | color: PropTypes.string, 80 | } 81 | export default Rating 82 | -------------------------------------------------------------------------------- /server/models/orderModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const orderSchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: 'User', 9 | }, 10 | orderItems: [ 11 | { 12 | name: { type: String, required: true }, 13 | qty: { type: Number, required: true }, 14 | image: { type: String, required: true }, 15 | price: { type: Number, required: true }, 16 | product: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | required: true, 19 | ref: 'Product', 20 | }, 21 | }, 22 | ], 23 | shippingAddress: { 24 | address: { type: String, required: true }, 25 | city: { type: String, required: true }, 26 | postalCode: { type: String, required: true }, 27 | country: { type: String, required: true }, 28 | }, 29 | paymentMethod: { 30 | type: String, 31 | required: true, 32 | }, 33 | paymentResult: { 34 | id: { type: String }, 35 | status: { type: String }, 36 | update_time: { type: String }, 37 | email_address: { type: String }, 38 | }, 39 | taxPrice: { 40 | type: Number, 41 | required: true, 42 | default: 0.0, 43 | }, 44 | shippingPrice: { 45 | type: Number, 46 | required: true, 47 | default: 0.0, 48 | }, 49 | totalPrice: { 50 | type: Number, 51 | required: true, 52 | default: 0.0, 53 | }, 54 | isPaid: { 55 | type: Boolean, 56 | required: true, 57 | default: false, 58 | }, 59 | paidAt: { 60 | type: Date, 61 | }, 62 | isDelivered: { 63 | type: Boolean, 64 | required: true, 65 | default: false, 66 | }, 67 | isPending: { 68 | type: Boolean, 69 | required: true, 70 | default: false, 71 | }, 72 | pendingAt: { 73 | type: Date, 74 | }, 75 | deliveredAt: { 76 | type: Date, 77 | }, 78 | }, 79 | { 80 | timestamps: true, 81 | } 82 | ) 83 | 84 | const Order = mongoose.model('Order', orderSchema) 85 | 86 | export default Order 87 | -------------------------------------------------------------------------------- /client/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 { savePaymentMethod } from '../actions/cartActions' 6 | import CheckoutSteps from '../components/CheckoutSteps' 7 | import { motion } from 'framer-motion' 8 | 9 | const PaymentScreen = ({ history }) => { 10 | window.scrollTo(0, 0) 11 | const cart = useSelector((state) => state.cart) 12 | const { shippingAddress } = cart 13 | 14 | if (!shippingAddress.address) { 15 | history.push('/shipping') 16 | } 17 | 18 | const [paymentMethod, setPaymentMethod] = useState('PayPal') 19 | 20 | const dispatch = useDispatch() 21 | 22 | const submitHandler = (e) => { 23 | e.preventDefault() 24 | dispatch(savePaymentMethod(paymentMethod)) 25 | 26 | history.push('/placeorder') 27 | } 28 | 29 | return ( 30 | 36 | 37 | 38 |

Payment Method

39 |
40 | 41 | Select a payment method 42 | 43 | setPaymentMethod(e.target.value)} 51 | className='pl-0' 52 | style={{ fontSize: '18px' }} 53 | > 54 | 55 | 56 | 59 |
60 |
61 |
62 | ) 63 | } 64 | 65 | export default PaymentScreen 66 | -------------------------------------------------------------------------------- /client/src/components/AwesomeFeatures.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Col, Image } from 'react-bootstrap' 3 | import deliveryIcon from '../assets/icons/fast-delivery-icon.png' 4 | import orderPlace from '../assets/icons/order-success.png' 5 | import worldwide from '../assets/icons/worldwide-delivery.png' 6 | import courier from '../assets/icons/courier-services.png' 7 | import styles from './AwesomeFeatures.module.css' 8 | 9 | const AwesomeFeatures = () => { 10 | return ( 11 |
12 | 13 | 14 |

15 | Just Relax at Home ,
we will take care. 16 |

17 |

18 | Just sit back and relax. We will take care of your needs and choice 19 |

20 | 21 |
22 | 23 | 24 |
25 | 26 |

27 | Fast delivery in 1 hour with our ninja's 28 |

29 |

Get the fastest delivery

30 |
31 | 32 | 33 |
34 | 35 |

36 | Easy to order by web or apps 37 |

38 |

Get the fastest delivery

39 |
40 | 41 | 42 |
43 | 44 |

45 | Wide Coverage Map around the world 46 |

47 |

Get the fastest delivery

48 |
49 | 50 | 51 |
52 | 53 |

54 | More than 150+ Couriers 55 |

56 |

Get the fastest delivery

57 |
58 | 59 |
60 |
61 | ) 62 | } 63 | 64 | export default AwesomeFeatures 65 | -------------------------------------------------------------------------------- /client/src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { Row, Col } from 'react-bootstrap' 4 | import Product from '../components/Product' 5 | import { listProducts } from '../actions/productActions' 6 | import Loader from '../components/Loader' 7 | import Message from '../components/Message' 8 | import Paginate from '../components/Paginate' 9 | import Meta from '../components/Meta' 10 | import HeroSection from '../components/HeroSection' 11 | import AwesomeFeatures from '../components/AwesomeFeatures' 12 | import Slogan from '../components/Slogan' 13 | import { motion } from 'framer-motion' 14 | 15 | const HomeScreen = ({ match }) => { 16 | const keyword = match.params.keyword 17 | const pageNumber = match.params.pageNumber || 1 18 | 19 | const dispatch = useDispatch() 20 | 21 | const productList = useSelector((state) => state.productList) 22 | const { loading, products, error, page, pages } = productList 23 | 24 | useEffect(() => { 25 | window.scrollTo(0, 0) 26 | dispatch(listProducts(keyword, pageNumber)) 27 | }, [dispatch, keyword, pageNumber]) 28 | 29 | return ( 30 | 31 | {loading ? ( 32 | 33 | ) : error ? ( 34 | {error} 35 | ) : ( 36 | <> 37 | 38 | 39 | 40 | 41 | {/*!keyword ? : Go Back*/} 42 |
43 |

44 | Discover Our Menu 45 |

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

Orders

30 | {loading ? ( 31 | 32 | ) : error ? ( 33 | {error} 34 | ) : ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {orders.map((order) => ( 49 | 50 | 51 | 52 | 53 | 54 | 55 | 64 | 65 | 74 | 81 | 82 | ))} 83 | 84 |
IDUSERDATETOTAL PRICEPAIDSTATUS
{order._id}{order.user && order.user.name}{order.createdAt.substring(0, 10)}${order.totalPrice} 56 | {order.isPaid ? ( 57 | 58 | {order.paidAt.substring(0, 10)} 59 | 60 | ) : ( 61 |
Pending
62 | )} 63 |
66 | {order.isDelivered ? ( 67 | 68 | {order.deliveredAt.substring(0, 10)} 69 | 70 | ) : ( 71 |
Not Delivered
72 | )} 73 |
75 | 76 | 79 | 80 |
85 | )} 86 |
87 | ) 88 | } 89 | 90 | export default OrderListScreen 91 | -------------------------------------------------------------------------------- /client/src/screens/UserListScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { LinkContainer } from 'react-router-bootstrap' 3 | import { Table, Button, Tab } from 'react-bootstrap' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | import Message from '../components/Message' 6 | import Loader from '../components/Loader' 7 | import { listUsers, deleteUser } from '../actions/userActions' 8 | 9 | const UserListScreen = ({ history }) => { 10 | const dispatch = useDispatch() 11 | const userList = useSelector((state) => state.userList) 12 | const { loading, error, users } = userList 13 | 14 | const userLogin = useSelector((state) => state.userLogin) 15 | const { userInfo } = userLogin 16 | 17 | const userDelete = useSelector((state) => state.userDelete) 18 | const { success: successDelete, error: errorDelete } = userDelete 19 | 20 | useEffect(() => { 21 | window.scrollTo(0, 0) 22 | if (userInfo && userInfo.isAdmin) { 23 | dispatch(listUsers()) 24 | } else { 25 | history.push('/login') 26 | } 27 | }, [dispatch, history, userInfo, successDelete]) 28 | 29 | const deleteHandler = (id) => { 30 | if (window.confirm('Are you sure?')) { 31 | dispatch(deleteUser(id)) 32 | } 33 | } 34 | 35 | return ( 36 |
37 |

Users

38 | {loading ? ( 39 | 40 | ) : error ? ( 41 | {error} 42 | ) : ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {users.map((user) => ( 55 | 56 | 57 | 58 | 61 | 68 | 82 | 83 | ))} 84 | 85 |
IDNAMEEMAILADMINACTION
{user._id}{user.name} 59 | {user.email} 60 | 62 | {user.isAdmin ? ( 63 | 64 | ) : ( 65 | 66 | )} 67 | 69 | 70 | 73 | 74 | 81 |
86 | )} 87 |
88 | ) 89 | } 90 | 91 | export default UserListScreen 92 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router, Route } from 'react-router-dom' 3 | 4 | import { Container } from 'react-bootstrap' 5 | import Header from './components/Header' 6 | import Footer from './components/Footer' 7 | import HomeScreen from './screens/HomeScreen' 8 | import ProductScreen from './screens/ProductScreen' 9 | import CartScreen from './screens/CartScreen' 10 | import LoginScreen from './screens/LoginScreen' 11 | import RegisterScreen from './screens/RegisterScreen' 12 | import ProfileScreen from './screens/ProfileScreen' 13 | import ShippingScreen from './screens/ShippingScreen' 14 | import PaymentScreen from './screens/PaymentScreen' 15 | import PlaceOrderScreen from './screens/PlaceOrderScreen' 16 | import OrderScreen from './screens/OrderScreen' 17 | import UserListScreen from './screens/UserListScreen' 18 | import UserEditScreen from './screens/UserEditScreen' 19 | import ProductListScreen from './screens/ProductListScreen' 20 | import ProductEditScreen from './screens/ProductEditScreen' 21 | import OrderListScreen from './screens/OrderListScreen' 22 | import { AnimatePresence } from 'framer-motion' 23 | 24 | const App = () => { 25 | return ( 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 51 | 52 | 53 | 57 | 58 | 59 | 64 | 65 | 66 |
67 |