├── 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 | Find Foods
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 | Ah! I want to order
19 |
20 |
21 |
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 |
setKeyword(e.target.value)}
24 | placeholder='Search Product'
25 | className={`mr-sm-2 ml-sm-5 ${styles.searchbox} ${styles.form_control}`}
26 | >
27 |
32 | Search
33 |
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 | You need to enable JavaScript to run this app.
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 |
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 |
8 |
9 | {step1 ? (
10 |
11 | Signin
12 |
13 | ) : (
14 | Signin
15 | )}
16 |
17 |
18 |
19 | {step2 ? (
20 |
21 | Shipping
22 |
23 | ) : (
24 | Shipping
25 | )}
26 |
27 |
28 |
29 | {step3 ? (
30 |
31 | Payment
32 |
33 | ) : (
34 | Payment
35 | )}
36 |
37 |
38 |
39 | {step4 ? (
40 |
41 | Place Order
42 |
43 | ) : (
44 | Place Order
45 | )}
46 |
47 |
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 | Details
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 | 
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 |
41 | Select a payment method
42 |
43 | setPaymentMethod(e.target.value)}
51 | className='pl-0'
52 | style={{ fontSize: '18px' }}
53 | >
54 |
55 |
56 |
57 | Continue
58 |
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 |
7 |
8 |
9 |
10 | Firehouse Pizza Shop
11 |
12 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
13 | Cupiditate autem suscipit harum, consequatur nulla incidunt
14 | possimus facilis quaerat nam vel earum nesciunt, magnam laboriosam
15 |
16 |
17 |
18 | Services
19 |
20 | Delivery Services
21 |
22 |
23 | Contact Us
24 |
25 |
26 | Terms of Use
27 |
28 |
29 | Privecy Policy
30 |
31 |
32 |
33 |
34 | Users
35 |
36 | User Login
37 |
38 |
39 | User Register
40 |
41 |
42 | Account Settings
43 |
44 |
45 | Orders
46 |
47 |
48 |
49 |
50 | Pages
51 |
52 | Near Restaurants
53 |
54 |
55 | Restaurant Details
56 |
57 |
58 | Available Regions
59 |
60 |
61 | Shipping Terms
62 |
63 |
64 |
65 |
66 |
67 | Copyright © Firehouse Pizza Shop
68 | Shahnewaz Tameem All rights reserved
69 |
70 |
71 |
72 |
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 | ID
39 | USER
40 | DATE
41 | TOTAL PRICE
42 | PAID
43 | STATUS
44 |
45 |
46 |
47 |
48 | {orders.map((order) => (
49 |
50 | {order._id}
51 | {order.user && order.user.name}
52 | {order.createdAt.substring(0, 10)}
53 | ${order.totalPrice}
54 |
55 |
56 | {order.isPaid ? (
57 |
58 | {order.paidAt.substring(0, 10)}
59 |
60 | ) : (
61 | Pending
62 | )}
63 |
64 |
65 |
66 | {order.isDelivered ? (
67 |
68 | {order.deliveredAt.substring(0, 10)}
69 |
70 | ) : (
71 | Not Delivered
72 | )}
73 |
74 |
75 |
76 |
77 | Details
78 |
79 |
80 |
81 |
82 | ))}
83 |
84 |
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 | ID
47 | NAME
48 | EMAIL
49 | ADMIN
50 | ACTION
51 |
52 |
53 |
54 | {users.map((user) => (
55 |
56 | {user._id}
57 | {user.name}
58 |
59 | {user.email}
60 |
61 |
62 | {user.isAdmin ? (
63 |
64 | ) : (
65 |
66 | )}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | deleteHandler(user._id)}
78 | >
79 |
80 |
81 |
82 |
83 | ))}
84 |
85 |
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 |
68 |
69 |
70 | )
71 | }
72 |
73 | export default App
74 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `yarn build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/src/screens/ShippingScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, 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 { saveShippingAddress } from '../actions/cartActions'
6 | import CheckoutSteps from '../components/CheckoutSteps'
7 | import { motion } from 'framer-motion'
8 |
9 | const ShippingScreen = ({ history }) => {
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 | useEffect(() => {
21 | window.scrollTo(0, 0)
22 | },[])
23 |
24 | const submitHandler = (e) => {
25 | e.preventDefault()
26 | dispatch(saveShippingAddress({ address, city, postalCode, country }))
27 | history.push('/payment')
28 | }
29 |
30 | return (
31 |
37 |
38 |
39 | Shipping
40 |
42 | Address
43 | setAddress(e.target.value)}
49 | className='input'
50 | >
51 |
52 |
53 |
54 | City
55 | setCity(e.target.value)}
61 | className='input'
62 | >
63 |
64 |
65 |
66 | Postal Code
67 | setPostalCode(e.target.value)}
73 | className='input'
74 | >
75 |
76 |
77 |
78 | Country
79 | setCountry(e.target.value)}
85 | className='input'
86 | >
87 |
88 |
89 |
94 | Continue
95 |
96 |
97 |
98 |
99 | )
100 | }
101 |
102 | export default ShippingScreen
103 |
--------------------------------------------------------------------------------
/server/controller/orderController.js:
--------------------------------------------------------------------------------
1 | import asyncHandler from 'express-async-handler'
2 | import Order from '../models/orderModel.js'
3 |
4 | //@desc Create New Order
5 | //@route POST /api/orders
6 | //@access Private
7 | const addOrderItems = asyncHandler(async (req, res) => {
8 | const {
9 | orderItems,
10 | shippingAddress,
11 | paymentMethod,
12 | itemsPrice,
13 | taxPrice,
14 | shippingPrice,
15 | totalPrice,
16 | } = req.body
17 |
18 | if (orderItems && orderItems.length === 0) {
19 | res.status(400)
20 | throw new Error('No order items')
21 | return
22 | } else {
23 | const order = new Order({
24 | orderItems,
25 | user: req.user._id,
26 | shippingAddress,
27 | paymentMethod,
28 | itemsPrice,
29 | taxPrice,
30 | shippingPrice,
31 | totalPrice,
32 | })
33 |
34 | const createdOrder = await order.save()
35 |
36 | res.status(201).json(createdOrder)
37 | }
38 | })
39 |
40 | //@desc Get order by id
41 | //@route GET /api/orders/:id
42 | //@access Private
43 | const getOrderById = asyncHandler(async (req, res) => {
44 | const order = await Order.findById(req.params.id).populate(
45 | 'user',
46 | 'name email'
47 | )
48 |
49 | if (order) {
50 | res.json(order)
51 | } else {
52 | res.status(404)
53 | throw new Error('Order not found')
54 | }
55 | })
56 |
57 | //@desc Update order to paid
58 | //@route GET /api/orders/:id/pay
59 | //@access Private
60 | const updateOrderToPaid = asyncHandler(async (req, res) => {
61 | const order = await Order.findById(req.params.id)
62 |
63 | if (order) {
64 | order.isPaid = true
65 | order.paidAt = Date.now()
66 | order.paymentResult = {
67 | id: req.body.id,
68 | status: req.body.status,
69 | update_time: req.body.update_time,
70 | email_address: req.body.payer.email_address,
71 | }
72 |
73 | const updatedOrder = await order.save()
74 | res.json(updatedOrder)
75 | } else {
76 | res.status(404)
77 | throw new Error('Order not found')
78 | }
79 | })
80 |
81 | //@desc Get logged in user orders
82 | //@route GET /api/orders/myorders
83 | //@access Private
84 | const getMyOrders = asyncHandler(async (req, res) => {
85 | const orders = await Order.find({ user: req.user._id })
86 | res.json(orders)
87 | })
88 |
89 | //@desc Get all orders
90 | //@route GET /api/orders
91 | //@access Private/Admin
92 | const getOrders = asyncHandler(async (req, res) => {
93 | const orders = await Order.find({}).populate('user', 'id name email')
94 | res.json(orders)
95 | })
96 |
97 | //@desc Update order to pending
98 | //@route GET /api/orders/:id/pending
99 | //@access Private
100 | const updateOrderToPending = asyncHandler(async (req, res) => {
101 | const order = await Order.findById(req.params.id)
102 |
103 | if (order) {
104 | order.isPending = true
105 | order.pendingAt = Date.now()
106 | const updatedOrder = await order.save()
107 | res.json(updatedOrder)
108 | } else {
109 | res.status(404)
110 | throw new Error('Order not found')
111 | }
112 | })
113 |
114 | //@desc Update order to delivered
115 | //@route GET /api/orders/:id/delivered
116 | //@access Private
117 | const updateOrderToDelivered = asyncHandler(async (req, res) => {
118 | const order = await Order.findById(req.params.id)
119 |
120 | if (order) {
121 | order.isDelivered = true
122 | order.deliveredAt = Date.now()
123 |
124 | const updatedOrder = await order.save()
125 | res.json(updatedOrder)
126 | } else {
127 | res.status(404)
128 | throw new Error('Order not found')
129 | }
130 | })
131 |
132 | export {
133 | addOrderItems,
134 | getOrderById,
135 | updateOrderToPaid,
136 | getMyOrders,
137 | getOrders,
138 | updateOrderToPending,
139 | updateOrderToDelivered,
140 | }
141 |
--------------------------------------------------------------------------------
/client/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route } from 'react-router-dom'
3 | import { LinkContainer } from 'react-router-bootstrap'
4 | import { Navbar, Nav, Container, NavDropdown, Image } from 'react-bootstrap'
5 | import { useDispatch, useSelector } from 'react-redux'
6 | import { logout } from '../actions/userActions'
7 | import SearchBox from './SearchBox'
8 | import HeroSection from './HeroSection'
9 | import logo from '../assets/logo/logo.png'
10 | import userProfile from '../assets/icons/user-profile.svg'
11 | import shoppingCart from '../assets/icons/shopping-cart.svg'
12 | import styles from './Header.module.css'
13 |
14 | const Header = () => {
15 | const dispatch = useDispatch()
16 |
17 | const userLogin = useSelector((state) => state.userLogin)
18 | const { userInfo } = userLogin
19 |
20 | const cart = useSelector((state) => state.cart)
21 | const { cartItems } = cart
22 | console.log(cartItems.length)
23 |
24 | const logoutHandler = () => {
25 | dispatch(logout())
26 | }
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 | } />
44 |
45 |
46 |
47 | {' '}
48 |
49 | {' '}
50 | {cartItems.length}
51 |
52 |
53 |
54 |
55 | {userInfo ? (
56 |
57 |
58 | Profile
59 |
60 |
61 | Logout
62 |
63 |
64 | ) : (
65 |
66 |
67 |
68 |
74 | {' '}
75 | Sign In
76 |
77 |
78 | )}
79 |
80 | {userInfo && userInfo.isAdmin && (
81 |
94 | )}
95 |
96 |
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | export default Header
104 |
--------------------------------------------------------------------------------
/client/src/reducers/userReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | USER_DETAILS_FAIL,
3 | USER_DETAILS_REQUEST,
4 | USER_DETAILS_SUCCESS,
5 | USER_DETAILS_RESET,
6 | USER_LOGIN_FAIL,
7 | USER_LOGIN_REQUEST,
8 | USER_LOGIN_SUCCESS,
9 | USER_LOGOUT,
10 | USER_REGISTER_FAIL,
11 | USER_REGISTER_REQUEST,
12 | USER_REGISTER_SUCCESS,
13 | USER_UPDATE_PROFILE_FAIL,
14 | USER_UPDATE_PROFILE_REQUEST,
15 | USER_UPDATE_PROFILE_SUCCESS,
16 | USER_LIST_REQUEST,
17 | USER_LIST_SUCCESS,
18 | USER_LIST_FAIL,
19 | USER_LIST_RESET,
20 | USER_DELETE_REQUEST,
21 | USER_DELETE_SUCCESS,
22 | USER_DELETE_FAIL,
23 | USER_UPDATE_SUCCESS,
24 | USER_UPDATE_FAIL,
25 | USER_UPDATE_REQUEST,
26 | USER_UPDATE_RESET,
27 | USER_UPDATE_PROFILE_RESET,
28 | } from '../constants/userConstants'
29 |
30 | export const userLoginReducer = (state = {}, action) => {
31 | switch (action.type) {
32 | case USER_LOGIN_REQUEST:
33 | return { loading: true }
34 | case USER_LOGIN_SUCCESS:
35 | return { loading: false, userInfo: action.payload }
36 | case USER_LOGIN_FAIL:
37 | return { loading: false, error: action.payload }
38 |
39 | case USER_LOGOUT:
40 | return {}
41 | default:
42 | return state
43 | }
44 | }
45 |
46 | export const userRegisterReducer = (state = {}, action) => {
47 | switch (action.type) {
48 | case USER_REGISTER_REQUEST:
49 | return { loading: true }
50 | case USER_REGISTER_SUCCESS:
51 | return { loading: false, userInfo: action.payload }
52 | case USER_REGISTER_FAIL:
53 | return { loading: false, error: action.payload }
54 | case USER_LOGOUT:
55 | return {}
56 | default:
57 | return state
58 | }
59 | }
60 |
61 | export const userDetailsReducer = (state = { user: {} }, action) => {
62 | switch (action.type) {
63 | case USER_DETAILS_REQUEST:
64 | return { ...state, loading: true }
65 | case USER_DETAILS_SUCCESS:
66 | return { loading: false, user: action.payload }
67 | case USER_DETAILS_FAIL:
68 | return { loading: false, error: action.payload }
69 | case USER_DETAILS_RESET:
70 | return { user: {} }
71 |
72 | default:
73 | return state
74 | }
75 | }
76 |
77 | export const userUpdateProfileReducer = (state = {}, action) => {
78 | switch (action.type) {
79 | case USER_UPDATE_PROFILE_REQUEST:
80 | return { loading: true }
81 | case USER_UPDATE_PROFILE_SUCCESS:
82 | return { loading: false, success: true, userInfo: action.payload }
83 | case USER_UPDATE_PROFILE_FAIL:
84 | return { loading: false, error: action.payload }
85 | case USER_UPDATE_PROFILE_RESET:
86 | return {}
87 | default:
88 | return state
89 | }
90 | }
91 |
92 | export const userListReducer = (state = { users: [] }, action) => {
93 | switch (action.type) {
94 | case USER_LIST_REQUEST:
95 | return { loading: true }
96 | case USER_LIST_SUCCESS:
97 | return { loading: false, users: action.payload }
98 | case USER_LIST_FAIL:
99 | return { loading: false, error: action.payload }
100 | case USER_LIST_RESET:
101 | return { users: [] }
102 | default:
103 | return state
104 | }
105 | }
106 |
107 | export const userDeleteReducer = (state = {}, action) => {
108 | switch (action.type) {
109 | case USER_DELETE_REQUEST:
110 | return { loading: true }
111 | case USER_DELETE_SUCCESS:
112 | return { loading: false, success: true }
113 | case USER_DELETE_FAIL:
114 | return { loading: false, error: action.payload }
115 | default:
116 | return state
117 | }
118 | }
119 |
120 | export const userUpdateReducer = (state = { user: {} }, action) => {
121 | switch (action.type) {
122 | case USER_UPDATE_REQUEST:
123 | return { loading: true }
124 | case USER_UPDATE_SUCCESS:
125 | return { loading: false, success: true }
126 | case USER_UPDATE_FAIL:
127 | return { loading: false, error: action.payload }
128 | case USER_UPDATE_RESET:
129 | return { user: {} }
130 | default:
131 | return state
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/client/src/reducers/productReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | PRODUCT_LIST_REQUEST,
3 | PRODUCT_LIST_SUCCESS,
4 | PRODUCT_LIST_FAIL,
5 | PRODUCT_DETAILS_REQUEST,
6 | PRODUCT_DETAILS_SUCCESS,
7 | PRODUCT_DETAILS_FAIL,
8 | PRODUCT_DELETE_REQUEST,
9 | PRODUCT_DELETE_SUCCESS,
10 | PRODUCT_DELETE_FAIL,
11 | PRODUCT_CREATE_RESET,
12 | PRODUCT_CREATE_FAIL,
13 | PRODUCT_CREATE_SUCCESS,
14 | PRODUCT_CREATE_REQUEST,
15 | PRODUCT_UPDATE_REQUEST,
16 | PRODUCT_UPDATE_SUCCESS,
17 | PRODUCT_UPDATE_FAIL,
18 | PRODUCT_UPDATE_RESET,
19 | PRODUCT_CREATE_REVIEW_REQUEST,
20 | PRODUCT_CREATE_REVIEW_SUCCESS,
21 | PRODUCT_CREATE_REVIEW_FAIL,
22 | PRODUCT_CREATE_REVIEW_RESET,
23 | PRODUCT_TOP_REQUEST,
24 | PRODUCT_TOP_SUCCESS,
25 | PRODUCT_TOP_FAIL,
26 | } from '../constants/productConstants'
27 |
28 | export const productListReducer = (state = { products: [] }, action) => {
29 | switch (action.type) {
30 | case PRODUCT_LIST_REQUEST:
31 | return { loading: true, products: [] }
32 | case PRODUCT_LIST_SUCCESS:
33 | return {
34 | loading: false,
35 | products: action.payload.products,
36 | pages: action.payload.pages,
37 | page: action.payload.page,
38 | }
39 | case PRODUCT_LIST_FAIL:
40 | return { loading: false, error: action.payload }
41 | default:
42 | return state
43 | }
44 | }
45 |
46 | export const productDetailsReducer = (
47 | state = { product: { reviews: [] } },
48 | action
49 | ) => {
50 | switch (action.type) {
51 | case PRODUCT_DETAILS_REQUEST:
52 | return { ...state, loading: true }
53 | case PRODUCT_DETAILS_SUCCESS:
54 | return { loading: false, product: action.payload }
55 | case PRODUCT_DETAILS_FAIL:
56 | return { loading: false, error: action.payload }
57 | default:
58 | return state
59 | }
60 | }
61 |
62 | export const productDeleteReducer = (state = {}, action) => {
63 | switch (action.type) {
64 | case PRODUCT_DELETE_REQUEST:
65 | return { loading: true }
66 | case PRODUCT_DELETE_SUCCESS:
67 | return { loading: false, success: true }
68 | case PRODUCT_DELETE_FAIL:
69 | return { loading: false, error: action.payload }
70 | default:
71 | return state
72 | }
73 | }
74 |
75 | export const productCreateReducer = (state = {}, action) => {
76 | switch (action.type) {
77 | case PRODUCT_CREATE_REQUEST:
78 | return { loading: true }
79 | case PRODUCT_CREATE_SUCCESS:
80 | return { loading: false, success: true, product: action.payload }
81 | case PRODUCT_CREATE_FAIL:
82 | return { loading: false, error: action.payload }
83 | case PRODUCT_CREATE_RESET:
84 | return {}
85 | default:
86 | return state
87 | }
88 | }
89 |
90 | export const productUpdateReducer = (state = { product: {} }, action) => {
91 | switch (action.type) {
92 | case PRODUCT_UPDATE_REQUEST:
93 | return { loading: true }
94 | case PRODUCT_UPDATE_SUCCESS:
95 | return { loading: false, success: true, product: action.payload }
96 | case PRODUCT_UPDATE_FAIL:
97 | return { loading: false, error: action.payload }
98 | case PRODUCT_UPDATE_RESET:
99 | return { product: {} }
100 | default:
101 | return state
102 | }
103 | }
104 |
105 | export const productReviewCreateReducer = (state = {}, action) => {
106 | switch (action.type) {
107 | case PRODUCT_CREATE_REVIEW_REQUEST:
108 | return { loading: true }
109 | case PRODUCT_CREATE_REVIEW_SUCCESS:
110 | return { loading: false, success: true }
111 | case PRODUCT_CREATE_REVIEW_FAIL:
112 | return { loading: false, error: action.payload }
113 | case PRODUCT_CREATE_REVIEW_RESET:
114 | return {}
115 | default:
116 | return state
117 | }
118 | }
119 |
120 | export const productTopRatedReducer = (state = { products: [] }, action) => {
121 | switch (action.type) {
122 | case PRODUCT_TOP_REQUEST:
123 | return { loading: true, products: [] }
124 | case PRODUCT_TOP_SUCCESS:
125 | return { loading: false, products: action.payload }
126 | case PRODUCT_TOP_FAIL:
127 | return { loading: false, error: action.payload }
128 |
129 | default:
130 | return state
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/client/src/screens/UserEditScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { Form, Button, Spinner, Image } from 'react-bootstrap'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import Message from '../components/Message'
6 | import FormContainer from '../components/FormContainer'
7 | import Loader from '../components/Loader'
8 | import { getUserDetails, updateUser } from '../actions/userActions'
9 | import { USER_UPDATE_RESET } from '../constants/userConstants'
10 | import leftArrow from '../assets/icons/arrow-left.svg'
11 |
12 | const UserEditScreen = ({ match, history }) => {
13 | const userId = match.params.id
14 |
15 | const [name, setName] = useState('')
16 | const [email, setEmail] = useState('')
17 | const [isAdmin, setIsAdmin] = useState(false)
18 |
19 | const dispatch = useDispatch()
20 |
21 | const userDetails = useSelector((state) => state.userDetails)
22 | const { loading, error, user } = userDetails
23 |
24 | const userUpdate = useSelector((state) => state.userUpdate)
25 | const {
26 | loading: loadingUpdate,
27 | error: errorUpdate,
28 | success: successUpdate,
29 | } = userUpdate
30 |
31 | useEffect(() => {
32 | window.scrollTo(0, 0)
33 | if (successUpdate) {
34 | dispatch({ type: USER_UPDATE_RESET })
35 | history.push('/admin/userlist')
36 | } else {
37 | if (!user.name || user._id !== userId) {
38 | dispatch(getUserDetails(userId))
39 | } else {
40 | setName(user.name)
41 | setEmail(user.email)
42 | setIsAdmin(user.isAdmin)
43 | }
44 | }
45 | }, [dispatch, userId, user, successUpdate, history])
46 |
47 | const submitHandler = (e) => {
48 | e.preventDefault()
49 | dispatch(updateUser({ _id: userId, name, email, isAdmin }))
50 | }
51 |
52 | return (
53 |
54 |
55 |
56 |
57 | {' '}
58 | Go Back
59 |
60 |
61 |
62 | Edit User Details
63 | {/*loadingUpdate && */}
64 | {errorUpdate && {errorUpdate} }
65 | {loading ? (
66 |
67 | ) : error ? (
68 | {error}
69 | ) : (
70 |
72 | Full Name
73 | setName(e.target.value)}
78 | className='input'
79 | >
80 |
81 |
82 |
83 | Email Address
84 | setEmail(e.target.value)}
89 | className='input'
90 | >
91 |
92 |
93 |
94 | setIsAdmin(e.target.checked)}
99 | >
100 |
101 |
102 | {loadingUpdate ? (
103 |
104 |
105 | {' '} Updating User
106 |
107 |
108 | ) : (
109 |
110 | Update User
111 |
112 | )}
113 |
114 | )}
115 |
116 |
117 | )
118 | }
119 |
120 | export default UserEditScreen
121 |
--------------------------------------------------------------------------------
/client/src/screens/LoginScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { Form, Button, Row, Col, Spinner, Image } from 'react-bootstrap'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import Message from '../components/Message'
6 | import { login } from '../actions/userActions'
7 | import FormContainer from '../components/FormContainer'
8 | import mailIcon from '../assets/icons/mail.svg'
9 | import { ToastContainer, Slide } from 'react-toastify'
10 | import 'react-toastify/dist/ReactToastify.css'
11 | import { motion } from 'framer-motion'
12 |
13 | const LoginScreen = ({ location, history }) => {
14 | const [email, setEmail] = useState('')
15 | const [password, setPassword] = useState('')
16 |
17 | const dispatch = useDispatch()
18 |
19 | const userLogin = useSelector((state) => state.userLogin)
20 | const { loading, error, userInfo } = userLogin
21 |
22 | const redirect = location.search ? location.search.split('=')[1] : '/'
23 |
24 | useEffect(() => {
25 | window.scrollTo(0, 0)
26 | if (userInfo) {
27 | history.push(redirect)
28 | }
29 | }, [history, userInfo, redirect])
30 |
31 | const submitHandler = (e) => {
32 | e.preventDefault()
33 | dispatch(login(email, password))
34 | }
35 |
36 | return (
37 |
43 |
44 |
45 |
Hello
46 | Sign into your account
47 |
48 |
49 | {error && (
50 |
61 | )}
62 | {/*loading && */}
63 |
65 | Email Address
66 | setEmail(e.target.value)}
71 | className='input'
72 | >
73 |
74 |
75 |
76 | Password
77 | setPassword(e.target.value)}
82 | className='input'
83 | >
84 |
85 |
86 | {loading ? (
87 |
88 |
95 |
96 |
97 |
98 | ) : (
99 |
105 | Sign In
106 |
107 | )}
108 |
109 |
110 |
111 |
112 | Don't Have an account?
113 |
116 |
117 | Signup
118 |
119 |
120 |
121 |
122 |
123 |
124 | )
125 | }
126 |
127 | export default LoginScreen
128 |
--------------------------------------------------------------------------------
/server/data/products.js:
--------------------------------------------------------------------------------
1 | const products = [
2 | {
3 | name: 'Prosciutto e funghi pizza',
4 | image: '/images/pizza-1.jpg',
5 | description:
6 | 'Prosciutto e funghi is a pizza variety that is topped with tomato sauce, mozzarella, thin slices of prosciutto cotto, and mushrooms. Some varieties can be topped with olives or served drizzled with olive oil.',
7 | brand: 'Pizza',
8 | category: 'Fast Food',
9 | price: 89.99,
10 | countInStock: 10,
11 | rating: 4.5,
12 | numReviews: 12,
13 | },
14 | {
15 | name: 'Garlic fingers',
16 | image: '/images/pizza-2.jpg',
17 | description:
18 | 'Garlic fingers is a popular food item throughout Atlantic Canada. Even though it looks like a pizza, garlic fingers are cut in strips or fingers instead of being cut into slices like a regular pizza. The dish consists of pizza dough that is topped with cheese, garlic butter, and parsley. It is baked until the cheese melts, and it can then be additionally topped with dill, vegetables, or pieces of bacon. Garlic fingers are often consumed with regular pizza as a side dish, and they are typically accompanied by dipping sauces such as Donair or marinara. ',
19 | brand: 'Pizza',
20 | category: 'Fast Food',
21 | price: 599.99,
22 | countInStock: 7,
23 | rating: 4.0,
24 | numReviews: 8,
25 | },
26 | {
27 | name: 'Pizza Grandiosa',
28 | image: '/images/pizza-3.jpg',
29 | description:
30 | 'Norwegian Grandiosa—whose name is derived from the Italian word for big or great—is the first frozen pizza ever produced in Norway. When it first appeared in 1980, the choice of toppings in Grandiosa Original included tomato sauce, pizza meat, Jarlsberg cheese, and peppers, but the company soon expanded the basic flavor range with classics like Grandiosa Pepperoni, Grandiosa Kjøttdeig & Løk (beef mince and onions) and Grandiosa Helmax (ham and salami), whereas now you can even indulge in Grandiosa Hot Nacho and Grandiosa Kebab pizza.',
31 | brand: 'Pizza',
32 | category: 'Fast Food',
33 | price: 929.99,
34 | countInStock: 5,
35 | rating: 3,
36 | numReviews: 12,
37 | },
38 | {
39 | name: 'Fugazzeta ',
40 | image: '/images/pizza-4.jpg',
41 | description:
42 | 'Fugazzeta is a mozzarella-stuffed pizza topped with onions, originating from Argentina. The onions should be thinly sliced, and they can be either raw or sautéed. The dough is usually made with milk, water, yeast, flour, sugar, salt, and olive oil.',
43 | brand: 'Pizza',
44 | category: 'Fast Food',
45 | price: 399.99,
46 | countInStock: 11,
47 | rating: 5,
48 | numReviews: 12,
49 | },
50 | {
51 | name: 'Detroit-Style pizza',
52 | image: '/images/pizza-5.png',
53 | description:
54 | 'Detroit-style pizza is a square pizza characterized by a thick deep-dish crisp crust and inverted toppings. Cheese is applied directly to the top of the dough, followed by a thick tomato sauce that is seasoned with garlic and spices. The most common topping is pepperoni, put either on top of the sauce or buried underneath the cheese.',
55 | brand: 'Pizza',
56 | category: 'Fast Food',
57 | price: 49.99,
58 | countInStock: 7,
59 | rating: 3.5,
60 | numReviews: 10,
61 | },
62 | {
63 | name: 'Pizza Ortolana',
64 | image: '/images/pizza-6.jpg',
65 | description:
66 | 'The name of this classic Italian pizza translates to greengrocer’s pizza. It consists of a basic pizza dough which is smeared with tomato sauce, topped with mozzarella and grilled slices of eggplant and zucchini, then baked. Lastly, pizza ortolana is typically drizzled with olive oil and topped with fresh basil.',
67 | brand: 'Pizza',
68 | category: 'Fast Food',
69 | price: 29.99,
70 | countInStock: 0,
71 | rating: 4,
72 | numReviews: 12,
73 | },
74 | {
75 | name: 'Pizza fiori di zucca',
76 | image: '/images/pizza-7.jpg',
77 | description:
78 | 'This is a variety of Italian pizza that is traditionally topped with mozzarella cheese, zucchini flowers, olive oil, and salted anchovies. If one is using the untraditional method, it is recommended to add some black pepper and garlic for extra flavor.',
79 | brand: 'Pizza',
80 | category: 'Fast Food',
81 | price: 49.99,
82 | countInStock: 0,
83 | rating: 4,
84 | numReviews: 12,
85 | },
86 | ]
87 |
88 | export default products
89 |
--------------------------------------------------------------------------------
/client/src/screens/ProductListScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { LinkContainer } from 'react-router-bootstrap'
3 | import { Table, 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 Paginate from '../components/Paginate'
8 | import {
9 | listProducts,
10 | deleteProduct,
11 | createProduct,
12 | } from '../actions/productActions'
13 | import { PRODUCT_CREATE_RESET } from '../constants/productConstants'
14 |
15 | const ProductListScreen = ({ match, history }) => {
16 | const pageNumber = match.params.pageNumber || 1
17 |
18 | const dispatch = useDispatch()
19 | const productList = useSelector((state) => state.productList)
20 | const { loading, error, products, page, pages } = productList
21 |
22 | const productDelete = useSelector((state) => state.productDelete)
23 | const {
24 | loading: loadingDelete,
25 | error: errorDelete,
26 | success: successDelete,
27 | } = productDelete
28 |
29 | const productCreate = useSelector((state) => state.productCreate)
30 | const {
31 | loading: loadingCreate,
32 | error: errorCreate,
33 | success: successCreate,
34 | product: createdProduct,
35 | } = productCreate
36 |
37 | const userLogin = useSelector((state) => state.userLogin)
38 | const { userInfo } = userLogin
39 |
40 | useEffect(() => {
41 | window.scrollTo(0, 0)
42 | dispatch({ type: PRODUCT_CREATE_RESET })
43 | if (!userInfo.isAdmin) {
44 | history.push('/login')
45 | }
46 | if (successCreate) {
47 | history.push(`/admin/product/${createdProduct._id}/edit`)
48 | } else {
49 | dispatch(listProducts('', pageNumber))
50 | }
51 | }, [
52 | dispatch,
53 | history,
54 | userInfo,
55 | successDelete,
56 | successCreate,
57 | createdProduct,
58 | pageNumber,
59 | ])
60 |
61 | const deleteHandler = (id) => {
62 | if (window.confirm('Are you sure?')) {
63 | dispatch(deleteProduct(id))
64 | }
65 | }
66 |
67 | const createProductHandler = () => {
68 | dispatch(createProduct())
69 | }
70 |
71 | return (
72 |
73 |
74 |
75 | Products
76 |
77 |
78 |
79 | Create Product
80 |
81 |
82 |
83 | {loadingDelete &&
}
84 | {errorDelete &&
{errorDelete} }
85 | {loadingCreate &&
}
86 | {errorCreate &&
{errorCreate} }
87 |
88 | {loading ? (
89 |
90 | ) : error ? (
91 |
{error}
92 | ) : (
93 | <>
94 |
95 |
96 |
97 | ID
98 | NAME
99 | PRICE
100 | CATEGORY
101 | BRAND
102 | ACTIONS
103 |
104 |
105 |
106 | {products.map((product) => (
107 |
108 | {product._id}
109 | {product.name}
110 | ${product.price}
111 | {product.category}
112 | {product.brand}
113 |
114 |
115 |
116 |
117 |
118 |
119 | deleteHandler(product._id)}
123 | >
124 |
125 |
126 |
127 |
128 | ))}
129 |
130 |
131 |
132 | >
133 | )}
134 |
135 | )
136 | }
137 |
138 | export default ProductListScreen
139 |
--------------------------------------------------------------------------------
/server/controller/productController.js:
--------------------------------------------------------------------------------
1 | import asyncHandler from 'express-async-handler'
2 | import Product from '../models/productModel.js'
3 |
4 | //@desc Fetch All products
5 | //@route GET /api/products
6 | //@access Public
7 | const getProducts = asyncHandler(async (req, res) => {
8 | const pageSize = 10
9 | const page = Number(req.query.pageNumber) || 1
10 |
11 | const keyword = req.query.keyword
12 | ? {
13 | name: {
14 | $regex: req.query.keyword,
15 | $options: 'i',
16 | },
17 | }
18 | : {}
19 |
20 | const count = await Product.countDocuments({ ...keyword })
21 | const products = await Product.find({ ...keyword })
22 | .limit(pageSize)
23 | .skip(pageSize * (page - 1))
24 |
25 | res.json({ products, page, pages: Math.ceil(count / pageSize) })
26 | })
27 |
28 | //@desc Fetch a single products
29 | //@route GET /api/products/:id
30 | //@access Public
31 | const getProductById = asyncHandler(async (req, res) => {
32 | const product = await Product.findById(req.params.id)
33 |
34 | if (product) {
35 | res.json(product)
36 | } else {
37 | res.status(404)
38 | throw new Error('Product Not Found!')
39 | }
40 | })
41 |
42 | //@desc Delete a product
43 | //@route DELETE /api/products/:id
44 | //@access Private/Admin
45 | const deleteProduct = asyncHandler(async (req, res) => {
46 | const product = await Product.findById(req.params.id)
47 |
48 | if (product) {
49 | await product.remove()
50 | res.json({ messsage: 'Product Deleted Successfully' })
51 | } else {
52 | res.status(404)
53 | throw new Error('Product Not Found!')
54 | }
55 | })
56 |
57 | //@desc Create a product
58 | //@route POST /api/products
59 | //@access Private/Admin
60 | const createProduct = asyncHandler(async (req, res) => {
61 | const product = new Product({
62 | name: 'Sample Name',
63 | price: 0,
64 | user: req.user._id,
65 | image: '/images/sample.jpg',
66 | brand: 'Sample brand',
67 | category: 'Sample category',
68 | countInStock: 0,
69 | numReviews: 0,
70 | description: 'Sample Description',
71 | })
72 | const createdProduct = await product.save()
73 | res.status(201).json(createdProduct)
74 | })
75 |
76 | //@desc Upudate a product
77 | //@route PUT /api/products/:id
78 | //@access Private/Admin
79 | const updateProduct = asyncHandler(async (req, res) => {
80 | const { name, price, description, image, brand, category, countInStock } =
81 | req.body
82 |
83 | const product = await Product.findById(req.params.id)
84 | if (product) {
85 | product.name = name
86 | product.price = price
87 | product.description = description
88 | product.image = image
89 | product.brand = brand
90 | product.category = category
91 | product.countInStock = countInStock
92 |
93 | const updatedProduct = await product.save()
94 | res.json(updatedProduct)
95 | } else {
96 | res.status(404)
97 | throw new Error('Product Not Found')
98 | }
99 | })
100 |
101 | //@desc Create new review
102 | //@route POST /api/products/:id/reviews
103 | //@access Private
104 | const createProductReview = asyncHandler(async (req, res) => {
105 | const { rating, comment } = req.body
106 |
107 | const product = await Product.findById(req.params.id)
108 |
109 | if (product) {
110 | const alreadyReviewed = product.reviews.find(
111 | (r) => r.user.toString() === req.user._id.toString()
112 | )
113 |
114 | if (alreadyReviewed) {
115 | res.status(400)
116 | throw new Error('Product already reviewed')
117 | }
118 |
119 | const review = {
120 | name: req.user.name,
121 | rating: Number(rating),
122 | comment,
123 | user: req.user._id,
124 | }
125 |
126 | product.reviews.push(review)
127 |
128 | product.numReviews = product.reviews.length
129 |
130 | product.rating =
131 | product.reviews.reduce((acc, item) => item.rating + acc, 0) /
132 | product.reviews.length
133 |
134 | await product.save()
135 | res.status(201).json({ message: 'Review added' })
136 | } else {
137 | res.status(404)
138 | throw new Error('Product not found')
139 | }
140 | })
141 |
142 | //@desc Get Top rated products
143 | //@route GET /api/products/top
144 | //@access Public
145 | const getTopProducts = asyncHandler(async (req, res) => {
146 | const products = await Product.find({}).sort({ rating: -1 }).limit(3)
147 |
148 | res.json(products)
149 | })
150 | export {
151 | getProducts,
152 | getProductById,
153 | deleteProduct,
154 | createProduct,
155 | updateProduct,
156 | createProductReview,
157 | getTopProducts,
158 | }
159 |
--------------------------------------------------------------------------------
/client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/17896441.18c45255.js.download:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[9],{141:function(e,t,a){"use strict";a.r(t);var n=a(0),l=a.n(n),r=a(165),c=a(153),i=a(144),m=a(147),s=a(154);var o=function(e){const{metadata:t}=e;return l.a.createElement("nav",{className:"pagination-nav"},l.a.createElement("div",{className:"pagination-nav__item"},t.previous&&l.a.createElement(s.a,{className:"pagination-nav__link",to:t.previous.permalink},l.a.createElement("div",{className:"pagination-nav__link--sublabel"},"Previous"),l.a.createElement("div",{className:"pagination-nav__link--label"},"\xab ",t.previous.title))),l.a.createElement("div",{className:"pagination-nav__item pagination-nav__item--next"},t.next&&l.a.createElement(s.a,{className:"pagination-nav__link",to:t.next.permalink},l.a.createElement("div",{className:"pagination-nav__link--sublabel"},"Next"),l.a.createElement("div",{className:"pagination-nav__link--label"},t.next.title," \xbb"))))};var d=function(e,t,a){const[l,r]=Object(n.useState)(void 0);Object(n.useEffect)(()=>{let n=[],c=[];function i(){const i=function(){let e=0,t=null;for(n=document.getElementsByClassName("anchor");e=0&&r<=a&&(t=l),e+=1}return t}();if(i){let a=0,n=!1;for(c=document.getElementsByClassName(e);a{document.removeEventListener("scroll",i),document.removeEventListener("resize",i)}})},E=a(145),g=a.n(E),v=a(104),u=a.n(v);function p({headings:e}){return d("contents__link","contents__link--active",100),l.a.createElement("div",{className:"col col--3"},l.a.createElement("div",{className:u.a.tableOfContents},l.a.createElement(_,{headings:e})))}function _({headings:e,isChild:t}){return e.length?l.a.createElement("ul",{className:t?"":"contents contents__left-border"},e.map(e=>l.a.createElement("li",{key:e.id},l.a.createElement("a",{href:`#${e.id}`,className:"contents__link",dangerouslySetInnerHTML:{__html:e.value}}),l.a.createElement(_,{isChild:!0,headings:e.children})))):null}t.default=function(e){const{siteConfig:t={}}=Object(i.a)(),{url:a,title:n}=t,{content:s}=e,{metadata:d}=s,{description:E,title:v,permalink:_,editUrl:h,lastUpdatedAt:N,lastUpdatedBy:f,version:b}=d,{frontMatter:{image:k,keywords:w,hide_title:y,hide_table_of_contents:C}}=s,x=v?`${v} | ${n}`:n;let L=a+Object(m.a)(k);return Object(c.a)(k)||(L=k),l.a.createElement(l.a.Fragment,null,l.a.createElement(r.a,null,l.a.createElement("title",null,x),l.a.createElement("meta",{property:"og:title",content:x}),E&&l.a.createElement("meta",{name:"description",content:E}),E&&l.a.createElement("meta",{property:"og:description",content:E}),w&&w.length&&l.a.createElement("meta",{name:"keywords",content:w.join(",")}),k&&l.a.createElement("meta",{property:"og:image",content:L}),k&&l.a.createElement("meta",{property:"twitter:image",content:L}),k&&l.a.createElement("meta",{name:"twitter:image:alt",content:`Image for ${v}`}),_&&l.a.createElement("meta",{property:"og:url",content:a+_})),l.a.createElement("div",{className:"padding-vert--lg"},l.a.createElement("div",{className:"container"},l.a.createElement("div",{className:"row"},l.a.createElement("div",{className:g()("col",u.a.docItemCol)},l.a.createElement("div",{className:u.a.docItemContainer},l.a.createElement("article",null,b&&l.a.createElement("div",null,l.a.createElement("span",{className:"badge badge--secondary"},"Version: ",b)),!y&&l.a.createElement("header",null,l.a.createElement("h1",{className:u.a.docTitle},v)),l.a.createElement("div",{className:"markdown"},l.a.createElement(s,null))),(h||N||f)&&l.a.createElement("div",{className:"margin-vert--xl"},l.a.createElement("div",{className:"row"},l.a.createElement("div",{className:"col"},h&&l.a.createElement("a",{href:h,target:"_blank",rel:"noreferrer noopener"},l.a.createElement("svg",{fill:"currentColor",height:"1.2em",width:"1.2em",preserveAspectRatio:"xMidYMid meet",viewBox:"0 0 40 40",style:{marginRight:"0.3em",verticalAlign:"sub"}},l.a.createElement("g",null,l.a.createElement("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"}))),"Edit this page")),(N||f)&&l.a.createElement("div",{className:"col text--right"},l.a.createElement("em",null,l.a.createElement("small",null,"Last updated"," ",N&&l.a.createElement(l.a.Fragment,null,"on"," ",l.a.createElement("time",{dateTime:new Date(1e3*N).toISOString(),className:u.a.docLastUpdatedAt},new Date(1e3*N).toLocaleDateString()),f&&" "),f&&l.a.createElement(l.a.Fragment,null,"by ",l.a.createElement("strong",null,f)),!1))))),l.a.createElement("div",{className:"margin-vert--lg"},l.a.createElement(o,{metadata:d})))),!C&&s.rightToc&&l.a.createElement(p,{headings:s.rightToc})))))}}}]);
--------------------------------------------------------------------------------
/client/src/reducers/orderReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | ORDER_CREATE_REQUEST,
3 | ORDER_CREATE_SUCCESS,
4 | ORDER_CREATE_FAIL,
5 | ORDER_DETAILS_REQUEST,
6 | ORDER_DETAILS_SUCCESS,
7 | ORDER_DETAILS_FAIL,
8 | ORDER_PAY_REQUEST,
9 | ORDER_PAY_SUCCESS,
10 | ORDER_PAY_FAIL,
11 | ORDER_PAY_RESET,
12 | ORDER_LIST_MY_REQUEST,
13 | ORDER_LIST_MY_SUCCESS,
14 | ORDER_LIST_MY_FAIL,
15 | ORDER_LIST_MY_RESET,
16 | ORDER_LIST_REQUEST,
17 | ORDER_LIST_SUCCESS,
18 | ORDER_LIST_FAIL,
19 | ORDER_DELIVER_REQUEST,
20 | ORDER_DELIVER_SUCCESS,
21 | ORDER_DELIVER_FAIL,
22 | ORDER_DELIVER_RESET,
23 | ORDER_PENDING_REQUEST,
24 | ORDER_PENDING_SUCCESS,
25 | ORDER_PENDING_FAIL,
26 | ORDER_PENDING_RESET,
27 | ORDER_CREATE_RESET,
28 | } from '../constants/orderConstants'
29 |
30 | export const orderCreateReducer = (state = {}, action) => {
31 | switch (action.type) {
32 | case ORDER_CREATE_REQUEST:
33 | return {
34 | loading: true,
35 | }
36 | case ORDER_CREATE_SUCCESS:
37 | return {
38 | loading: false,
39 | success: true,
40 | order: action.payload,
41 | }
42 | case ORDER_CREATE_FAIL:
43 | return {
44 | loading: false,
45 | error: action.payload,
46 | }
47 | case ORDER_CREATE_RESET:
48 | return {}
49 | default:
50 | return state
51 | }
52 | }
53 |
54 | export const orderDetailsReducer = (
55 | state = { loading: true, orderItems: [], shippingAddress: {} },
56 | action
57 | ) => {
58 | switch (action.type) {
59 | case ORDER_DETAILS_REQUEST:
60 | return {
61 | ...state,
62 | loading: true,
63 | }
64 | case ORDER_DETAILS_SUCCESS:
65 | return {
66 | loading: false,
67 | order: action.payload,
68 | }
69 | case ORDER_DETAILS_FAIL:
70 | return {
71 | loading: false,
72 | error: action.payload,
73 | }
74 | default:
75 | return state
76 | }
77 | }
78 |
79 | export const orderPayReducer = (state = {}, action) => {
80 | switch (action.type) {
81 | case ORDER_PAY_REQUEST:
82 | return {
83 | loading: true,
84 | }
85 | case ORDER_PAY_SUCCESS:
86 | return {
87 | loading: false,
88 | success: true,
89 | }
90 | case ORDER_PAY_FAIL:
91 | return {
92 | loading: false,
93 | error: action.payload,
94 | }
95 | case ORDER_PAY_RESET:
96 | return {}
97 | default:
98 | return state
99 | }
100 | }
101 |
102 | export const orderListMyReducer = (state = { orders: [] }, action) => {
103 | switch (action.type) {
104 | case ORDER_LIST_MY_REQUEST:
105 | return {
106 | loading: true,
107 | }
108 | case ORDER_LIST_MY_SUCCESS:
109 | return {
110 | loading: false,
111 | orders: action.payload,
112 | }
113 | case ORDER_LIST_MY_FAIL:
114 | return {
115 | loading: false,
116 | error: action.payload,
117 | }
118 | case ORDER_LIST_MY_RESET:
119 | return {
120 | orders: [],
121 | }
122 | default:
123 | return state
124 | }
125 | }
126 |
127 | export const orderListReducer = (state = { orders: [] }, action) => {
128 | switch (action.type) {
129 | case ORDER_LIST_REQUEST:
130 | return {
131 | loading: true,
132 | }
133 | case ORDER_LIST_SUCCESS:
134 | return {
135 | loading: false,
136 | orders: action.payload,
137 | }
138 | case ORDER_LIST_FAIL:
139 | return {
140 | loading: false,
141 | error: action.payload,
142 | }
143 | default:
144 | return state
145 | }
146 | }
147 |
148 | export const orderDeliverReducer = (state = {}, action) => {
149 | switch (action.type) {
150 | case ORDER_DELIVER_REQUEST:
151 | return {
152 | loading: true,
153 | }
154 | case ORDER_DELIVER_SUCCESS:
155 | return {
156 | loading: false,
157 | success: true,
158 | }
159 | case ORDER_DELIVER_FAIL:
160 | return {
161 | loading: false,
162 | error: action.payload,
163 | }
164 | case ORDER_DELIVER_RESET:
165 | return {}
166 | default:
167 | return state
168 | }
169 | }
170 |
171 | export const orderPendingReducer = (state = {}, action) => {
172 | switch (action.type) {
173 | case ORDER_PENDING_REQUEST:
174 | return {
175 | loading: true,
176 | }
177 | case ORDER_PENDING_SUCCESS:
178 | return {
179 | loading: false,
180 | success: true,
181 | }
182 | case ORDER_PENDING_FAIL:
183 | return {
184 | loading: false,
185 | error: action.payload,
186 | }
187 | case ORDER_PENDING_RESET:
188 | return {}
189 | default:
190 | return state
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/66551e45.d0728d00.js.download:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[23],{123:function(e,t,n){"use strict";n.r(t),n.d(t,"frontMatter",(function(){return o})),n.d(t,"metadata",(function(){return c})),n.d(t,"rightToc",(function(){return l})),n.d(t,"default",(function(){return s}));var r=n(1),a=n(6),i=(n(0),n(143)),o={id:"replace-default-transition",title:"Replace the default transition",sidebar_label:"Replace the default transition"},c={id:"replace-default-transition",title:"Replace the default transition",description:"There are 4 built-in transitions provided.",source:"@site/docs/replace-default-transition.md",permalink:"/react-toastify/replace-default-transition",editUrl:"https://github.com/fkhadra/react-toastify-doc/edit/master/docs/replace-default-transition.md",sidebar_label:"Replace the default transition",sidebar:"someSidebar",previous:{title:"Usage with redux",permalink:"/react-toastify/use-react-toastify-with-redux"},next:{title:"Define a custom enter and exit animation",permalink:"/react-toastify/custom-animation"}},l=[],u={rightToc:l};function s(e){var t=e.components,n=Object(a.a)(e,["components"]);return Object(i.b)("wrapper",Object(r.a)({},u,n,{components:t,mdxType:"MDXLayout"}),Object(i.b)("p",null,"There are 4 built-in transitions provided."),Object(i.b)("ul",null,Object(i.b)("li",{parentName:"ul"},Object(i.b)("p",{parentName:"li"},"Bounce"),Object(i.b)("img",{src:"https://user-images.githubusercontent.com/5574267/38770379-985f49c8-4012-11e8-9db1-5d4d1f26a3d5.gif"})),Object(i.b)("li",{parentName:"ul"},Object(i.b)("p",{parentName:"li"},"Slide"),Object(i.b)("img",{src:"https://user-images.githubusercontent.com/5574267/38770381-98a81d24-4012-11e8-8011-1190f3fb17c3.gif"})),Object(i.b)("li",{parentName:"ul"},Object(i.b)("p",{parentName:"li"},"Zoom"),Object(i.b)("img",{src:"https://user-images.githubusercontent.com/5574267/38770382-98c16342-4012-11e8-9abf-3cf3d3eabd8c.gif"})),Object(i.b)("li",{parentName:"ul"},Object(i.b)("p",{parentName:"li"},"Flip"),Object(i.b)("img",{src:"https://user-images.githubusercontent.com/5574267/38770380-9877dde4-4012-11e8-9485-0dc43346ce30.gif"}))),Object(i.b)("p",null,"Bounce is used by default, but you can replace it with your own transition, or with one from the list above."),Object(i.b)("pre",null,Object(i.b)("code",Object(r.a)({parentName:"pre"},{className:"language-jsx"}),"import { Slide, Zoom, Flip, Bounce } from 'react-toastify';\n\n \n//...\n \n\n")),Object(i.b)("p",null,"You get the idea...This can also be done per toast."),Object(i.b)("pre",null,Object(i.b)("code",Object(r.a)({parentName:"pre"},{className:"language-jsx"}),'toast("hello", {\n transition: Slide\n})\n')))}s.isMDXComponent=!0},143:function(e,t,n){"use strict";n.d(t,"a",(function(){return p})),n.d(t,"b",(function(){return d}));var r=n(0),a=n.n(r);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var u=a.a.createContext({}),s=function(e){var t=a.a.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):c({},t,{},e)),n},p=function(e){var t=s(e.components);return a.a.createElement(u.Provider,{value:t},e.children)},b={inlineCode:"code",wrapper:function(e){var t=e.children;return a.a.createElement(a.a.Fragment,{},t)}},f=Object(r.forwardRef)((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,o=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),p=s(n),f=r,d=p["".concat(o,".").concat(f)]||p[f]||b[f]||i;return n?a.a.createElement(d,c({ref:t},u,{components:n})):a.a.createElement(d,c({ref:t},u))}));function d(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=f;var c={};for(var l in t)hasOwnProperty.call(t,l)&&(c[l]=t[l]);c.originalType=e,c.mdxType="string"==typeof e?e:r,o[1]=c;for(var u=2;u {
9 | const { email, password } = req.body
10 |
11 | const user = await User.findOne({ email })
12 |
13 | if (user && (await user.matchPassword(password))) {
14 | res.json({
15 | _id: user._id,
16 | name: user.name,
17 | email: user.email,
18 | isAdmin: user.isAdmin,
19 | token: generateToken(user._id),
20 | })
21 | } else {
22 | res.status(401)
23 | throw new Error('Invalid Username or Password')
24 | }
25 | })
26 |
27 | //@desc Register User
28 | //@route POST /api/users
29 | //@access Public
30 | const registerUser = asyncHandler(async (req, res) => {
31 | const { name, email, password } = req.body
32 |
33 | const userExists = await User.findOne({ email })
34 |
35 | if (userExists) {
36 | res.status(400)
37 | throw new Error('User already exists!')
38 | }
39 |
40 | const user = await User.create({
41 | name,
42 | email,
43 | password,
44 | })
45 |
46 | if (user) {
47 | res.status(201).json({
48 | _id: user._id,
49 | name: user.name,
50 | email: user.email,
51 | isAdmin: user.isAdmin,
52 | token: generateToken(user._id),
53 | })
54 | } else {
55 | res.status(400)
56 | throw new Error('Invaid User Data')
57 | }
58 | })
59 |
60 | //@desc Get User Profile
61 | //@route GET /api/users/profile
62 | //@access Private
63 | const getUserProfile = asyncHandler(async (req, res) => {
64 | const user = await User.findById(req.user._id)
65 |
66 | if (user) {
67 | res.json({
68 | _id: user._id,
69 | name: user.name,
70 | email: user.email,
71 | isAdmin: user.isAdmin,
72 | })
73 | } else {
74 | res.status(404)
75 | throw new Error('User not found')
76 | }
77 | })
78 |
79 | //@desc Update User Profile
80 | //@route PUT /api/users/profile
81 | //@access Private
82 | const updateUserProfile = asyncHandler(async (req, res) => {
83 | const user = await User.findById(req.user._id)
84 |
85 | if (user) {
86 | user.name = req.body.name || user.name
87 | user.email = req.body.email || user.email
88 | if (req.body.password) {
89 | user.password = req.body.password
90 | }
91 |
92 | const updatedUser = await user.save()
93 | res.json({
94 | _id: updatedUser._id,
95 | name: updatedUser.name,
96 | email: updatedUser.email,
97 | isAdmin: updatedUser.isAdmin,
98 | token: generateToken(updatedUser._id),
99 | })
100 | } else {
101 | res.status(404)
102 | throw new Error('User not found')
103 | }
104 | })
105 |
106 | //@desc Get All users
107 | //@route GET /api/users
108 | //@access Private/Admin
109 | const getUsers = asyncHandler(async (req, res) => {
110 | const users = await User.find({})
111 | res.json(users)
112 | })
113 |
114 | //@desc Delete User
115 | //@route GET /api/users/:id
116 | //@access Private/Admin
117 | const deleteUser = asyncHandler(async (req, res) => {
118 | const user = await User.findById(req.params.id)
119 | if (user) {
120 | await user.remove()
121 | res.json({ message: 'User deleted successfully!' })
122 | } else {
123 | res.status(404)
124 | throw new Error('User not found')
125 | }
126 | })
127 |
128 | //@desc Get user by id
129 | //@route GET /api/users/:id
130 | //@access Private/Admin
131 | const getUserById = asyncHandler(async (req, res) => {
132 | const user = await User.findById(req.params.id).select('-password')
133 | if (user) {
134 | res.json(user)
135 | } else {
136 | res.json(404)
137 | throw new Error('User not found')
138 | }
139 | })
140 |
141 | //@desc Update User
142 | //@route PUT /api/users/:id
143 | //@access Private/Admin
144 | const updateUser = asyncHandler(async (req, res) => {
145 | const user = await User.findById(req.params.id)
146 |
147 | if (user) {
148 | user.name = req.body.name || user.name
149 | user.email = req.body.email || user.email
150 | user.isAdmin = req.body.isAdmin || user.isAdmin
151 |
152 | const updatedUser = await user.save()
153 | res.json({
154 | _id: updatedUser._id,
155 | name: updatedUser.name,
156 | email: updatedUser.email,
157 | isAdmin: updatedUser.isAdmin,
158 | })
159 | } else {
160 | res.status(404)
161 | throw new Error('User not found')
162 | }
163 | })
164 |
165 | export {
166 | authUser,
167 | registerUser,
168 | getUserProfile,
169 | updateUserProfile,
170 | getUsers,
171 | deleteUser,
172 | getUserById,
173 | updateUser,
174 | }
175 |
--------------------------------------------------------------------------------
/client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/c4a783b7.42f0f495.js.download:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[34],{132:function(t){t.exports=JSON.parse('{"docsSidebars":{"someSidebar":[{"type":"category","label":"Getting Started","items":[{"type":"link","label":"Introduction","href":"/react-toastify/introduction"},{"type":"link","label":"Installation","href":"/react-toastify/installation"},{"type":"link","label":"Release notes","href":"/react-toastify/release-notes"}]},{"type":"category","label":"Usage","items":[{"type":"link","label":"Positioning toast","href":"/react-toastify/positioning-toast"},{"type":"link","label":"Handling autoClose","href":"/react-toastify/autoClose"},{"type":"link","label":"Render more than string","href":"/react-toastify/render-what-you-want"},{"type":"link","label":"Remove toast programmatically","href":"/react-toastify/remove-toast"},{"type":"link","label":"Pause toast timer when the window loses focus","href":"/react-toastify/pause-on-focus-loss"},{"type":"link","label":"Use a custom id","href":"/react-toastify/use-a-custom-id"},{"type":"link","label":"Prevent duplicate","href":"/react-toastify/prevent-duplicate"},{"type":"link","label":"Delay notification appearance","href":"/react-toastify/delay-toast-appearance"},{"type":"link","label":"Limit the number of toast displayed","href":"/react-toastify/limit-the-number-of-toast-displayed"},{"type":"link","label":"Use a controlled progress bar","href":"/react-toastify/use-a-controlled-progress-bar"},{"type":"link","label":"Update a toast","href":"/react-toastify/update-toast"},{"type":"link","label":"Define callback","href":"/react-toastify/define-callback"},{"type":"link","label":"Listen for changes","href":"/react-toastify/listen-for-changes"},{"type":"link","label":"Use a custom close button or remove it","href":"/react-toastify/use-a-custom-close-button-or-remove-it"},{"type":"link","label":"Add an undo action to a toast (like Google Drive)","href":"/react-toastify/add-an-undo-action-to-a-toast"},{"type":"link","label":"Usage with redux","href":"/react-toastify/use-react-toastify-with-redux"},{"type":"link","label":"Replace the default transition","href":"/react-toastify/replace-default-transition"},{"type":"link","label":"Define a custom enter and exit animation","href":"/react-toastify/custom-animation"},{"type":"link","label":"Drag to remove","href":"/react-toastify/drag-to-remove"},{"type":"link","label":"Enable right to left support","href":"/react-toastify/enable-right-to-left-support"},{"type":"link","label":"Accessibility","href":"/react-toastify/accessibility"},{"type":"link","label":"Lazy container and multi-container","href":"/react-toastify/lazy-container-and-multi-container"},{"type":"link","label":"How to style","href":"/react-toastify/how-to-style"},{"type":"link","label":"Dispatch toast outside of react component","href":"/react-toastify/dispatch-toast-outside-of-react-component"}]},{"type":"category","label":"API Reference","items":[{"type":"link","label":"ToastContainer","href":"/react-toastify/api/toast-container"},{"type":"link","label":"toast","href":"/react-toastify/api/toast"},{"type":"link","label":"cssTransition","href":"/react-toastify/api/css-transition"},{"type":"link","label":"collapseToast","href":"/react-toastify/api/collapse-toast"},{"type":"link","label":"useToastContainer","href":"/react-toastify/api/use-toast-container"},{"type":"link","label":"useToast","href":"/react-toastify/api/use-toast"}]}]},"permalinkToSidebar":{"/react-toastify/accessibility":"someSidebar","/react-toastify/add-an-undo-action-to-a-toast":"someSidebar","/react-toastify/api/collapse-toast":"someSidebar","/react-toastify/api/css-transition":"someSidebar","/react-toastify/api/toast":"someSidebar","/react-toastify/api/toast-container":"someSidebar","/react-toastify/api/use-toast":"someSidebar","/react-toastify/api/use-toast-container":"someSidebar","/react-toastify/autoClose":"someSidebar","/react-toastify/custom-animation":"someSidebar","/react-toastify/define-callback":"someSidebar","/react-toastify/delay-toast-appearance":"someSidebar","/react-toastify/dispatch-toast-outside-of-react-component":"someSidebar","/react-toastify/drag-to-remove":"someSidebar","/react-toastify/enable-right-to-left-support":"someSidebar","/react-toastify/how-to-style":"someSidebar","/react-toastify/installation":"someSidebar","/react-toastify/introduction":"someSidebar","/react-toastify/lazy-container-and-multi-container":"someSidebar","/react-toastify/limit-the-number-of-toast-displayed":"someSidebar","/react-toastify/listen-for-changes":"someSidebar","/react-toastify/pause-on-focus-loss":"someSidebar","/react-toastify/positioning-toast":"someSidebar","/react-toastify/prevent-duplicate":"someSidebar","/react-toastify/release-notes":"someSidebar","/react-toastify/remove-toast":"someSidebar","/react-toastify/render-what-you-want":"someSidebar","/react-toastify/replace-default-transition":"someSidebar","/react-toastify/update-toast":"someSidebar","/react-toastify/use-a-controlled-progress-bar":"someSidebar","/react-toastify/use-a-custom-close-button-or-remove-it":"someSidebar","/react-toastify/use-a-custom-id":"someSidebar","/react-toastify/use-react-toastify-with-redux":"someSidebar"}}')}}]);
--------------------------------------------------------------------------------
/client/src/screens/RegisterScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { Form, Button, Row, Col, Spinner } from 'react-bootstrap'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import Message from '../components/Message'
6 | import { register } from '../actions/userActions'
7 | import FormContainer from '../components/FormContainer'
8 | import { ToastContainer, Slide } from 'react-toastify'
9 | import 'react-toastify/dist/ReactToastify.css'
10 | import { motion } from 'framer-motion'
11 |
12 | const RegisterScreen = ({ location, history }) => {
13 | const [name, setName] = useState('')
14 | const [email, setEmail] = useState('')
15 | const [password, setPassword] = useState('')
16 | const [confirmPassword, setConfirmPassword] = useState('')
17 | const [message, setMessage] = useState(null)
18 |
19 | const dispatch = useDispatch()
20 |
21 | const userRegister = useSelector((state) => state.userRegister)
22 | const { loading, error, userInfo } = userRegister
23 |
24 | const redirect = location.search ? location.search.split('=')[1] : '/'
25 |
26 | useEffect(() => {
27 | window.scrollTo(0, 0)
28 | if (userInfo) {
29 | history.push(redirect)
30 | }
31 | }, [history, userInfo, redirect])
32 |
33 | const submitHandler = (e) => {
34 | e.preventDefault()
35 |
36 | //check password equality
37 | if (password !== confirmPassword) {
38 | setMessage('Passwords do not match!')
39 | } else {
40 | //dispatch register
41 | dispatch(register(name, email, password))
42 | }
43 | }
44 |
45 | return (
46 |
52 |
53 | Sign Up
54 | {message && {message} }
55 | {error && (
56 |
67 | )}
68 | {/*loading && */}
69 |
71 | Full Name
72 | setName(e.target.value)}
77 | className='input'
78 | >
79 |
80 |
81 |
82 | Email Address
83 | setEmail(e.target.value)}
88 | className='input'
89 | >
90 |
91 |
92 |
93 | Password
94 | setPassword(e.target.value)}
99 | className='input'
100 | >
101 |
102 |
103 |
104 | Confirm Password
105 | setConfirmPassword(e.target.value)}
110 | className='input'
111 | >
112 |
113 |
114 | {loading ? (
115 |
116 |
123 |
124 |
125 |
126 | ) : (
127 |
133 | Sign Up
134 |
135 | )}
136 |
137 |
138 |
139 |
140 | Already Have an Account?{' '}
141 |
142 |
143 | Login
144 |
145 |
146 |
147 |
148 |
149 |
150 | )
151 | }
152 |
153 | export default RegisterScreen
154 |
--------------------------------------------------------------------------------
/client/src/components/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Loader = () => {
4 | return (
5 |
40 | )
41 | }
42 |
43 | export default Loader
44 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;900&family=Nunito:wght@400;700;900&display=swap');
2 | .navbar-dark .navbar-nav .nav-link {
3 | color: #252c2e;
4 | font-family: 'Nunito', sans-serif;
5 | font-size: 16px;
6 | }
7 |
8 | .navbar-dark .navbar-nav .nav-link:hover,
9 | .navbar-dark .navbar-nav .nav-link:focus,
10 | .navbar-dark .navbar-nav .nav-link:active,
11 | .navbar-dark .navbar-nav .active > .nav-link,
12 | .navbar-dark .navbar-nav .nav-link.active,
13 | .navbar-dark .navbar-nav .nav-link.show,
14 | .navbar-dark .navbar-nav .show > .nav-link {
15 | color: #252c2e;
16 | }
17 | body {
18 | /* font-family: 'Lato', sans-serif; */
19 | font-family: 'Nunito', sans-serif;
20 | letter-spacing: 0.2px;
21 | overflow-x: hidden;
22 | color: #252c2e;
23 | font-size: 16px;
24 | }
25 | main {
26 | min-height: 80vh;
27 | }
28 |
29 | a {
30 | text-decoration: none !important;
31 | }
32 |
33 | h1,
34 | h2,
35 | h3,
36 | h4,
37 | h5,
38 | h6 {
39 | text-transform: capitalize;
40 | letter-spacing: 0.2px;
41 | color: #252c2e;
42 | }
43 |
44 | h1 {
45 | font-size: 2.5rem;
46 | padding: 1rem 0;
47 | }
48 |
49 | h2 {
50 | font-size: 1.4rem;
51 | padding: 0.5rem 0;
52 | }
53 |
54 | h3 {
55 | padding: 1rem 0;
56 | }
57 |
58 | .custom-btn {
59 | padding: 15px 40px;
60 | background-color: #ff4555;
61 | color: #fff;
62 | font-size: 16px;
63 | border-radius: 30px;
64 | box-shadow: 0 3.4px 14.8px rgba(0, 0, 0, 0.196),
65 | 0 11.4px 49.8px rgba(0, 0, 0, 0.289), 0 51px 223px rgba(0, 0, 0, 0.36);
66 | transition: 0.3s all ease-in-out;
67 | }
68 |
69 | .custom-btn:hover {
70 | background-color: #ff5765;
71 | }
72 |
73 | p {
74 | font-size: 17px;
75 | line-height: 25px;
76 | color: #626566;
77 | font-family: 'Lato', sans-serif;
78 | font-weight: normal;
79 | }
80 |
81 | .input {
82 | border: 2px solid #252c2e;
83 | border-radius: 5px;
84 | background-color: transparent;
85 | box-shadow: none;
86 | }
87 |
88 | .input:focus {
89 | color: #252c2e;
90 | background-color: transparent;
91 | border-color: #252c2e;
92 | outline: 0;
93 | box-shadow: none;
94 | }
95 |
96 | .login_btn {
97 | padding: 12px 24px;
98 | display: block;
99 | }
100 |
101 | .btn-dark {
102 | background-color: #252c2e;
103 | }
104 |
105 | .btn-light:hover {
106 | outline: none;
107 | background-color: transparent;
108 | }
109 | .btn-light.focus,
110 | .btn-light:focus,
111 | .btn-light:not(:disabled):not(.disabled).active,
112 | .btn-light:not(:disabled):not(.disabled):active,
113 | .show > .btn-light.dropdown-toggle {
114 | color: #1a1a1a;
115 | background-color: transparent;
116 | border-color: transparent;
117 | box-shadow: none;
118 | }
119 | .btn.focus,
120 | .btn:focus {
121 | box-shadow: none;
122 | }
123 |
124 | /* loader */
125 | @keyframes rotate {
126 | to {
127 | transform: rotate(360deg);
128 | }
129 | }
130 | /* Variables */
131 | /* Loading Icon */
132 | .loading {
133 | width: 100px;
134 | height: 100px;
135 | }
136 | .loading__ring {
137 | position: absolute;
138 | width: 100px;
139 | height: 100px;
140 | }
141 | .loading__ring:first-child {
142 | transform: skew(30deg, 20deg);
143 | }
144 | .loading__ring:last-child {
145 | transform: skew(-30deg, -20deg) scale(-1, 1);
146 | }
147 | .loading__ring:last-child svg {
148 | animation-delay: -0.5s;
149 | }
150 | .loading__ring svg {
151 | animation: rotate 1s linear infinite;
152 | fill: #ff4555;
153 | }
154 |
155 | input:-webkit-autofill,
156 | input:-webkit-autofill:hover,
157 | input:-webkit-autofill:focus,
158 | input:-webkit-autofill:active {
159 | -webkit-box-shadow: 0 0 0 30px white inset !important;
160 | }
161 |
162 | [type='radio']:checked,
163 | [type='radio']:not(:checked) {
164 | position: absolute;
165 | left: -9999px;
166 | }
167 | [type='radio']:checked + label,
168 | [type='radio']:not(:checked) + label {
169 | position: relative;
170 | padding-left: 28px;
171 | cursor: pointer;
172 | line-height: 20px;
173 | display: inline-block;
174 | color: #666;
175 | }
176 | [type='radio']:checked + label:before,
177 | [type='radio']:not(:checked) + label:before {
178 | content: '';
179 | position: absolute;
180 | left: 0;
181 | top: 0;
182 | width: 18px;
183 | height: 18px;
184 | border: 1px solid #ddd;
185 | border-radius: 100%;
186 | background: #fff;
187 | }
188 | [type='radio']:checked + label:after,
189 | [type='radio']:not(:checked) + label:after {
190 | content: '';
191 | width: 12px;
192 | height: 12px;
193 | background: #ff5765;
194 | position: absolute;
195 | top: 3px;
196 | left: 3px;
197 | border-radius: 100%;
198 | -webkit-transition: all 0.2s ease;
199 | transition: all 0.2s ease;
200 | }
201 | [type='radio']:not(:checked) + label:after {
202 | opacity: 0;
203 | -webkit-transform: scale(0);
204 | transform: scale(0);
205 | }
206 | [type='radio']:checked + label:after {
207 | opacity: 1;
208 | -webkit-transform: scale(1);
209 | transform: scale(1);
210 | }
211 |
212 | .pending,
213 | .delivered {
214 | padding: 8px 16px;
215 | background-color: #ffebed;
216 | color: #ff4a59;
217 | border-radius: 30px;
218 | font-weight: bold;
219 | display: inline-block;
220 | font-size: 16px;
221 | }
222 |
223 | .delivered {
224 | background-color: #ebf9f4;
225 | color: #42aa85;
226 | }
227 |
228 | select {
229 | padding: 1em 1.5em;
230 | background: #4d5061 !important;
231 | color: #fff !important;
232 | border: 0;
233 |
234 | }
235 |
236 | select:focus {
237 | outline: none !important;
238 | border-radius: none !important;
239 | box-shadow: none!important;
240 | border-color: none !important;
241 | }
242 |
--------------------------------------------------------------------------------
/client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/runtime_main.44451698.js.download:
--------------------------------------------------------------------------------
1 | !function(e){function a(a){for(var c,t,n=a[0],b=a[1],o=a[2],u=0,i=[];u {
22 | const productId = match.params.id
23 | const qty = location.search ? Number(location.search.split('=')[1]) : 1
24 |
25 | const dispatch = useDispatch()
26 |
27 | const cart = useSelector((state) => state.cart)
28 | const { cartItems } = cart
29 | console.log(cartItems)
30 | useEffect(() => {
31 | window.scrollTo(0, 0)
32 | if (productId) {
33 | dispatch(addToCart(productId, qty))
34 | }
35 | }, [dispatch, productId, qty])
36 |
37 | const removeFromCartHandler = (id) => {
38 | dispatch(removeFromCart(id))
39 | }
40 |
41 | const checkoutHandler = () => {
42 | history.push('/login?redirect=shipping')
43 | }
44 |
45 | return (
46 |
52 |
53 |
54 | {cartItems.length === 0 ? (
55 |
56 |
57 |
58 | ) : (
59 |
60 | Shopping Cart
61 |
62 | {cartItems.map((item) => (
63 |
64 |
65 |
66 |
76 |
77 |
78 |
79 | {item.name}
80 |
81 |
82 |
83 | ${item.price}
84 |
85 |
86 |
90 | dispatch(
91 | addToCart(item.product, Number(e.target.value))
92 | )
93 | }
94 | >
95 | {[...Array(item.countInStock).keys()].map((x) => (
96 |
97 | {x + 1}
98 |
99 | ))}
100 |
101 |
102 |
103 | removeFromCartHandler(item.product)}
107 | >
108 |
113 |
114 |
115 |
116 |
117 | ))}
118 |
119 |
120 | )}
121 |
122 | {cartItems.length !== 0 && (
123 |
124 |
125 |
126 |
127 |
128 | Subtotal (
129 | {cartItems.reduce((acc, item) => acc + item.qty, 0)})
130 | items
131 | {' '}
132 |
133 | ${' '}
134 | {cartItems
135 | .reduce((acc, item) => acc + item.qty * item.price, 0)
136 | .toFixed(2)}
137 |
138 |
139 |
140 |
146 | Proceed to checkout
147 |
148 |
149 |
150 |
151 |
152 | )}
153 |
154 |
155 |
156 | )
157 | }
158 |
159 | export default CartScreen
160 |
--------------------------------------------------------------------------------
/client/src/actions/productActions.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { ORDER_LIST_MY_FAIL } from '../constants/orderConstants'
3 | import {
4 | PRODUCT_LIST_REQUEST,
5 | PRODUCT_LIST_SUCCESS,
6 | PRODUCT_LIST_FAIL,
7 | PRODUCT_DETAILS_REQUEST,
8 | PRODUCT_DETAILS_SUCCESS,
9 | PRODUCT_DETAILS_FAIL,
10 | PRODUCT_DELETE_REQUEST,
11 | PRODUCT_DELETE_SUCCESS,
12 | PRODUCT_CREATE_REQUEST,
13 | PRODUCT_CREATE_SUCCESS,
14 | PRODUCT_CREATE_FAIL,
15 | PRODUCT_UPDATE_SUCCESS,
16 | PRODUCT_UPDATE_FAIL,
17 | PRODUCT_UPDATE_REQUEST,
18 | PRODUCT_CREATE_REVIEW_REQUEST,
19 | PRODUCT_CREATE_REVIEW_SUCCESS,
20 | PRODUCT_CREATE_REVIEW_FAIL,
21 | PRODUCT_TOP_REQUEST,
22 | PRODUCT_TOP_SUCCESS,
23 | PRODUCT_TOP_FAIL,
24 | PRODUCT_DELETE_FAIL,
25 | } from '../constants/productConstants'
26 | import { logout } from './userActions'
27 |
28 | export const listProducts =
29 | (keyword = '', pageNumber = '') =>
30 | async (dispatch) => {
31 | try {
32 | dispatch({ type: PRODUCT_LIST_REQUEST })
33 |
34 | const { data } = await axios.get(
35 | `/api/products?keyword=${keyword}&pageNumber=${pageNumber}`
36 | )
37 |
38 | dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data })
39 | } catch (error) {
40 | dispatch({
41 | type: PRODUCT_LIST_FAIL,
42 | payload:
43 | error.response && error.response.data.message
44 | ? error.response.data.message
45 | : error.message,
46 | })
47 | }
48 | }
49 |
50 | export const listProductDetail = (id) => async (dispatch) => {
51 | try {
52 | dispatch({ type: PRODUCT_DETAILS_REQUEST })
53 |
54 | const { data } = await axios.get(`/api/products/${id}`)
55 |
56 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data })
57 | } catch (error) {
58 | dispatch({
59 | type: PRODUCT_DETAILS_FAIL,
60 | payload:
61 | error.response && error.response.data.message
62 | ? error.response.data.message
63 | : error.message,
64 | })
65 | }
66 | }
67 |
68 | export const deleteProduct = (id) => async (dispatch, getState) => {
69 | try {
70 | dispatch({ type: PRODUCT_DELETE_REQUEST })
71 |
72 | const {
73 | userLogin: { userInfo },
74 | } = getState()
75 |
76 | const config = {
77 | headers: {
78 | Authorization: `Bearer ${userInfo.token}`,
79 | },
80 | }
81 | await axios.delete(`/api/products/${id}`, config)
82 |
83 | dispatch({ type: PRODUCT_DELETE_SUCCESS })
84 | } catch (error) {
85 | const message =
86 | error.response && error.response.data.message
87 | ? error.response.data.message
88 | : error.message
89 | if (message === 'Not authorized, token failed') {
90 | dispatch(logout())
91 | }
92 | dispatch({
93 | type: PRODUCT_DELETE_FAIL,
94 | payload: message,
95 | })
96 | }
97 | }
98 |
99 | export const createProduct = () => async (dispatch, getState) => {
100 | try {
101 | dispatch({ type: PRODUCT_CREATE_REQUEST })
102 |
103 | const {
104 | userLogin: { userInfo },
105 | } = getState()
106 |
107 | const config = {
108 | headers: {
109 | Authorization: `Bearer ${userInfo.token}`,
110 | },
111 | }
112 | const { data } = await axios.post('/api/products', {}, config)
113 |
114 | dispatch({ type: PRODUCT_CREATE_SUCCESS, payload: data })
115 | } catch (error) {
116 | const message =
117 | error.response && error.response.data.message
118 | ? error.response.data.message
119 | : error.message
120 | if (message === 'Not authorized, token failed') {
121 | dispatch(logout())
122 | }
123 | dispatch({
124 | type: PRODUCT_CREATE_FAIL,
125 | payload: message,
126 | })
127 | }
128 | }
129 |
130 | export const updateProduct = (product) => async (dispatch, getState) => {
131 | try {
132 | dispatch({ type: PRODUCT_UPDATE_REQUEST })
133 |
134 | const {
135 | userLogin: { userInfo },
136 | } = getState()
137 |
138 | const config = {
139 | headers: {
140 | 'Content-Type': 'application/json',
141 | Authorization: `Bearer ${userInfo.token}`,
142 | },
143 | }
144 | const { data } = await axios.put(
145 | `/api/products/${product._id}`,
146 | product,
147 | config
148 | )
149 |
150 | dispatch({ type: PRODUCT_UPDATE_SUCCESS, payload: data })
151 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data })
152 | } catch (error) {
153 | const message =
154 | error.response && error.response.data.message
155 | ? error.response.data.message
156 | : error.message
157 | if (message === 'Not authorized, token failed') {
158 | dispatch(logout())
159 | }
160 | dispatch({
161 | type: PRODUCT_UPDATE_FAIL,
162 | payload: message,
163 | })
164 | }
165 | }
166 |
167 | export const createProductReview =
168 | (productId, review) => async (dispatch, getState) => {
169 | try {
170 | dispatch({ type: PRODUCT_CREATE_REVIEW_REQUEST })
171 |
172 | const {
173 | userLogin: { userInfo },
174 | } = getState()
175 |
176 | const config = {
177 | headers: {
178 | 'Content-Type': 'application/json',
179 | Authorization: `Bearer ${userInfo.token}`,
180 | },
181 | }
182 | await axios.post(`/api/products/${productId}/reviews`, review, config)
183 |
184 | dispatch({ type: PRODUCT_CREATE_REVIEW_SUCCESS })
185 | } catch (error) {
186 | const message =
187 | error.response && error.response.data.message
188 | ? error.response.data.message
189 | : error.message
190 | if (message === 'Not authorized, token failed') {
191 | dispatch(logout())
192 | }
193 | dispatch({
194 | type: PRODUCT_CREATE_REVIEW_FAIL,
195 | payload: message,
196 | })
197 | }
198 | }
199 |
200 | export const listTopProducts = () => async (dispatch) => {
201 | try {
202 | dispatch({ type: PRODUCT_TOP_REQUEST })
203 |
204 | const { data } = await axios.get(`/api/products/top`)
205 |
206 | dispatch({ type: PRODUCT_TOP_SUCCESS, payload: data })
207 | } catch (error) {
208 | dispatch({
209 | type: PRODUCT_TOP_FAIL,
210 | payload:
211 | error.response && error.response.data.message
212 | ? error.response.data.message
213 | : error.message,
214 | })
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/client/src/screens/PlaceOrderScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Button, Row, Col, ListGroup, Image, Card } from 'react-bootstrap'
3 | import { Link } from 'react-router-dom'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import Message from '../components/Message'
6 | import CheckoutSteps from '../components/CheckoutSteps'
7 | import { createOrder } from '../actions/orderActions'
8 | import { ORDER_CREATE_RESET } from '../constants/orderConstants'
9 | import { USER_DETAILS_RESET } from '../constants/userConstants'
10 | import { motion } from 'framer-motion'
11 |
12 | const PlaceOrderScreen = ({ history }) => {
13 | const dispatch = useDispatch()
14 | const cart = useSelector((state) => state.cart)
15 |
16 | if (!cart.shippingAddress.address) {
17 | history.push('/shipping')
18 | } else if (!cart.paymentMethod) {
19 | history.push('/payment')
20 | }
21 |
22 | const addDecimals = (num) => {
23 | return (Math.round(num * 100) / 100).toFixed(2)
24 | }
25 |
26 | //calculate price
27 |
28 | cart.itemsPrice = addDecimals(
29 | cart.cartItems.reduce((acc, item) => acc + item.price * item.qty, 0)
30 | )
31 |
32 | cart.shippingPrice = addDecimals(cart.itemsPrice > 100 ? 0 : 100)
33 |
34 | cart.taxPrice = addDecimals(Number((0.15 * cart.itemsPrice).toFixed(2)))
35 |
36 | cart.totalPrice = (
37 | Number(cart.itemsPrice) +
38 | Number(cart.shippingPrice) +
39 | Number(cart.taxPrice)
40 | ).toFixed(2)
41 |
42 | const orderCreate = useSelector((state) => state.orderCreate)
43 | const { order, success, error } = orderCreate
44 |
45 | useEffect(() => {
46 | window.scrollTo(0, 0)
47 | if (success) {
48 | history.push(`/order/${order._id}`)
49 | dispatch({ type: USER_DETAILS_RESET })
50 | dispatch({ type: ORDER_CREATE_RESET })
51 | }
52 |
53 | // eslint-disable-next-line
54 | }, [history, success])
55 |
56 | const placrOrderHandler = () => {
57 | dispatch(
58 | createOrder({
59 | orderItems: cart.cartItems,
60 | shippingAddress: cart.shippingAddress,
61 | paymentMethod: cart.paymentMethod,
62 | itemsPrice: cart.itemsPrice,
63 | shippingPrice: cart.shippingPrice,
64 | taxPrice: cart.taxPrice,
65 | totalPrice: cart.totalPrice,
66 | })
67 | )
68 | }
69 |
70 | return (
71 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | Shipping To
84 |
85 | Address:
86 | {cart.shippingAddress.address}, {cart.shippingAddress.city},{' '}
87 | {cart.shippingAddress.postalCode},{' '}
88 | {cart.shippingAddress.country}
89 |
90 |
91 |
92 |
93 | Payment Method
94 | Method:
95 | {cart.paymentMethod}
96 |
97 |
98 |
99 | Order Items
100 | {cart.cartItems.length === 0 ? (
101 | Your cart is empty
102 | ) : (
103 |
104 | {cart.cartItems.map((item, idx) => (
105 |
106 |
107 |
108 |
117 |
118 |
119 |
123 | {item.name}
124 |
125 |
126 |
127 |
128 | {item.qty} x ${item.price} = $
129 | {item.qty * item.price}
130 |
131 |
132 |
133 | ))}
134 |
135 | )}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | Order Summary
145 |
146 |
147 |
148 | Items
149 | ${cart.itemsPrice}
150 |
151 |
152 |
153 |
154 |
155 | Shipping
156 | ${cart.shippingPrice}
157 |
158 |
159 |
160 |
161 |
162 | Tax
163 | ${cart.taxPrice}
164 |
165 |
166 |
167 |
168 |
169 | Total
170 | ${cart.totalPrice}
171 |
172 |
173 |
174 |
175 | {error && {error} }
176 |
177 |
178 |
179 |
185 | Place Order
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | )
195 | }
196 |
197 | export default PlaceOrderScreen
198 |
--------------------------------------------------------------------------------
/client/src/assets/icons/Lazy container and multi-container _ React-Toastify_files/4ae66d7c.fed4a6df.js.download:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[18],{118:function(t,e,n){"use strict";n.r(e),n.d(e,"frontMatter",(function(){return i})),n.d(e,"metadata",(function(){return c})),n.d(e,"rightToc",(function(){return l})),n.d(e,"default",(function(){return p}));var a=n(1),o=n(6),r=(n(0),n(143)),i={id:"lazy-container-and-multi-container",title:"Lazy container and multi-container",sidebar_label:"Lazy container and multi-container"},c={id:"lazy-container-and-multi-container",title:"Lazy container and multi-container",description:"## Lazy ToastContainer",source:"@site/docs/lazy-container-and-multi-container.md",permalink:"/react-toastify/lazy-container-and-multi-container",editUrl:"https://github.com/fkhadra/react-toastify-doc/edit/master/docs/lazy-container-and-multi-container.md",sidebar_label:"Lazy container and multi-container",sidebar:"someSidebar",previous:{title:"Accessibility",permalink:"/react-toastify/accessibility"},next:{title:"How to style",permalink:"/react-toastify/how-to-style"}},l=[{value:"Lazy ToastContainer",id:"lazy-toastcontainer",children:[{value:"Configure the ToastContainer when it is mounted on demand",id:"configure-the-toastcontainer-when-it-is-mounted-on-demand",children:[]}]},{value:"Multi container support",id:"multi-container-support",children:[]}],s={rightToc:l};function p(t){var e=t.components,n=Object(o.a)(t,["components"]);return Object(r.b)("wrapper",Object(a.a)({},s,n,{components:e,mdxType:"MDXLayout"}),Object(r.b)("h2",{id:"lazy-toastcontainer"},"Lazy ToastContainer"),Object(r.b)("pre",null,Object(r.b)("code",Object(a.a)({parentName:"pre"},{className:"language-js"})," import React, { Component } from 'react';\n import { toast } from 'react-toastify';\n import 'react-toastify/dist/ReactToastify.css';\n\n // Call it once in your app. At the root of your app is the best place\n toast.configure()\n\n const App = () => {\n const notify = () => toast(\"Wow so easy !\");\n \n return Notify ! ;\n }\n")),Object(r.b)("p",null,"The library will mount a ",Object(r.b)("inlineCode",{parentName:"p"},"ToastContainer")," for you if none is mounted. "),Object(r.b)("h3",{id:"configure-the-toastcontainer-when-it-is-mounted-on-demand"},"Configure the ToastContainer when it is mounted on demand"),Object(r.b)("p",null,"The configure function accepts the same props as the ToastContainer. As soon as the container is\nrendered, the call to configure will have no effect."),Object(r.b)("pre",null,Object(r.b)("code",Object(a.a)({parentName:"pre"},{className:"language-js"}),"toast.configure({\n autoClose: 8000,\n draggable: false,\n //etc you get the idea\n});\n")),Object(r.b)("h2",{id:"multi-container-support"},"Multi container support"),Object(r.b)("p",null,"To enable multiple container support, you have to pass ",Object(r.b)("inlineCode",{parentName:"p"},"enableMultiContainer")," and specify a ",Object(r.b)("inlineCode",{parentName:"p"},"containerId")," and use it in\neach toast, to do so add ",Object(r.b)("inlineCode",{parentName:"p"},"containerId")," to the toast's options object."),Object(r.b)("p",null,"Note: adding ",Object(r.b)("inlineCode",{parentName:"p"},"enableMultiContainer")," prop to the ",Object(r.b)("inlineCode",{parentName:"p"},"")," will:"),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},"Check each toast to verify if its ",Object(r.b)("inlineCode",{parentName:"li"},"containerId")," match the container ",Object(r.b)("inlineCode",{parentName:"li"},"containerId")," so it can be rendered."),Object(r.b)("li",{parentName:"ul"},"Ensure not to render any ",Object(r.b)("inlineCode",{parentName:"li"},"toast")," that has ",Object(r.b)("inlineCode",{parentName:"li"},"containerId"),"."),Object(r.b)("li",{parentName:"ul"},"Render any toast if both the ",Object(r.b)("inlineCode",{parentName:"li"},"toast")," and ",Object(r.b)("inlineCode",{parentName:"li"},"")," does not include ",Object(r.b)("inlineCode",{parentName:"li"},"containerId")," and ",Object(r.b)("inlineCode",{parentName:"li"},"containerId")," respectively.")),Object(r.b)("p",null,"A simple example to demonstrate multi toast container capability."),Object(r.b)("ul",null,Object(r.b)("li",{parentName:"ul"},"Notify A button will show a toast on the bottom left."),Object(r.b)("li",{parentName:"ul"},"Notify B button will show a toast on the top right.\n")),Object(r.b)("pre",null,Object(r.b)("code",Object(a.a)({parentName:"pre"},{className:"language-js"})," import React, { Component } from 'react';\n import { ToastContainer, toast } from 'react-toastify';\n import 'react-toastify/dist/ReactToastify.css';\n\n\n class App extends Component {\n notifyA = () => toast('Wow so easy !', {containerId: 'A'});\n notifyB = () => toast('Wow so easy !', {containerId: 'B'});\n\n render(){\n return (\n \n \n \n \n Notify A ! \n Notify B ! \n
\n );\n }\n }\n\n")))}p.isMDXComponent=!0},143:function(t,e,n){"use strict";n.d(e,"a",(function(){return u})),n.d(e,"b",(function(){return m}));var a=n(0),o=n.n(a);function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,a)}return n}function c(t){for(var e=1;e=0||(o[n]=t[n]);return o}(t,e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}var s=o.a.createContext({}),p=function(t){var e=o.a.useContext(s),n=e;return t&&(n="function"==typeof t?t(e):c({},e,{},t)),n},u=function(t){var e=p(t.components);return o.a.createElement(s.Provider,{value:e},t.children)},b={inlineCode:"code",wrapper:function(t){var e=t.children;return o.a.createElement(o.a.Fragment,{},e)}},d=Object(a.forwardRef)((function(t,e){var n=t.components,a=t.mdxType,r=t.originalType,i=t.parentName,s=l(t,["components","mdxType","originalType","parentName"]),u=p(n),d=a,m=u["".concat(i,".").concat(d)]||u[d]||b[d]||r;return n?o.a.createElement(m,c({ref:e},s,{components:n})):o.a.createElement(m,c({ref:e},s))}));function m(t,e){var n=arguments,a=e&&e.mdxType;if("string"==typeof t||a){var r=n.length,i=new Array(r);i[0]=d;var c={};for(var l in e)hasOwnProperty.call(e,l)&&(c[l]=e[l]);c.originalType=t,c.mdxType="string"==typeof t?t:a,i[1]=c;for(var s=2;s async (dispatch, getState) => {
29 | try {
30 | dispatch({ type: ORDER_CREATE_REQUEST })
31 |
32 | const {
33 | userLogin: { userInfo },
34 | } = getState()
35 |
36 | const config = {
37 | headers: {
38 | 'Content-Type': 'application/json',
39 | Authorization: `Bearer ${userInfo.token}`,
40 | },
41 | }
42 | const { data } = await axios.post('/api/orders', order, config)
43 |
44 | dispatch({
45 | type: ORDER_CREATE_SUCCESS,
46 | payload: data,
47 | })
48 |
49 | dispatch({
50 | type: CART_CLEAR_ITEMS,
51 | payload: data,
52 | })
53 | localStorage.removeItem('cartItems')
54 | } catch (error) {
55 | const message =
56 | error.response && error.response.data.message
57 | ? error.response.data.message
58 | : error.message
59 |
60 | if (message === 'Not authorized, token failed') {
61 | dispatch(logout())
62 | }
63 | dispatch({
64 | type: ORDER_CREATE_FAIL,
65 | payload: message,
66 | })
67 | }
68 | }
69 |
70 | export const getOderDetails = (id) => async (dispatch, getState) => {
71 | try {
72 | dispatch({ type: ORDER_DETAILS_REQUEST })
73 |
74 | const {
75 | userLogin: { userInfo },
76 | } = getState()
77 |
78 | const config = {
79 | headers: {
80 | 'Content-Type': 'application/json',
81 | Authorization: `Bearer ${userInfo.token}`,
82 | },
83 | }
84 | const { data } = await axios.get(`/api/orders/${id}`, config)
85 |
86 | dispatch({
87 | type: ORDER_DETAILS_SUCCESS,
88 | payload: data,
89 | })
90 | } catch (error) {
91 | const message =
92 | error.response && error.response.data.message
93 | ? error.response.data.message
94 | : error.message
95 | if (message === 'Not authorized, token failed') {
96 | dispatch(logout())
97 | }
98 | dispatch({
99 | type: ORDER_DETAILS_FAIL,
100 | payload: message,
101 | })
102 | }
103 | }
104 |
105 | export const payOrder =
106 | (orderId, paymentResult) => async (dispatch, getState) => {
107 | try {
108 | dispatch({ type: ORDER_PAY_REQUEST })
109 |
110 | const {
111 | userLogin: { userInfo },
112 | } = getState()
113 |
114 | const config = {
115 | headers: {
116 | 'Content-Type': 'application/json',
117 | Authorization: `Bearer ${userInfo.token}`,
118 | },
119 | }
120 | const { data } = await axios.put(
121 | `/api/orders/${orderId}/pay`,
122 | paymentResult,
123 | config
124 | )
125 |
126 | dispatch({
127 | type: ORDER_PAY_SUCCESS,
128 | payload: data,
129 | })
130 | } catch (error) {
131 | const message =
132 | error.response && error.response.data.message
133 | ? error.response.data.message
134 | : error.message
135 | if (message === 'Not authorized, token failed') {
136 | dispatch(logout())
137 | }
138 | dispatch({
139 | type: ORDER_PAY_FAIL,
140 | payload: message,
141 | })
142 | }
143 | }
144 |
145 | export const listMyOrders = () => async (dispatch, getState) => {
146 | try {
147 | dispatch({ type: ORDER_LIST_MY_REQUEST })
148 |
149 | const {
150 | userLogin: { userInfo },
151 | } = getState()
152 |
153 | const config = {
154 | headers: {
155 | Authorization: `Bearer ${userInfo.token}`,
156 | },
157 | }
158 | const { data } = await axios.get(`/api/orders/myorders`, config)
159 |
160 | dispatch({
161 | type: ORDER_LIST_MY_SUCCESS,
162 | payload: data,
163 | })
164 | } catch (error) {
165 | const message =
166 | error.response && error.response.data.message
167 | ? error.response.data.message
168 | : error.message
169 | if (message === 'Not authorized, token failed') {
170 | dispatch(logout())
171 | }
172 | dispatch({
173 | type: ORDER_LIST_MY_FAIL,
174 | payload: message,
175 | })
176 | }
177 | }
178 |
179 | export const listOrders = () => async (dispatch, getState) => {
180 | try {
181 | dispatch({ type: ORDER_LIST_REQUEST })
182 |
183 | const {
184 | userLogin: { userInfo },
185 | } = getState()
186 |
187 | const config = {
188 | headers: {
189 | Authorization: `Bearer ${userInfo.token}`,
190 | },
191 | }
192 | const { data } = await axios.get(`/api/orders`, config)
193 |
194 | dispatch({
195 | type: ORDER_LIST_SUCCESS,
196 | payload: data,
197 | })
198 | } catch (error) {
199 | const message =
200 | error.response && error.response.data.message
201 | ? error.response.data.message
202 | : error.message
203 | if (message === 'Not authorized, token failed') {
204 | dispatch(logout())
205 | }
206 | dispatch({
207 | type: ORDER_LIST_FAIL,
208 | payload: message,
209 | })
210 | }
211 | }
212 |
213 | export const deliverOrder = (order) => async (dispatch, getState) => {
214 | try {
215 | dispatch({ type: ORDER_DELIVER_REQUEST })
216 |
217 | const {
218 | userLogin: { userInfo },
219 | } = getState()
220 |
221 | const config = {
222 | headers: {
223 | Authorization: `Bearer ${userInfo.token}`,
224 | },
225 | }
226 | const { data } = await axios.put(
227 | `/api/orders/${order._id}/deliver`,
228 | {},
229 | config
230 | )
231 |
232 | dispatch({
233 | type: ORDER_DELIVER_SUCCESS,
234 | payload: data,
235 | })
236 | } catch (error) {
237 | const message =
238 | error.response && error.response.data.message
239 | ? error.response.data.message
240 | : error.message
241 | if (message === 'Not authorized, token failed') {
242 | dispatch(logout())
243 | }
244 | dispatch({
245 | type: ORDER_DELIVER_FAIL,
246 | payload: message,
247 | })
248 | }
249 | }
250 |
251 | export const pendingOrder = (order) => async (dispatch, getState) => {
252 | try {
253 | dispatch({ type: ORDER_PENDING_REQUEST })
254 |
255 | const {
256 | userLogin: { userInfo },
257 | } = getState()
258 |
259 | const config = {
260 | headers: {
261 | Authorization: `Bearer ${userInfo.token}`,
262 | },
263 | }
264 | const { data } = await axios.put(
265 | `/api/orders/${order._id}/pending`,
266 | {},
267 | config
268 | )
269 |
270 | dispatch({
271 | type: ORDER_PENDING_SUCCESS,
272 | payload: data,
273 | })
274 | } catch (error) {
275 | dispatch({
276 | type: ORDER_PENDING_FAIL,
277 | payload:
278 | error.response && error.response.data.message
279 | ? error.response.data.message
280 | : error.message,
281 | })
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/client/src/screens/ProductEditScreen.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import React, { useState, useEffect } from 'react'
3 | import { Link } from 'react-router-dom'
4 | import { Form, Button, Spinner, Image } from 'react-bootstrap'
5 | import { useDispatch, useSelector } from 'react-redux'
6 | import Message from '../components/Message'
7 | import FormContainer from '../components/FormContainer'
8 | import Loader from '../components/Loader'
9 | import { listProductDetail, updateProduct } from '../actions/productActions'
10 | import { PRODUCT_UPDATE_RESET } from '../constants/productConstants'
11 | import leftArrow from '../assets/icons/arrow-left.svg'
12 |
13 | const ProductEditScreen = ({ match, history }) => {
14 | const productId = match.params.id
15 |
16 | const [name, setName] = useState('')
17 | const [price, setPrice] = useState(0)
18 | const [image, setImage] = useState('')
19 | const [brand, setBrand] = useState('')
20 | const [category, setCategory] = useState('')
21 | const [countInStock, setCountInStock] = useState(0)
22 | const [description, setDescription] = useState('')
23 | const [uploading, setUploading] = useState(false)
24 |
25 | const dispatch = useDispatch()
26 |
27 | const productDetails = useSelector((state) => state.productDetails)
28 | const { loading, error, product } = productDetails
29 |
30 | const productUpdate = useSelector((state) => state.productUpdate)
31 | const {
32 | loading: loadingUpdate,
33 | error: errorUpdate,
34 | success: successUpdate,
35 | } = productUpdate
36 |
37 | useEffect(() => {
38 | window.scrollTo(0, 0)
39 | if (successUpdate) {
40 | dispatch({ type: PRODUCT_UPDATE_RESET })
41 | history.push('/admin/productlist')
42 | } else {
43 | if (!product.name || product._id !== productId) {
44 | dispatch(listProductDetail(productId))
45 | } else {
46 | setName(product.name)
47 | setPrice(product.price)
48 | setImage(product.image)
49 | setBrand(product.brand)
50 | setCategory(product.category)
51 | setCountInStock(product.countInStock)
52 | setDescription(product.description)
53 | }
54 | }
55 | }, [dispatch, productId, product, history, successUpdate])
56 |
57 | const submitHandler = (e) => {
58 | e.preventDefault()
59 |
60 | dispatch(
61 | updateProduct({
62 | _id: productId,
63 | name,
64 | price,
65 | image,
66 | brand,
67 | category,
68 | description,
69 | countInStock,
70 | })
71 | )
72 | }
73 |
74 | const uploadFileHandler = async (e) => {
75 | const file = e.target.files[0]
76 | const formData = new FormData()
77 | formData.append('image', file)
78 | setUploading(true)
79 |
80 | try {
81 | const config = {
82 | headers: {
83 | 'Content-Type': 'multipart/form-data',
84 | },
85 | }
86 | const { data } = await axios.post('/api/upload', formData, config)
87 |
88 | setImage(data)
89 | setUploading(false)
90 | } catch (error) {
91 | console.error(error)
92 | setUploading(false)
93 | }
94 | }
95 |
96 | return (
97 |
98 |
99 |
100 |
101 | {' '}
102 | Go Back
103 |
104 |
105 |
106 | Edit Product Details
107 | {/*loadingUpdate && */}
108 | {errorUpdate && {errorUpdate} }
109 |
110 | {loading ? (
111 |
112 | ) : error ? (
113 | {error}
114 | ) : (
115 |
117 | Product Name
118 | setName(e.target.value)}
123 | className="input"
124 | >
125 |
126 |
127 |
128 | Price
129 | setPrice(e.target.value)}
134 | className="input"
135 | >
136 |
137 |
138 |
139 | Image Url
140 | setImage(e.target.value)}
145 | className="input"
146 | >
147 |
153 | {uploading && }
154 |
155 |
156 |
157 | Brand
158 | setBrand(e.target.value)}
163 | className="input"
164 | >
165 |
166 |
167 |
168 | Count In Stock
169 | setCountInStock(e.target.value)}
174 | className="input"
175 | >
176 |
177 |
178 |
179 | Category
180 | setCategory(e.target.value)}
185 | className="input"
186 | >
187 |
188 |
189 |
190 | Description
191 | setDescription(e.target.value)}
196 | className="input"
197 | >
198 |
199 |
200 | {loadingUpdate ? (
201 |
202 |
203 | {' '} Updating...
204 |
205 |
206 | ) : (
207 |
208 | Update Product
209 |
210 | )}
211 |
212 | )}
213 |
214 |
215 | )
216 | }
217 |
218 | export default ProductEditScreen
219 |
--------------------------------------------------------------------------------
/client/src/screens/ProfileScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Form, Button, Row, Col, Spinner, Table } from 'react-bootstrap'
3 | import { LinkContainer } from 'react-router-bootstrap'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import Message from '../components/Message'
6 | import Loader from '../components/Loader'
7 | import { getUserDetails, updateUserProfile } from '../actions/userActions'
8 | import { listMyOrders } from '../actions/orderActions'
9 | import { USER_UPDATE_PROFILE_RESET } from '../constants/userConstants'
10 |
11 | const ProfileScreen = ({ history }) => {
12 | const [name, setName] = useState('')
13 | const [email, setEmail] = useState('')
14 | const [password, setPassword] = useState('')
15 | const [confirmPassword, setConfirmPassword] = useState('')
16 | const [message, setMessage] = useState(null)
17 |
18 | const dispatch = useDispatch()
19 |
20 | const userDetails = useSelector((state) => state.userDetails)
21 | const { error, user } = userDetails
22 |
23 | const userLogin = useSelector((state) => state.userLogin)
24 | const { userInfo, loading: userLoading } = userLogin
25 |
26 | const userUpdateProfile = useSelector((state) => state.userUpdateProfile)
27 |
28 | const { loading, success } = userUpdateProfile
29 |
30 | const orderListMy = useSelector((state) => state.orderListMy)
31 | const { loading: loadingOrders, error: errorOrders, orders } = orderListMy
32 |
33 | useEffect(() => {
34 | window.scrollTo(0, 0)
35 | if (!userInfo) {
36 | history.push('/login')
37 | } else {
38 | if (!user || !user.name || success) {
39 | dispatch({ type: USER_UPDATE_PROFILE_RESET })
40 | dispatch(getUserDetails('profile'))
41 | dispatch(listMyOrders())
42 | } else {
43 | setName(user.name)
44 | setEmail(user.email)
45 | }
46 | }
47 | }, [dispatch, history, userInfo, user, success])
48 |
49 | const submitHandler = (e) => {
50 | e.preventDefault()
51 |
52 | //check password equality
53 | if (password !== confirmPassword) {
54 | setMessage('Passwords do not match!')
55 | } else {
56 | dispatch(updateUserProfile({ id: user._id, name, email, password }))
57 | }
58 | }
59 |
60 | return (
61 |
62 | {userLoading || loading || loadingOrders ? (
63 |
64 | ) : (
65 |
66 |
67 | User Profile
68 | {message && {message} }
69 |
70 | {success && (
71 | Profile Updated Successfully
72 | )}
73 | {userLoading ? (
74 |
75 | ) : error ? (
76 | {error}
77 | ) : (
78 |
80 | Full Name
81 | setName(e.target.value)}
86 | className='input'
87 | >
88 |
89 |
90 |
91 | Email Address
92 | setEmail(e.target.value)}
97 | className='input'
98 | >
99 |
100 |
101 |
102 | Password
103 | setPassword(e.target.value)}
108 | className='input'
109 | >
110 |
111 |
112 |
113 | Confirm Password
114 | setConfirmPassword(e.target.value)}
119 | className='input'
120 | >
121 |
122 |
123 | {loading ? (
124 |
125 |
131 | Update
132 |
133 |
134 | ) : (
135 |
136 | Update
137 |
138 | )}
139 |
140 | )}
141 |
142 |
143 | My Orders
144 | {loadingOrders ? (
145 |
146 | ) : errorOrders ? (
147 | {errorOrders}
148 | ) : (
149 |
150 |
151 |
152 | ID
153 | Date
154 | TOTAL
155 | PAID
156 | DELIVERED
157 | ACTION
158 |
159 |
160 |
161 | {orders.map((order) => (
162 |
163 | {order._id}
164 | {order.createdAt.substring(0, 10)}
165 | $ {order.totalPrice}
166 |
167 | {order.isPaid ? (
168 |
169 | {order.paidAt.substring(0, 10)}
170 |
171 | ) : (
172 | Pending
173 | )}
174 |
175 |
176 |
177 | {order.isDelivered ? (
178 |
179 | {order.deliveredAt.substring(0, 10)}
180 |
181 | ) : (
182 | Not Delivered
183 | )}
184 |
185 |
186 |
187 |
191 | Details
192 |
193 |
194 |
195 |
196 | ))}
197 |
198 |
199 | )}
200 |
201 |
202 | )}
203 |
204 | )
205 | }
206 |
207 | export default ProfileScreen
208 |
--------------------------------------------------------------------------------