├── .nvmrc ├── mocharc.json ├── frontend ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── about.txt │ ├── manifest.json │ └── index.html ├── src │ ├── constants │ │ ├── deliveryChargeConstants.js │ │ ├── categoryConstants.js │ │ ├── cartConstants.js │ │ ├── carouselConstants.js │ │ ├── orderConstants.js │ │ ├── productConstants.js │ │ └── userConstants.js │ ├── utils │ │ ├── capitalizeFirstLetter.js │ │ ├── currencyFormatter.js │ │ └── calculateDiscount.js │ ├── setupTests.js │ ├── App.test.js │ ├── screens │ │ ├── CategoryListScreen.js │ │ ├── Error404Screen.js │ │ ├── CategoryScreen.js │ │ ├── SearchScreen.js │ │ ├── HomeScreen.js │ │ ├── ForgotPasswordScreen.js │ │ ├── PaymentScreen.js │ │ ├── CreateCarouselScreen.js │ │ ├── ResetPasswordScreen.js │ │ ├── LoginScreen.js │ │ ├── CarouselListScreen.js │ │ ├── OrderListScreen.js │ │ ├── UsersListScreen.js │ │ ├── CarouselEditScreen.js │ │ ├── RegisterScreen.js │ │ ├── UserEditScreen.js │ │ ├── ProductListScreen.js │ │ ├── ShippingScreen.js │ │ ├── ProductScreen.js │ │ ├── CartScreen.js │ │ └── ProfileScreen.js │ ├── components │ │ ├── FormContainer.js │ │ ├── Loader.js │ │ ├── Message.js │ │ ├── Category.js │ │ ├── SearchBox.js │ │ ├── Slider.js │ │ ├── Footer.js │ │ ├── Product.js │ │ ├── CheckoutSteps.js │ │ └── Header.js │ ├── reportWebVitals.js │ ├── pages │ │ ├── Privacy.js │ │ ├── Terms.js │ │ ├── Returns.js │ │ └── Help.js │ ├── reducers │ │ ├── categoryReducers.js │ │ ├── cartReducers.js │ │ ├── carouselReducers.js │ │ ├── productReducers.js │ │ ├── orderReducers.js │ │ └── userReducers.js │ ├── actions │ │ ├── categoryActions.js │ │ ├── cartActions.js │ │ ├── carouselActions.js │ │ ├── productActions.js │ │ └── orderActions.js │ ├── index.js │ ├── store.js │ ├── App.js │ └── index.css ├── package.json └── README.md ├── .prettierrc.json ├── backend ├── utils │ ├── generateToken.js │ └── email.js ├── data │ ├── carousels.js │ ├── users.js │ ├── categories.js │ └── products.js ├── middleware │ ├── errorMiddleware.js │ └── authMiddleware.js ├── routes │ ├── carouselRoutes.js │ ├── productRoutes.js │ ├── orderRoutes.js │ ├── userRoutes.js │ ├── uploadRoutes.js │ └── categoryRoutes.js ├── models │ ├── carouselModel.js │ ├── categoryModel.js │ ├── userModel.js │ ├── productModel.js │ └── orderModel.js ├── config │ └── db.js ├── controllers │ ├── carouselControllers.js │ ├── productController.js │ └── orderControllers.js ├── seeder.js └── server.js ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── test ├── orderControllers.test.js ├── productController.test.js └── userController.test.js ├── package.json ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v14 -------------------------------------------------------------------------------- /mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": 10000, 3 | "exit": true 4 | } -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/src/constants/deliveryChargeConstants.js: -------------------------------------------------------------------------------- 1 | export const freeDeliveryCutoff = 400 2 | export const deliveryCharge = 40 3 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roopeshsn/freshcomm/HEAD/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/src/utils/capitalizeFirstLetter.js: -------------------------------------------------------------------------------- 1 | export default function capitalizeFirstLetter(s) { 2 | return (s && s[0].toUpperCase() + s.slice(1)) || s 3 | } 4 | -------------------------------------------------------------------------------- /backend/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | const generateToken = (id) => { 4 | return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d' }) 5 | } 6 | 7 | module.exports = generateToken 8 | -------------------------------------------------------------------------------- /frontend/src/constants/categoryConstants.js: -------------------------------------------------------------------------------- 1 | export const CATEGORY_LIST_REQUEST = 'CATEGORY_LIST_REQUEST' 2 | export const CATEGORY_LIST_SUCCESS = 'CATEGORY_LIST_SUCCESS' 3 | export const CATEGORY_LIST_FAIL = 'CATEGORY_LIST_FAIL' 4 | -------------------------------------------------------------------------------- /frontend/src/utils/currencyFormatter.js: -------------------------------------------------------------------------------- 1 | export default function formatter(value) { 2 | const options = { 3 | style: 'currency', 4 | currency: 'INR', 5 | minimumFractionDigits: 2, 6 | } 7 | return new Intl.NumberFormat('en-IN', options).format(value) 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom' 6 | -------------------------------------------------------------------------------- /frontend/src/constants/cartConstants.js: -------------------------------------------------------------------------------- 1 | export const CART_ADD_ITEM = 'CART_ADD_ITEM' 2 | export const CART_REMOVE_ITEM = 'CART_REMOVE_ITEM' 3 | export const CART_SAVE_SHIPPING_ADDRESS = 'CART_SAVE_SHIPPING_ADDRESS' 4 | export const CART_SAVE_PAYMENT_METHOD = 'CART_SAVE_PAYMENT_METHOD' 5 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react' 2 | import App from './App' 3 | 4 | test('renders learn react link', () => { 5 | render() 6 | const linkElement = screen.getByText(/learn react/i) 7 | expect(linkElement).toBeInTheDocument() 8 | }) 9 | -------------------------------------------------------------------------------- /frontend/src/utils/calculateDiscount.js: -------------------------------------------------------------------------------- 1 | function calculateDiscount(originalPrice, sellingPrice) { 2 | let discountPrice = originalPrice - sellingPrice 3 | let discountPercentage = Math.floor((discountPrice / originalPrice) * 100) 4 | return [discountPrice, discountPercentage] 5 | } 6 | 7 | export default calculateDiscount 8 | -------------------------------------------------------------------------------- /frontend/src/screens/CategoryListScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Col, Row } from 'react-bootstrap' 3 | 4 | export const CategoryListScreen = () => { 5 | return ( 6 | <> 7 | 8 | 9 |

Category

10 | 11 |
12 | 13 |

Page is under construction!

14 |
15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /backend/data/carousels.js: -------------------------------------------------------------------------------- 1 | const carousels = [ 2 | { 3 | imageSrc: 'https://i.ibb.co/VxN2j1t/slide-1.jpg', 4 | imageAlt: 'slide-1', 5 | }, 6 | { 7 | imageSrc: 'https://i.ibb.co/J7rQchw/slide-2.jpg', 8 | imageAlt: 'slide-2', 9 | }, 10 | 11 | { 12 | imageSrc: 'https://i.ibb.co/pJpN4Fx/slide-3.jpg', 13 | imageAlt: 'slide-3', 14 | }, 15 | ] 16 | 17 | module.exports = carousels 18 | -------------------------------------------------------------------------------- /frontend/public/about.txt: -------------------------------------------------------------------------------- 1 | This favicon was generated using the following graphics from Twitter Twemoji: 2 | 3 | - Graphics Title: 1f6cd.svg 4 | - Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji) 5 | - Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f6cd.svg 6 | - Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 7 | -------------------------------------------------------------------------------- /backend/data/users.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs') 2 | 3 | const users = [ 4 | { 5 | name: 'Roopesh', 6 | email: 'roopeshsaravanan.dev@gmail.com', 7 | password: bcrypt.hashSync('123456', 10), 8 | isAdmin: true, 9 | }, 10 | { 11 | name: 'Admin', 12 | email: 'admin@admin.com', 13 | password: bcrypt.hashSync('123456', 10), 14 | isAdmin: true, 15 | }, 16 | ] 17 | 18 | module.exports = users 19 | -------------------------------------------------------------------------------- /frontend/src/components/FormContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container, Row, Col } 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 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry) 5 | getFID(onPerfEntry) 6 | getFCP(onPerfEntry) 7 | getLCP(onPerfEntry) 8 | getTTFB(onPerfEntry) 9 | }) 10 | } 11 | } 12 | 13 | export default reportWebVitals 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | node_modules/ 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /frontend/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 | 27 | -------------------------------------------------------------------------------- /frontend/src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Spinner } from 'react-bootstrap' 3 | 4 | const Loader = () => { 5 | return ( 6 | 13 | Loading... 14 | 15 | ) 16 | } 17 | 18 | export default Loader 19 | -------------------------------------------------------------------------------- /frontend/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Alert } from 'react-bootstrap' 3 | 4 | const Message = ({ className, variant, children }) => { 5 | return ( 6 | 11 | {children} 12 | 13 | ) 14 | } 15 | 16 | Message.defaultProps = { 17 | variant: 'info', 18 | className: 'my-2', 19 | } 20 | 21 | export default Message 22 | -------------------------------------------------------------------------------- /backend/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 statusCode = res.statusCode === 200 ? 500 : res.statusCode 9 | res.status(statusCode) 10 | res.json({ 11 | message: err.message, 12 | stack: process.env.NODE_ENV === 'production' ? null : err.stack, 13 | }) 14 | } 15 | 16 | module.exports = { notFound, errorHandler } 17 | -------------------------------------------------------------------------------- /backend/routes/carouselRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { 3 | getCarousels, 4 | updateCarousel, 5 | getCarouselById, 6 | createCarousel, 7 | } = require('../controllers/carouselControllers') 8 | const { protect, admin } = require('../middleware/authMiddleware') 9 | const router = express.Router() 10 | 11 | router.route('/').get(getCarousels).post(protect, admin, createCarousel) 12 | 13 | router.route('/:id').get(getCarouselById).put(protect, admin, updateCarousel) 14 | 15 | module.exports = router 16 | -------------------------------------------------------------------------------- /backend/models/carouselModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const carouselSchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: 'User', 9 | }, 10 | imageSrc: { 11 | type: String, 12 | required: true, 13 | }, 14 | imageAlt: { 15 | type: String, 16 | required: true, 17 | }, 18 | }, 19 | { 20 | timestamps: true, 21 | }, 22 | ) 23 | 24 | const Carousel = mongoose.model('Carousel', carouselSchema) 25 | 26 | module.exports = Carousel 27 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Large Green", 3 | "name": "Large Green - Daily Grocery App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, 11 | { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } 12 | ], 13 | "start_url": ".", 14 | "display": "standalone", 15 | "theme_color": "#000000", 16 | "background_color": "#ffffff" 17 | } 18 | -------------------------------------------------------------------------------- /backend/routes/productRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const { 4 | getProducts, 5 | getProductById, 6 | deleteProduct, 7 | updateProduct, 8 | createProduct, 9 | } = require('../controllers/productController') 10 | const { protect, admin } = require('../middleware/authMiddleware') 11 | 12 | router.route('/').get(getProducts).post(protect, admin, createProduct) 13 | router 14 | .route('/:id') 15 | .get(getProductById) 16 | .delete(protect, admin, deleteProduct) 17 | .put(protect, admin, updateProduct) 18 | 19 | module.exports = router 20 | -------------------------------------------------------------------------------- /frontend/src/pages/Privacy.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Privacy = () => { 4 | return ( 5 |
6 |

Privacy Policy

7 |

8 | We value the trust you place in us and recognize the importance of 9 | secure transactions and information privacy. This Privacy Policy 10 | describes how Freshbey and its affiliates (collectively “Freshbey, we, 11 | our, us”) collect, use, share or otherwise process your personal 12 | information through Freshbey website. 13 |

14 |
15 | ) 16 | } 17 | 18 | export default Privacy 19 | -------------------------------------------------------------------------------- /frontend/src/components/Category.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Card } from 'react-bootstrap' 3 | import { Link } from 'react-router-dom' 4 | 5 | const Category = ({ category }) => { 6 | return ( 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Category 23 | -------------------------------------------------------------------------------- /frontend/src/reducers/categoryReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CATEGORY_LIST_REQUEST, 3 | CATEGORY_LIST_SUCCESS, 4 | CATEGORY_LIST_FAIL, 5 | } from '../constants/categoryConstants' 6 | 7 | export const categoryListReducer = (state = { categories: [] }, action) => { 8 | switch (action.type) { 9 | case CATEGORY_LIST_REQUEST: 10 | return { loading: true, categories: [] } 11 | case CATEGORY_LIST_SUCCESS: 12 | return { loading: false, categories: action.payload } 13 | case CATEGORY_LIST_FAIL: 14 | return { loading: false, error: action.payload } 15 | default: 16 | return state 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/utils/email.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | 3 | const sendEmail = async (options) => { 4 | const transporter = nodemailer.createTransport({ 5 | host: process.env.EMAIL_HOST, 6 | port: process.env.EMAIL_PORT, 7 | auth: { 8 | user: process.env.EMAIL_USERNAME, 9 | pass: process.env.EMAIL_PASSWORD, 10 | }, 11 | }) 12 | 13 | const mailOptions = { 14 | from: 'Roopesh Saravanan { 4 | return ( 5 |
6 |

Terms

7 |

8 | This document is an electronic record in terms of Information Technology 9 | Act, 2000 and rules there under as applicable and the amended provisions 10 | pertaining to electronic records in various statutes as amended by the 11 | Information Technology Act, 2000. This electronic record is generated by 12 | a computer system and does not require any physical or digital 13 | signatures. 14 |

15 |
16 | ) 17 | } 18 | 19 | export default Terms 20 | -------------------------------------------------------------------------------- /frontend/src/pages/Returns.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Returns = () => { 4 | return ( 5 |
6 |

Returns

7 |

8 | Returns is a scheme provided by respective sellers directly under this 9 | policy in terms of which the option of exchange, replacement and/ or 10 | refund is offered by the respective sellers to you. All products listed 11 | under a particular category may not have the same returns policy. For 12 | all products, the returns/replacement policy provided on the product 13 | page shall prevail over the general returns policy. 14 |

15 |
16 | ) 17 | } 18 | 19 | export default Returns 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /frontend/src/actions/categoryActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import { 4 | CATEGORY_LIST_REQUEST, 5 | CATEGORY_LIST_SUCCESS, 6 | CATEGORY_LIST_FAIL, 7 | } from '../constants/categoryConstants' 8 | 9 | export const listCategories = () => async (dispatch) => { 10 | try { 11 | dispatch({ type: CATEGORY_LIST_REQUEST }) 12 | const { data } = await axios.get(`/api/categories`) 13 | dispatch({ type: CATEGORY_LIST_SUCCESS, payload: data }) 14 | } catch (error) { 15 | dispatch({ 16 | type: CATEGORY_LIST_FAIL, 17 | payload: 18 | error.response && error.response.data.message 19 | ? error.response.data.message 20 | : error.message, 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const { 4 | addOrderItems, 5 | getOrderById, 6 | updateOrderToPaid, 7 | getMyOrders, 8 | getOrders, 9 | updateOrderToDelivered, 10 | } = require('../controllers/orderControllers') 11 | const { protect, admin } = require('../middleware/authMiddleware.js') 12 | 13 | router.route('/').post(protect, addOrderItems).get(protect, admin, getOrders) 14 | router.route('/myorders').get(protect, getMyOrders) 15 | router.route('/:id').get(protect, getOrderById) 16 | router.route('/:id/pay').put(protect, updateOrderToPaid) 17 | router.route('/:id/deliver').put(protect, admin, updateOrderToDelivered) 18 | 19 | module.exports = router 20 | -------------------------------------------------------------------------------- /backend/models/categoryModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const categorySchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: 'User', 9 | }, 10 | name: { 11 | type: String, 12 | required: true, 13 | }, 14 | imageSrc: { 15 | type: String, 16 | required: true, 17 | }, 18 | imageAlt: { 19 | type: String, 20 | required: true, 21 | }, 22 | href: { 23 | type: String, 24 | required: true, 25 | }, 26 | }, 27 | { 28 | timestamps: true, 29 | }, 30 | ) 31 | 32 | const Category = mongoose.model('Category', categorySchema) 33 | 34 | module.exports = Category 35 | -------------------------------------------------------------------------------- /frontend/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 | import axios from 'axios' 11 | axios.defaults.withCredentials = true 12 | ReactDOM.render( 13 | 14 | 15 | , 16 | document.getElementById('root'), 17 | ) 18 | 19 | // If you want to start measuring performance in your app, pass a function 20 | // to log results (for example: reportWebVitals(console.log)) 21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 22 | reportWebVitals() 23 | -------------------------------------------------------------------------------- /backend/config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const cloudinary = require('cloudinary').v2 3 | 4 | const connectDB = async () => { 5 | try { 6 | const conn = await mongoose.connect(process.env.MONGODB_URI, { 7 | useNewUrlParser: true, 8 | useUnifiedTopology: true, 9 | }) 10 | console.log(`MongoDB connected: ${conn.connection.host}`) 11 | } catch (error) { 12 | console.log(`Error: ${error.message}`) 13 | process.exit(1) 14 | } 15 | } 16 | 17 | //cloudinary config details which will be used further to upload images 18 | cloudinary.config({ 19 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 20 | api_key: process.env.CLOUDINARY_API_KEY, 21 | api_secret: process.env.CLOUDINARY_API_SECRET, 22 | secure: true, 23 | }) 24 | 25 | module.exports = { 26 | connectDB, 27 | cloudinary, 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/screens/Error404Screen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Row, Col, Button } from 'react-bootstrap' 3 | import { LinkContainer } from 'react-router-bootstrap' 4 | 5 | function Error404Screen() { 6 | return ( 7 | <> 8 |
9 |
10 | 11 | 12 |
404
13 | 14 | 15 |

16 | Page not found 17 |

18 | 19 |
20 | 21 | 22 | 23 |
24 | 25 | ) 26 | } 27 | 28 | export default Error404Screen 29 | -------------------------------------------------------------------------------- /backend/data/categories.js: -------------------------------------------------------------------------------- 1 | const categories = [ 2 | { 3 | name: 'vegetables', 4 | imageSrc: 'https://i.ibb.co/8D0nnzc/fresh-vegetables.jpg', 5 | imageAlt: 'vegetables image', 6 | href: '#', 7 | }, 8 | { 9 | name: 'fruits', 10 | imageSrc: 'https://i.ibb.co/zmRpGsD/fresh-fruits.jpg', 11 | imageAlt: 'fruits image', 12 | href: '#', 13 | }, 14 | 15 | { 16 | name: 'exotic', 17 | imageSrc: 'https://i.ibb.co/Jx1rPLJ/exotic-corner.jpg', 18 | imageAlt: 'exotic image', 19 | href: '#', 20 | }, 21 | { 22 | name: 'seasonal', 23 | imageSrc: 'https://i.ibb.co/sbjKQdv/seasonal-veggies.jpg', 24 | imageAlt: 'seasonal image', 25 | href: '#', 26 | }, 27 | 28 | { 29 | name: 'sprouts', 30 | imageSrc: 'https://i.ibb.co/7x3y5nT/cuts-and-sprouts.jpg', 31 | imageAlt: 'sprouts image', 32 | href: '#', 33 | }, 34 | ] 35 | 36 | module.exports = categories 37 | -------------------------------------------------------------------------------- /backend/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { 3 | authUser, 4 | getUserProfile, 5 | registerUser, 6 | updateUserProfile, 7 | forgotPassword, 8 | resetPassword, 9 | getUsers, 10 | deleteUser, 11 | getUserById, 12 | updateUser, 13 | logoutUser, 14 | } = require('../controllers/userController') 15 | const { protect, admin } = require('../middleware/authMiddleware') 16 | const router = express.Router() 17 | 18 | router.route('/').post(registerUser).get(protect, admin, getUsers) 19 | router.post('/login', authUser) 20 | router 21 | .route('/profile') 22 | .get(protect, getUserProfile) 23 | .put(protect, updateUserProfile) 24 | router.post('/forgotpassword', forgotPassword) 25 | router.patch('/resetpassword/:token', resetPassword) 26 | router.get('/logout', logoutUser) 27 | router 28 | .route('/:id') 29 | .delete(protect, admin, deleteUser) 30 | .get(protect, admin, getUserById) 31 | .put(protect, admin, updateUser) 32 | 33 | module.exports = router 34 | -------------------------------------------------------------------------------- /backend/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const asyncHandler = require('express-async-handler') 3 | const User = require('../models/userModel') 4 | 5 | const protect = asyncHandler(async (req, res, next) => { 6 | const { accessToken: token } = req.cookies 7 | if (token) { 8 | try { 9 | const decoded = jwt.verify(token, process.env.JWT_SECRET) 10 | req.user = await User.findById(decoded.id).select('-password') 11 | next() 12 | } catch (error) { 13 | console.error(error) 14 | res.status(401) 15 | throw new Error('Not authorized, token failed') 16 | } 17 | } 18 | 19 | if (!token) { 20 | res.status(401) 21 | throw new Error('Not authorized, no token') 22 | } 23 | }) 24 | 25 | const admin = (req, res, next) => { 26 | if (req.user && req.user.isAdmin) { 27 | next() 28 | } else { 29 | res.status(401) 30 | throw new Error('Not authorized as an admin') 31 | } 32 | } 33 | 34 | module.exports = { protect, admin } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /frontend/src/constants/carouselConstants.js: -------------------------------------------------------------------------------- 1 | export const CAROUSEL_LIST_REQUEST = 'CAROUSEL_LIST_REQUEST' 2 | export const CAROUSEL_LIST_SUCCESS = 'CAROUSEL_LIST_SUCCESS' 3 | export const CAROUSEL_LIST_FAIL = 'CAROUSEL_LIST_FAIL' 4 | 5 | export const CAROUSEL_DETAILS_REQUEST = 'CAROUSEL_DETAILS_REQUEST' 6 | export const CAROUSEL_DETAILS_SUCCESS = 'CAROUSEL_DETAILS_SUCCESS' 7 | export const CAROUSEL_DETAILS_FAIL = 'CAROUSEL_DETAILS_FAIL' 8 | export const CAROUSEL_DETAILS_RESET = 'CAROUSEL_DETAILS_RESET' 9 | 10 | export const CAROUSEL_CREATE_REQUEST = 'CAROUSEL_CREATE_REQUEST' 11 | export const CAROUSEL_CREATE_SUCCESS = 'CAROUSEL_CREATE_SUCCESS' 12 | export const CAROUSEL_CREATE_FAIL = 'CAROUSEL_CREATE_FAIL' 13 | export const CAROUSEL_CREATE_RESET = 'CAROUSEL_CREATE_RESET' 14 | 15 | export const CAROUSEL_UPDATE_REQUEST = 'CAROUSEL_UPDATE_REQUEST' 16 | export const CAROUSEL_UPDATE_SUCCESS = 'CAROUSEL_UPDATE_SUCCESS' 17 | export const CAROUSEL_UPDATE_FAIL = 'CAROUSEL_UPDATE_FAIL' 18 | export const CAROUSEL_UPDATE_RESET = 'CAROUSEL_UPDATE_RESET' 19 | -------------------------------------------------------------------------------- /frontend/src/components/SearchBox.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Form } from 'react-bootstrap' 3 | import FormContainer from './FormContainer' 4 | 5 | const SearchBox = ({ history }) => { 6 | const [keyword, setKeyword] = useState('') 7 | 8 | const submitHandler = (e) => { 9 | e.preventDefault() 10 | setKeyword(e.target.value) 11 | if (keyword.trim()) { 12 | history.push(`/search/${keyword}`) 13 | } else { 14 | history.push('/') 15 | } 16 | } 17 | 18 | return ( 19 | 20 |
21 | submitHandler(e)} 25 | placeholder="Search Freshbey" 26 | className="me-2" 27 | > 28 | 29 | {/* */} 32 |
33 |
34 | ) 35 | } 36 | 37 | export default SearchBox 38 | -------------------------------------------------------------------------------- /backend/routes/uploadRoutes.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const multer = require('multer') 4 | const { cloudinary } = require('../config/db') 5 | const { CloudinaryStorage } = require('multer-storage-cloudinary') 6 | 7 | const router = express.Router() 8 | 9 | const storage = new CloudinaryStorage({ 10 | cloudinary, 11 | params: { 12 | folder: 'Products', 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 | return cb(new Error('Upload Images(.jpg, .jpeg, .png) 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 | module.exports = router 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Roopesh Saravanan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | Freshbey 21 | 22 | 23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /backend/routes/categoryRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const asyncHandler = require('express-async-handler') 3 | const router = express.Router() 4 | const Product = require('../models/productModel') 5 | const Category = require('../models/categoryModel') 6 | 7 | // @desc Fetch all categories 8 | // @route GET /api/categories 9 | // @access Public 10 | 11 | router.get( 12 | '/', 13 | asyncHandler(async (req, res) => { 14 | const categories = await Category.find({}) 15 | res.json(categories) 16 | }), 17 | ) 18 | 19 | // @desc Fetch products based on category 20 | // @route GET /api/categories/:category 21 | // @access Public 22 | 23 | router.get( 24 | '/:category', 25 | asyncHandler(async (req, res) => { 26 | const categorywiseProducts = await Product.find({ 27 | category: req.params.category, 28 | }) 29 | // const categorywiseProducts = products.filter((p) => p.category === req.params.category) 30 | if (categorywiseProducts) { 31 | res.json(categorywiseProducts) 32 | } else { 33 | res.status(404) 34 | throw new Error('Products not found') 35 | } 36 | }), 37 | ) 38 | 39 | module.exports = router 40 | -------------------------------------------------------------------------------- /frontend/src/pages/Help.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Help = () => { 4 | return ( 5 |
6 |

Freshbey Help Centre

7 |

8 | The Freshbey Help Centre page lists out various types of issues that you 9 | may have encountered so that there can be quick resolution and you can 10 | go back to shopping online. For example, you can get more information 11 | regarding order tracking, delivery date changes, help with returns (and 12 | refunds), and much more. You can get the Freshbey Help Centre number or 13 | even access Freshbey Help Centre support if you need professional help 14 | regarding various topics. The support executive will ensure speedy 15 | assistance so that your shopping experience is positive and enjoyable. 16 | You can even inform your loved ones of the support page so that they can 17 | properly get their grievances addressed as well. Once you have all your 18 | queries addressed, you can pull out your shopping list and shop for all 19 | your essentials in one place. You can shop during festive sales to get 20 | your hands on some unbelievable deals online. 21 |

22 |
23 | ) 24 | } 25 | 26 | export default Help 27 | -------------------------------------------------------------------------------- /frontend/src/screens/CategoryScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Col, Row } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import Product from '../components/Product' 5 | import { listProductsByCategory } from '../actions/productActions' 6 | import Loader from '../components/Loader' 7 | import Message from '../components/Message' 8 | 9 | const CategoryScreen = ({ match, history }) => { 10 | const dispatch = useDispatch() 11 | const productList = useSelector((state) => state.productListByCategory) 12 | const { loading, error, products } = productList 13 | useEffect(() => { 14 | dispatch(listProductsByCategory(match.params.category)) 15 | }, [dispatch, match]) 16 | return ( 17 | <> 18 |

Latest Stocks on demand

19 | {loading ? ( 20 | 21 | ) : error ? ( 22 | {error} 23 | ) : ( 24 | 25 | {products.map((product) => ( 26 | 27 | 28 | 29 | ))} 30 | 31 | )} 32 | 33 | ) 34 | } 35 | 36 | export default CategoryScreen 37 | -------------------------------------------------------------------------------- /frontend/src/constants/orderConstants.js: -------------------------------------------------------------------------------- 1 | export const ORDER_CREATE_REQUEST = 'ORDER_CREATE_REQUEST' 2 | export const ORDER_CREATE_SUCCESS = 'ORDER_CREATE_SUCCESS' 3 | export const ORDER_CREATE_FAIL = 'ORDER_CREATE_FAIL' 4 | 5 | export const ORDER_DETAILS_REQUEST = 'ORDER_DETAILS_REQUEST' 6 | export const ORDER_DETAILS_SUCCESS = 'ORDER_DETAILS_SUCCESS' 7 | export const ORDER_DETAILS_FAIL = 'ORDER_DETAILS_FAIL' 8 | 9 | export const ORDER_PAY_REQUEST = 'ORDER_PAY_REQUEST' 10 | export const ORDER_PAY_SUCCESS = 'ORDER_PAY_SUCCESS' 11 | export const ORDER_PAY_FAIL = 'ORDER_PAY_FAIL' 12 | export const ORDER_PAY_RESET = 'ORDER_PAY_RESET' 13 | 14 | export const ORDER_LIST_MY_REQUEST = 'ORDER_LIST_MY_REQUEST' 15 | export const ORDER_LIST_MY_SUCCESS = 'ORDER_LIST_MY_SUCCESS' 16 | export const ORDER_LIST_MY_FAIL = 'ORDER_LIST_MY_FAIL' 17 | export const ORDER_LIST_MY_RESET = 'ORDER_LIST_MY_RESET' 18 | 19 | export const ORDER_LIST_REQUEST = 'ORDER_LIST_REQUEST' 20 | export const ORDER_LIST_SUCCESS = 'ORDER_LIST_SUCCESS' 21 | export const ORDER_LIST_FAIL = 'ORDER_LIST_FAIL' 22 | 23 | export const ORDER_DELIVER_REQUEST = 'ORDER_DELIVER_REQUEST' 24 | export const ORDER_DELIVER_SUCCESS = 'ORDER_DELIVER_SUCCESS' 25 | export const ORDER_DELIVER_FAIL = 'ORDER_DELIVER_FAIL' 26 | export const ORDER_DELIVER_RESET = 'ORDER_DELIVER_RESET' 27 | -------------------------------------------------------------------------------- /frontend/src/screens/SearchScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Col, Row } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 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 | 9 | const SearchScreen = ({ match, history }) => { 10 | const keyword = match.params.keyword 11 | const dispatch = useDispatch() 12 | const productList = useSelector((state) => state.productListBySearch) 13 | const { loading, error, products } = productList 14 | useEffect(() => { 15 | dispatch(listProducts(keyword)) 16 | }, [dispatch, match, keyword]) 17 | return ( 18 | <> 19 |

Results based on your search

20 | {loading ? ( 21 | 22 | ) : error ? ( 23 | {error} 24 | ) : ( 25 | 26 | {/* {!products && No products found based on your search} */} 27 | {products.map((product) => ( 28 | 29 | 30 | 31 | ))} 32 | 33 | )} 34 | 35 | ) 36 | } 37 | 38 | export default SearchScreen 39 | -------------------------------------------------------------------------------- /frontend/src/reducers/cartReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CART_ADD_ITEM, 3 | CART_REMOVE_ITEM, 4 | CART_SAVE_PAYMENT_METHOD, 5 | CART_SAVE_SHIPPING_ADDRESS, 6 | } from '../constants/cartConstants' 7 | 8 | export const cartReducer = ( 9 | state = { cartItems: [], shippingAddress: {} }, 10 | action, 11 | ) => { 12 | switch (action.type) { 13 | case CART_ADD_ITEM: 14 | const item = action.payload 15 | const existItem = state.cartItems.find((x) => x.product === item.product) 16 | if (existItem) { 17 | return { 18 | ...state, 19 | cartItems: state.cartItems.map((x) => 20 | x.product === existItem.product ? item : x, 21 | ), 22 | } 23 | } else { 24 | return { 25 | ...state, 26 | cartItems: [...state.cartItems, item], 27 | } 28 | } 29 | 30 | case CART_REMOVE_ITEM: 31 | return { 32 | ...state, 33 | cartItems: state.cartItems.filter( 34 | (item) => item.product !== action.payload, 35 | ), 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 | 50 | default: 51 | return state 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/orderControllers.test.js: -------------------------------------------------------------------------------- 1 | const Order = require('../backend/models/orderModel') 2 | const app = require('../backend/server') 3 | const Product = require('../backend/models/productModel') 4 | const chai = require('chai') 5 | const chaiHttp = require('chai-http') 6 | const expect = chai.expect 7 | chai.use(chaiHttp) 8 | chai.should() 9 | describe('ORDER TESTS', () => { 10 | beforeEach((done) => { 11 | Order.deleteMany({}, (err) => { 12 | done() 13 | }) 14 | }) 15 | describe('/POST /api/order', () => { 16 | it('should create order', (done) => { 17 | let order = new Order({ 18 | orderItems: [ 19 | { 20 | name: 'onions', 21 | qty: '1', 22 | imageSrc: 'hjldehde', 23 | price: '20', 24 | product: 1, 25 | }, 26 | ], 27 | user: 1, 28 | shippingAddress: { 29 | address: 'test', 30 | pinCode: 2, 31 | city: 'Nairobi', 32 | state: 'Narok', 33 | country: 'Kenya', 34 | }, 35 | paymentMethod: 'wtf', 36 | itemsPrice: 2, 37 | shippingPrice: 4, 38 | totalPrice: 23, 39 | }) 40 | chai 41 | .request(app) 42 | .post('/api/orders') 43 | .send(order) 44 | .end((err, res) => { 45 | res.should.have.a('object') 46 | done() 47 | }) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /frontend/src/actions/cartActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | CART_ADD_ITEM, 4 | CART_REMOVE_ITEM, 5 | CART_SAVE_PAYMENT_METHOD, 6 | CART_SAVE_SHIPPING_ADDRESS, 7 | } from '../constants/cartConstants' 8 | 9 | export const addToCart = (id, qty) => async (dispatch, getState) => { 10 | const { data } = await axios.get(`/api/products/${id}`) 11 | dispatch({ 12 | type: CART_ADD_ITEM, 13 | payload: { 14 | product: data._id, 15 | name: data.name, 16 | imageSrc: data.imageSrc, 17 | price: data.price, 18 | countInStock: data.countInStock, 19 | qty, 20 | }, 21 | }) 22 | 23 | localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems)) 24 | } 25 | 26 | export const removeFromCart = (id) => (dispatch, getState) => { 27 | dispatch({ 28 | type: CART_REMOVE_ITEM, 29 | payload: id, 30 | }) 31 | localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems)) 32 | } 33 | 34 | export const saveShippingAddress = (data) => (dispatch) => { 35 | dispatch({ 36 | type: CART_SAVE_SHIPPING_ADDRESS, 37 | payload: data, 38 | }) 39 | 40 | localStorage.setItem('shippingAddress', JSON.stringify(data)) 41 | } 42 | 43 | export const savePaymentMethod = (data) => (dispatch) => { 44 | dispatch({ 45 | type: CART_SAVE_PAYMENT_METHOD, 46 | payload: data, 47 | }) 48 | 49 | localStorage.setItem('paymentMethod', JSON.stringify(data)) 50 | } 51 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "proxy": "http://127.0.0.1:5000", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.14.1", 8 | "@testing-library/react": "^11.2.7", 9 | "@testing-library/user-event": "^12.8.3", 10 | "axios": "^0.22.0", 11 | "react": "^17.0.2", 12 | "react-bootstrap": "^1.6.3", 13 | "react-dom": "^17.0.2", 14 | "react-helmet": "^6.1.0", 15 | "react-icons": "^4.3.1", 16 | "react-redux": "^7.2.5", 17 | "react-router-bootstrap": "^0.25.0", 18 | "react-router-dom": "^5.3.0", 19 | "react-scripts": "5.0.1", 20 | "redux": "^4.1.1", 21 | "redux-devtools-extension": "^2.13.9", 22 | "redux-thunk": "^2.3.0", 23 | "swiper": "^7.0.7", 24 | "web-vitals": "^1.1.2" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "purgecss": "^4.0.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { Col, Row } from 'react-bootstrap' 4 | import Category from '../components/Category' 5 | import { listCategories } from '../actions/categoryActions' 6 | import Loader from '../components/Loader' 7 | import Message from '../components/Message' 8 | import Slider from '../components/Slider' 9 | 10 | const HomeScreen = () => { 11 | const dispatch = useDispatch() 12 | const categoryList = useSelector((state) => state.categoryList) 13 | const { loading, error, categories } = categoryList 14 | useEffect(() => { 15 | dispatch(listCategories()) 16 | }, [dispatch]) 17 | return ( 18 | <> 19 | 20 |
21 |

Shop by category

22 | {loading ? ( 23 | 24 | ) : error ? ( 25 | {error} 26 | ) : ( 27 | 28 | {categories.map((category) => ( 29 | 37 | 38 | 39 | ))} 40 | 41 | )} 42 |
43 | 44 | ) 45 | } 46 | 47 | export default HomeScreen 48 | -------------------------------------------------------------------------------- /frontend/src/components/Slider.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Carousel, Image } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { listCarousels } from '../actions/carouselActions' 5 | import Loader from '../components/Loader' 6 | import Message from '../components/Message' 7 | 8 | const Slider = () => { 9 | const dispatch = useDispatch() 10 | const carouselList = useSelector((state) => state.carouselList) 11 | const { loading, carousels, error } = carouselList 12 | useEffect(() => { 13 | dispatch(listCarousels()) 14 | }, [dispatch]) 15 | return ( 16 | <> 17 | {loading ? ( 18 | 19 | ) : error ? ( 20 | {error} 21 | ) : ( 22 | 31 | {carousels.map((carousel) => ( 32 | 37 | {carousel.imageAlt} 43 | 44 | ))} 45 | 46 | )} 47 | 48 | ) 49 | } 50 | 51 | export default Slider 52 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const bcrypt = require('bcryptjs') 3 | const crypto = require('crypto') 4 | 5 | const userSchema = mongoose.Schema( 6 | { 7 | name: { 8 | type: String, 9 | required: true, 10 | }, 11 | email: { 12 | type: String, 13 | unique: true, 14 | required: true, 15 | }, 16 | password: { 17 | type: String, 18 | required: true, 19 | }, 20 | isAdmin: { 21 | type: Boolean, 22 | required: true, 23 | default: false, 24 | }, 25 | passwordResetToken: { 26 | type: String, 27 | }, 28 | passwordResetExpires: { 29 | type: Date, 30 | }, 31 | }, 32 | { 33 | timestamps: true, 34 | }, 35 | ) 36 | 37 | userSchema.methods.matchPassword = async function (enteredPassword) { 38 | return await bcrypt.compare(enteredPassword, this.password) 39 | } 40 | 41 | userSchema.methods.createPasswordResetToken = function () { 42 | const resetToken = crypto.randomBytes(32).toString('hex') 43 | this.passwordResetToken = crypto 44 | .createHash('sha256') 45 | .update(resetToken) 46 | .digest('hex') 47 | this.passwordResetExpires = Date.now() + 10 * 60 * 1000 48 | return resetToken 49 | } 50 | 51 | userSchema.pre('save', async function (next) { 52 | if (!this.isModified('password')) { 53 | next() 54 | } 55 | 56 | const salt = await bcrypt.genSalt(10) 57 | this.password = await bcrypt.hash(this.password, salt) 58 | }) 59 | 60 | const User = mongoose.model('User', userSchema) 61 | 62 | module.exports = User 63 | -------------------------------------------------------------------------------- /backend/models/productModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const productSchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: 'User', 9 | }, 10 | name: { 11 | type: String, 12 | required: [true, 'Please Enter Name of the Product'], 13 | }, 14 | imageSrc: { 15 | type: String, 16 | required: [true, 'Please Upload an Image File'], 17 | }, 18 | imageAlt: { 19 | type: String, 20 | required: [true, 'Please Enter Image Alt'], 21 | }, 22 | category: { 23 | type: String, 24 | required: [true, 'Please Enter Category of the Product'], 25 | }, 26 | description: { 27 | type: String, 28 | required: [true, 'Please Enter Description of the Product'], 29 | }, 30 | price: { 31 | type: Number, 32 | required: [true, 'Please Enter Price of Product'], 33 | default: 0, 34 | min: [0, 'Price should be greater than or equal to 0'], 35 | }, 36 | mrp: { 37 | type: Number, 38 | required: [true, 'Please Enter MRP of the Product'], 39 | default: 0, 40 | min: [0, 'MRP should be greater than or equal to 0'], 41 | }, 42 | countInStock: { 43 | type: Number, 44 | required: [true, 'Please Enter Stock of the Product'], 45 | default: 0, 46 | min: [0, 'Count In Stock value should be greater than or equal to 0'], 47 | }, 48 | }, 49 | { 50 | timestamps: true, 51 | }, 52 | ) 53 | 54 | const Product = mongoose.model('Product', productSchema) 55 | 56 | module.exports = Product 57 | -------------------------------------------------------------------------------- /frontend/src/constants/productConstants.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = 'PRODUCT_LIST_REQUEST' 2 | export const PRODUCT_LIST_SUCCESS = 'PRODUCT_LIST_SUCCESS' 3 | export const PRODUCT_LIST_FAIL = 'PRODUCT_LIST_FAIL' 4 | 5 | export const PRODUCT_LIST_BY_CATEGORY_REQUEST = 6 | 'PRODUCT_LIST_BY_CATEGORY_REQUEST' 7 | export const PRODUCT_LIST_BY_CATEGORY_SUCCESS = 8 | 'PRODUCT_LIST_BY_CATEGORY_SUCCESS' 9 | export const PRODUCT_LIST_BY_CATEGORY_FAIL = 'PRODUCT_LIST_BY_CATEGORY_FAIL' 10 | 11 | export const PRODUCT_LIST_BY_SEARCH_REQUEST = 'PRODUCT_LIST_BY_SEARCH_REQUEST' 12 | export const PRODUCT_LIST_BY_SEARCH_SUCCESS = 'PRODUCT_LIST_BY_SEARCH_SUCCESS' 13 | export const PRODUCT_LIST_BY_SEARCH_FAIL = 'PRODUCT_LIST_BY_SEARCH_FAIL' 14 | 15 | export const PRODUCT_DETAILS_REQUEST = 'PRODUCT_DETAILS_REQUEST' 16 | export const PRODUCT_DETAILS_SUCCESS = 'PRODUCT_DETAILS_SUCCESS' 17 | export const PRODUCT_DETAILS_FAIL = 'PRODUCT_DETAILS_FAIL' 18 | export const PRODUCT_DETAILS_RESET = 'PRODUCT_DETAILS_RESET' 19 | 20 | export const PRODUCT_DELETE_REQUEST = 'PRODUCT_DELETE_REQUEST' 21 | export const PRODUCT_DELETE_SUCCESS = 'PRODUCT_DELETE_SUCCESS' 22 | export const PRODUCT_DELETE_FAIL = 'PRODUCT_DELETE_FAIL' 23 | 24 | export const PRODUCT_CREATE_REQUEST = 'PRODUCT_CREATE_REQUEST' 25 | export const PRODUCT_CREATE_SUCCESS = 'PRODUCT_CREATE_SUCCESS' 26 | export const PRODUCT_CREATE_FAIL = 'PRODUCT_CREATE_FAIL' 27 | export const PRODUCT_CREATE_RESET = 'PRODUCT_CREATE_RESET' 28 | 29 | export const PRODUCT_UPDATE_REQUEST = 'PRODUCT_UPDATE_REQUEST' 30 | export const PRODUCT_UPDATE_SUCCESS = 'PRODUCT_UPDATE_SUCCESS' 31 | export const PRODUCT_UPDATE_FAIL = 'PRODUCT_UPDATE_FAIL' 32 | export const PRODUCT_UPDATE_RESET = 'PRODUCT_UPDATE_RESET' 33 | -------------------------------------------------------------------------------- /frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container, Row, Col, Nav } from 'react-bootstrap' 3 | import { Link } from 'react-router-dom' 4 | 5 | const Footer = () => { 6 | return ( 7 |
8 | 9 | 10 | 11 |
    12 |
  • 13 | 14 | Returns 15 | 16 |
  • 17 |
  • 18 | 19 | Terms 20 | 21 |
  • 22 |
  • 23 | 24 | Privacy 25 | 26 |
  • 27 |
  • 28 | 29 | Help 30 | 31 |
  • 32 |
  • 33 | 38 | GitHub 39 | 40 |
  • 41 |
42 | 43 | 44 | Copyright © 2022 Freshbey 45 | 46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | export default Footer 53 | -------------------------------------------------------------------------------- /frontend/src/screens/ForgotPasswordScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Button, Form } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { forgotPassword } from '../actions/userActions' 5 | import FormContainer from '../components/FormContainer' 6 | import Message from '../components/Message' 7 | import Loader from '../components/Loader' 8 | 9 | const ForgotPasswordScreen = ({ match }) => { 10 | const [email, setEmail] = useState('') 11 | const dispatch = useDispatch() 12 | 13 | const { loading, message, error } = useSelector( 14 | (state) => state.userForgotPassword, 15 | ) 16 | 17 | useEffect(() => { 18 | if (match.params.email) { 19 | setEmail(match.params.email) 20 | } 21 | }, [match]) 22 | 23 | const submitHandler = (e) => { 24 | e.preventDefault() 25 | dispatch(forgotPassword(email)) 26 | } 27 | 28 | return ( 29 | 30 |

Password assistance

31 |

Enter the email address associated with your account.

32 | {error && {error}} 33 | {message && {message}} 34 | {loading && } 35 | 36 |
37 | 38 | Email Address 39 | setEmail(e.target.value)} 45 | > 46 | 47 | 50 |
51 |
52 | ) 53 | } 54 | 55 | export default ForgotPasswordScreen 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freshbey", 3 | "version": "0.1.0", 4 | "description": "An online grocery ecommerce store", 5 | "main": "server.js", 6 | "engines": { 7 | "node": "14.16.1", 8 | "npm": "7.24.1" 9 | }, 10 | "scripts": { 11 | "test": "mocha ***/*.test.js --timeout 50000", 12 | "test-watch": "nodemon --exec \"npm test\"", 13 | "prettier": "prettier --write **/*.js", 14 | "start": "node backend/server", 15 | "server": "nodemon backend/server", 16 | "client": "npm start --prefix frontend", 17 | "dev": "concurrently npm start \"npm run server\" \"npm run client\"", 18 | "data:import": "node backend/seeder", 19 | "data:destroy": "node backend/seeder -d", 20 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix frontend && npm run build --prefix frontend" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/roopeshsn/freshbey.git" 25 | }, 26 | "author": "Roopesh Saravanan", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/roopeshsn/freshbey/issues" 30 | }, 31 | "homepage": "https://github.com/roopeshsn/freshbey#readme", 32 | "dependencies": { 33 | "bcryptjs": "^2.4.3", 34 | "cloudinary": "^1.32.0", 35 | "compression": "^1.7.4", 36 | "cookie-parser": "^1.4.6", 37 | "cors": "^2.8.5", 38 | "dotenv": "^10.0.0", 39 | "express": "^4.17.3", 40 | "express-async-handler": "^1.1.4", 41 | "jsonwebtoken": "^9.0.0", 42 | "mocha-junit-reporter": "^2.0.2", 43 | "mongoose": "^6.4.6", 44 | "multer": "^1.4.3", 45 | "multer-storage-cloudinary": "^4.0.0", 46 | "nodemailer": "^6.6.5" 47 | }, 48 | "devDependencies": { 49 | "chai": "^4.3.6", 50 | "chai-http": "^4.3.0", 51 | "concurrently": "^6.3.0", 52 | "mocha": "^9.2.2", 53 | "nodemon": "^2.0.20", 54 | "prettier": "2.7.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /backend/controllers/carouselControllers.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = require('express-async-handler') 2 | const Carousel = require('../models/carouselModel') 3 | 4 | // @desc Fetch all carousels 5 | // @route GET /api/carousels 6 | // @access Public 7 | const getCarousels = asyncHandler(async (req, res) => { 8 | const carousels = await Carousel.find({}) 9 | if (carousels) { 10 | res.json(carousels) 11 | } else { 12 | res.status(404) 13 | throw new Error('No carousels found') 14 | } 15 | }) 16 | 17 | // @desc Fetch single carousel 18 | // @route GET /api/carousels/:id 19 | // @access Public 20 | const getCarouselById = asyncHandler(async (req, res) => { 21 | const carousel = await Carousel.findById(req.params.id) 22 | if (carousel) { 23 | res.json(carousel) 24 | } else { 25 | res.status(404) 26 | throw new Error('Carousel not found') 27 | } 28 | }) 29 | 30 | // @desc Create a carousel 31 | // @route POST /api/carousels 32 | // @access Private/Admin 33 | const createCarousel = asyncHandler(async (req, res) => { 34 | const { imageSrc, imageAlt } = req.body 35 | const carousel = new Carousel({ 36 | user: req.user._id, 37 | imageSrc, 38 | imageAlt, 39 | }) 40 | 41 | const createdCarousel = await carousel.save() 42 | res.status(201).json(createdCarousel) 43 | }) 44 | 45 | // @desc Update a carousel 46 | // @route PUT /api/carousels/:id 47 | // @access Private/Admin 48 | const updateCarousel = asyncHandler(async (req, res) => { 49 | const { imageSrc, imageAlt } = req.body 50 | 51 | const carousel = await Carousel.findById(req.params.id) 52 | 53 | if (carousel) { 54 | carousel.imageSrc = imageSrc 55 | carousel.imageAlt = imageAlt 56 | 57 | const updatedCarousel = await carousel.save() 58 | res.json(updatedCarousel) 59 | } else { 60 | res.status(404) 61 | throw new Error('Carousel not found') 62 | } 63 | }) 64 | 65 | module.exports = { 66 | getCarousels, 67 | getCarouselById, 68 | createCarousel, 69 | updateCarousel, 70 | } 71 | -------------------------------------------------------------------------------- /backend/seeder.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv') 2 | const users = require('./data/users') 3 | const products = require('./data/products') 4 | const categories = require('./data/categories') 5 | const carousels = require('./data/carousels') 6 | const User = require('./models/userModel') 7 | const Carousel = require('./models/carouselModel') 8 | const Category = require('./models/categoryModel') 9 | const Product = require('./models/productModel') 10 | const Order = require('./models/orderModel') 11 | const { connectDB } = require('./config/db') 12 | 13 | dotenv.config() 14 | 15 | connectDB() 16 | 17 | const importData = async () => { 18 | try { 19 | await Order.deleteMany() 20 | await Product.deleteMany() 21 | await Category.deleteMany() 22 | await Carousel.deleteMany() 23 | await User.deleteMany() 24 | const createdUsers = await User.insertMany(users) 25 | const adminUser = createdUsers[0]._id 26 | const sampleCategories = categories.map((category) => { 27 | return { ...category, user: adminUser } 28 | }) 29 | const sampleCarousels = carousels.map((carousel) => { 30 | return { ...carousel, user: adminUser } 31 | }) 32 | const sampleProducts = products.map((product) => { 33 | return { ...product, user: adminUser } 34 | }) 35 | await Carousel.insertMany(sampleCarousels) 36 | await Category.insertMany(sampleCategories) 37 | await Product.insertMany(sampleProducts) 38 | console.log('Data Imported!') 39 | process.exit() 40 | } catch (error) { 41 | console.error(error) 42 | process.exit(1) 43 | } 44 | } 45 | 46 | const destroyData = async () => { 47 | try { 48 | await Order.deleteMany() 49 | await Product.deleteMany() 50 | await Category.deleteMany() 51 | await Carousel.deleteMany() 52 | await User.deleteMany() 53 | console.log('Data Destroyed!') 54 | process.exit() 55 | } catch (error) { 56 | console.error(error) 57 | process.exit(1) 58 | } 59 | } 60 | 61 | if (process.argv[2] === '-d') { 62 | destroyData() 63 | } else { 64 | importData() 65 | } 66 | -------------------------------------------------------------------------------- /frontend/src/constants/userConstants.js: -------------------------------------------------------------------------------- 1 | export const USER_LOGIN_REQUEST = 'USER_LOGIN_REQUEST' 2 | export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS' 3 | export const USER_LOGIN_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_FORGOT_PASSWORD_REQUEST = 'USER_FORGOT_PASSWORD_REQUEST' 11 | export const USER_FORGOT_PASSWORD_SUCCESS = 'USER_FORGOT_PASSWORD_SUCCESS' 12 | export const USER_FORGOT_PASSWORD_FAIL = 'USER_FORGOT_PASSWORD_FAIL' 13 | 14 | export const USER_RESET_PASSWORD_REQUEST = 'USER_RESET_PASSWORD_REQUEST' 15 | export const USER_RESET_PASSWORD_SUCCESS = 'USER_RESET_PASSWORD_SUCCESS' 16 | export const USER_RESET_PASSWORD_FAIL = 'USER_RESET_PASSWORD_FAIL' 17 | 18 | export const USER_DETAILS_REQUEST = 'USER_DETAILS_REQUEST' 19 | export const USER_DETAILS_SUCCESS = 'USER_DETAILS_SUCCESS' 20 | export const USER_DETAILS_FAIL = 'USER_DETAILS_FAIL' 21 | export const USER_DETAILS_RESET = 'USER_DETAILS_RESET' 22 | 23 | export const USER_UPDATE_PROFILE_REQUEST = 'USER_UPDATE_PROFILE_REQUEST' 24 | export const USER_UPDATE_PROFILE_SUCCESS = 'USER_UPDATE_PROFILE_SUCCESS' 25 | export const USER_UPDATE_PROFILE_FAIL = 'USER_UPDATE_PROFILE_FAIL' 26 | export const USER_UPDATE_PROFILE_RESET = 'USER_UPDATE_PROFILE_RESET' 27 | 28 | export const USERS_LIST_REQUEST = 'USERS_LIST_REQUEST' 29 | export const USERS_LIST_SUCCESS = 'USERS_LIST_SUCCESS' 30 | export const USERS_LIST_FAIL = 'USERS_LIST_FAIL' 31 | export const USERS_LIST_RESET = 'USERS_LIST_RESET' 32 | 33 | export const USER_DELETE_REQUEST = 'USER_DELETE_REQUEST' 34 | export const USER_DELETE_SUCCESS = 'USER_DELETE_SUCCESS' 35 | export const USER_DELETE_FAIL = 'USER_DELETE_FAIL' 36 | 37 | export const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST' 38 | export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS' 39 | export const USER_UPDATE_FAIL = 'USER_UPDATE_FAIL' 40 | export const USER_UPDATE_RESET = 'USER_UPDATE_RESET' 41 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const express = require('express') 3 | const cors = require('cors') 4 | require('dotenv').config() 5 | const cookieParser = require('cookie-parser') 6 | const { connectDB } = require('./config/db') 7 | const productRoutes = require('./routes/productRoutes') 8 | const userRoutes = require('./routes/userRoutes') 9 | const orderRoutes = require('./routes/orderRoutes') 10 | const uploadRoutes = require('./routes/uploadRoutes') 11 | const categoryRoutes = require('./routes/categoryRoutes') 12 | const carouselRoutes = require('./routes/carouselRoutes') 13 | const { notFound, errorHandler } = require('./middleware/errorMiddleware') 14 | const compression = require('compression') 15 | 16 | const app = express() 17 | app.use(cookieParser()) 18 | app.use( 19 | cors({ 20 | origin: ['http://localhost:3000', 'https://freshbey.herokuapp.com'], 21 | credentials: true, 22 | }), 23 | ) 24 | const PORT = process.env.PORT || 5000 25 | 26 | // DB connection 27 | connectDB() 28 | 29 | app.use(compression()) 30 | app.use(express.json()) 31 | app.use('/api/carousels', carouselRoutes) 32 | app.use('/api/categories', categoryRoutes) 33 | app.use('/api/products', productRoutes) 34 | app.use('/api/users', userRoutes) 35 | app.use('/api/orders', orderRoutes) 36 | app.use('/api/upload', uploadRoutes) 37 | 38 | if (process.env.NODE_ENV === 'production') { 39 | app.use(express.static(path.join(__dirname, '../frontend/build'))) 40 | 41 | app.get('*', (req, res) => 42 | res.sendFile( 43 | path.resolve(__dirname, '..', 'frontend', 'build', 'index.html'), 44 | ), 45 | ) 46 | } else { 47 | app.get(`/`, (req, res) => { 48 | let greetings = { 49 | message: 'Welcome to Freshbey Backend', 50 | version: process.env.VERSION, 51 | license: 'MIT', 52 | } 53 | res.send(greetings).status(200) 54 | console.log(greetings) 55 | }) 56 | } 57 | 58 | app.use(notFound) 59 | app.use(errorHandler) 60 | 61 | app.listen( 62 | PORT, 63 | console.log(`${process.env.NODE_ENV} Server Started on PORT: ${PORT}`), 64 | ) 65 | 66 | module.exports = app 67 | -------------------------------------------------------------------------------- /backend/models/orderModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('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: String, required: true }, 14 | imageSrc: { type: String, required: true }, 15 | price: { type: String, 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 | pinCode: { type: String, required: true }, // PinCode type is String (because: Different Countries uses PinCode as AlphaNumeric or Numeric as well - anujverma-eng) 26 | city: { type: String, required: true }, 27 | state: { type: String, required: true }, 28 | country: { type: String, required: true }, 29 | }, 30 | paymentMethod: { 31 | type: String, 32 | required: true, 33 | }, 34 | paymentResult: { 35 | id: { type: String }, 36 | status: { type: String }, 37 | update_time: { type: String }, 38 | email_address: { type: String }, 39 | }, 40 | itemsPrice: { 41 | type: Number, 42 | required: true, 43 | default: 0.0, 44 | }, 45 | shippingPrice: { 46 | type: Number, 47 | required: true, 48 | default: 0.0, 49 | }, 50 | totalPrice: { 51 | type: Number, 52 | required: true, 53 | default: 0.0, 54 | }, 55 | isPaid: { 56 | type: Boolean, 57 | required: true, 58 | default: false, 59 | }, 60 | paidAt: { 61 | type: Date, 62 | }, 63 | isDelivered: { 64 | type: Boolean, 65 | required: true, 66 | default: false, 67 | }, 68 | deliveredAt: { 69 | type: Date, 70 | }, 71 | }, 72 | { 73 | timestamps: true, 74 | }, 75 | ) 76 | 77 | const Order = mongoose.model('Order', orderSchema) 78 | 79 | module.exports = Order 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | The following is a set of guidelines for contributing to Conception and its packages, which are hosted on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | To begin, please check the Issues tab to see where you can contribute. 8 | 9 | ## Issue Label/Tagging Breakdown 10 | 11 | Checking the open issues is a great place to see what's currently being worked on and also what's available to work on. Labels are used to group different issues by status, type, and difficulty. The main labels you should be aware of are: 12 | 13 | - react 14 | - bug 15 | - enhancement 16 | - good first issue 17 | - code level easy 18 | - code level medium 19 | - code level hard 20 | - in progress 21 | - ready to work on 22 | - discussion 23 | 24 | **To claim an issue to work on please write a comment on the issue letting me know you'd like to take it on. Once the issue has been claimed the 'in progress' label will be added.** 25 | 26 | If something doesn't make sense please ask for clarification or assistance by writing a comment on the issue. 27 | 28 | ## Pull Requests 29 | 30 | Before submitting a PR that relates directly to an issue, please write a comment on the relevant issue letting me know you'd like to take it on. Once the issue has been claimed the 'in progress' label will be added. After long periods of inactivity, I'll remove the 'in progress' label so someone else can work on it. 31 | 32 | If you've never submitted a pull request before then I would recommend you check out this guide: [Step-by-step guide to contributing on GitHub](https://www.dataschool.io/how-to-contribute-on-github/) 33 | 34 | Don't panic if you feel as though your code isn't quite up-to-scratch. This is an opportunity for you to learn and to receive feedback. If your code needs some work then please see this as a positive experience and opportunity to improve. 35 | 36 | Pull requests big or small will be accepted, however, if you are submitting a major feature then please make sure you create an issue for discussion before attempting this work, likewise if you spot a bug please raise a new issue before creating a pull request. 37 | -------------------------------------------------------------------------------- /frontend/src/screens/PaymentScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Form, Button, Col } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import FormContainer from '../components/FormContainer' 5 | import CheckoutSteps from '../components/CheckoutSteps' 6 | import { savePaymentMethod } from '../actions/cartActions' 7 | 8 | const PaymentScreen = ({ history }) => { 9 | const cart = useSelector((state) => state.cart) 10 | const { shippingAddress } = cart 11 | 12 | if (!shippingAddress.address) { 13 | history.push('/shipping') 14 | } 15 | 16 | const [paymentMethod, setPaymentMethod] = useState('COD') 17 | 18 | const dispatch = useDispatch() 19 | 20 | const submitHandler = (e) => { 21 | e.preventDefault() 22 | dispatch(savePaymentMethod(paymentMethod)) 23 | history.push('/placeorder') 24 | } 25 | 26 | return ( 27 | 28 | 29 |

Payment Method

30 |
31 | 32 | Select Method 33 | 34 | setPaymentMethod(e.target.value)} 42 | > 43 | setPaymentMethod(e.target.value)} 51 | > 52 | setPaymentMethod(e.target.value)} 59 | > 60 | 61 | 62 | 63 | 66 |
67 |
68 | ) 69 | } 70 | 71 | export default PaymentScreen 72 | -------------------------------------------------------------------------------- /frontend/src/screens/CreateCarouselScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Button, Form } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { Link } from 'react-router-dom' 5 | import { createCarousel } from '../actions/carouselActions' 6 | import FormContainer from '../components/FormContainer' 7 | import Loader from '../components/Loader' 8 | import Message from '../components/Message' 9 | 10 | const CreateCarouselScreen = ({ history }) => { 11 | const [imageSrc, setImageSrc] = useState('') 12 | const [imageAlt, setImageAlt] = useState('') 13 | 14 | const dispatch = useDispatch() 15 | 16 | const carouselCreate = useSelector((state) => state.carouselCreate) 17 | const { loading, error, success } = carouselCreate 18 | 19 | useEffect(() => { 20 | if (success) { 21 | history.push('/admin/carousellist') 22 | } 23 | }, [success, history]) 24 | 25 | const submitHandler = (e) => { 26 | e.preventDefault() 27 | dispatch( 28 | createCarousel({ 29 | imageSrc, 30 | imageAlt, 31 | }), 32 | ) 33 | } 34 | 35 | return ( 36 | <> 37 | 38 |

Create Carousel

39 | {loading && } 40 | {error && {error}} 41 | 42 |
43 | 44 | Image Src 45 | setImageSrc(e.target.value)} 50 | > 51 | 52 | 53 | 54 | Image Alt 55 | setImageAlt(e.target.value)} 60 | > 61 | 62 | 63 | 66 | 67 | Go Back 68 | 69 |
70 |
71 | 72 | ) 73 | } 74 | 75 | export default CreateCarouselScreen 76 | -------------------------------------------------------------------------------- /frontend/src/reducers/carouselReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CAROUSEL_LIST_REQUEST, 3 | CAROUSEL_LIST_SUCCESS, 4 | CAROUSEL_LIST_FAIL, 5 | CAROUSEL_DETAILS_REQUEST, 6 | CAROUSEL_DETAILS_SUCCESS, 7 | CAROUSEL_DETAILS_FAIL, 8 | CAROUSEL_DETAILS_RESET, 9 | CAROUSEL_UPDATE_REQUEST, 10 | CAROUSEL_UPDATE_SUCCESS, 11 | CAROUSEL_UPDATE_FAIL, 12 | CAROUSEL_UPDATE_RESET, 13 | CAROUSEL_CREATE_REQUEST, 14 | CAROUSEL_CREATE_SUCCESS, 15 | CAROUSEL_CREATE_FAIL, 16 | CAROUSEL_CREATE_RESET, 17 | } from '../constants/carouselConstants' 18 | 19 | export const carouselListReducer = (state = { carousels: [] }, action) => { 20 | switch (action.type) { 21 | case CAROUSEL_LIST_REQUEST: 22 | return { loading: true, carousels: [] } 23 | case CAROUSEL_LIST_SUCCESS: 24 | return { loading: false, carousels: action.payload } 25 | case CAROUSEL_LIST_FAIL: 26 | return { loading: false, error: action.payload } 27 | default: 28 | return state 29 | } 30 | } 31 | 32 | export const carouselDetailsReducer = (state = { carousel: {} }, action) => { 33 | switch (action.type) { 34 | case CAROUSEL_DETAILS_REQUEST: 35 | return { loading: true, ...state } 36 | case CAROUSEL_DETAILS_SUCCESS: 37 | return { loading: false, carousel: action.payload } 38 | case CAROUSEL_DETAILS_FAIL: 39 | return { loading: false, error: action.payload } 40 | case CAROUSEL_DETAILS_RESET: 41 | return {} 42 | default: 43 | return state 44 | } 45 | } 46 | 47 | export const carouselCreateReducer = (state = { carousel: {} }, action) => { 48 | switch (action.type) { 49 | case CAROUSEL_CREATE_REQUEST: 50 | return { loading: true } 51 | case CAROUSEL_CREATE_SUCCESS: 52 | return { loading: false, success: true, carousel: action.payload } 53 | case CAROUSEL_CREATE_FAIL: 54 | return { loading: false, error: action.payload } 55 | case CAROUSEL_CREATE_RESET: 56 | return {} 57 | default: 58 | return state 59 | } 60 | } 61 | 62 | export const carouselUpdateReducer = (state = { carousel: {} }, action) => { 63 | switch (action.type) { 64 | case CAROUSEL_UPDATE_REQUEST: 65 | return { loading: true } 66 | case CAROUSEL_UPDATE_SUCCESS: 67 | return { loading: false, success: true, carousel: action.payload } 68 | case CAROUSEL_UPDATE_FAIL: 69 | return { loading: false, error: action.payload } 70 | case CAROUSEL_UPDATE_RESET: 71 | return { carousel: {} } 72 | default: 73 | return state 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/screens/ResetPasswordScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Button, Form } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { Link } from 'react-router-dom' 5 | import { resetPassword } from '../actions/userActions' 6 | import FormContainer from '../components/FormContainer' 7 | import Loader from '../components/Loader' 8 | import Message from '../components/Message' 9 | 10 | const ResetPasswordScreen = ({ match }) => { 11 | const [password, setPassword] = useState('') 12 | const [confirmPassword, setConfirmPassword] = useState('') 13 | const [passwordErrorMessage, setPasswordErrorMessage] = useState(null) 14 | 15 | const dispatch = useDispatch() 16 | 17 | const submitHandler = (e) => { 18 | e.preventDefault() 19 | const token = match.params.token 20 | if (password !== confirmPassword) { 21 | setPasswordErrorMessage('Passwords do not match') 22 | } else { 23 | setPasswordErrorMessage(null) 24 | dispatch(resetPassword(token, password)) 25 | } 26 | } 27 | 28 | const { loading, message, error } = useSelector( 29 | (state) => state.userResetPassword, 30 | ) 31 | 32 | return ( 33 | 34 |

Reset Password

35 | {passwordErrorMessage && ( 36 | {passwordErrorMessage} 37 | )} 38 | {message && {message}} 39 | {message && Click here to login} 40 | {error && error} 41 | {loading && } 42 |
43 | 44 | New password 45 | setPassword(e.target.value)} 49 | > 50 | 51 | 52 | Confirm new password 53 | setConfirmPassword(e.target.value)} 57 | > 58 | 59 | 62 |
63 |
64 | ) 65 | } 66 | 67 | export default ResetPasswordScreen 68 | -------------------------------------------------------------------------------- /frontend/src/screens/LoginScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Form, Button, Row, Col } from 'react-bootstrap' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | import Message from '../components/Message' 6 | import Loader from '../components/Loader' 7 | import FormContainer from '../components/FormContainer' 8 | import { login } from '../actions/userActions' 9 | 10 | const LoginScreen = ({ location, history }) => { 11 | const [email, setEmail] = useState('') 12 | const [password, setPassword] = useState('') 13 | 14 | const dispatch = useDispatch() 15 | 16 | const userLogin = useSelector((state) => state.userLogin) 17 | const { loading, error, userInfo } = userLogin 18 | 19 | const redirect = location.search ? location.search.split('=')[1] : '/' 20 | 21 | useEffect(() => { 22 | if (userInfo) { 23 | history.push(redirect) 24 | } 25 | }, [history, userInfo, redirect]) 26 | 27 | const submitHandler = (e) => { 28 | e.preventDefault() 29 | dispatch(login(email, password)) 30 | } 31 | 32 | return ( 33 | 34 |

Sign In

35 | {error && {error}} 36 | {loading && } 37 |
38 | 39 | Email Address 40 | setEmail(e.target.value)} 45 | > 46 | 47 | 48 | 49 | Password 50 | setPassword(e.target.value)} 55 | > 56 | 57 | 58 | 61 |
62 | 63 | 64 | 65 | Need help?{' '} 66 | 67 | Forgot Password 68 | 69 | 70 | 71 | New Customer?{' '} 72 | 73 | Register 74 | 75 | 76 | 77 |
78 | ) 79 | } 80 | 81 | export default LoginScreen 82 | -------------------------------------------------------------------------------- /test/productController.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../backend/server') 2 | const Product = require('../backend/models/productModel') 3 | const chai = require('chai') 4 | const chaiHttp = require('chai-http') 5 | const expect = chai.expect 6 | chai.use(chaiHttp) 7 | chai.should() 8 | 9 | describe('PRODUCT TESTS', () => { 10 | beforeEach((done) => { 11 | Product.deleteMany({}, (err) => { 12 | done() 13 | }) 14 | }) 15 | describe('/GET /api/products', () => { 16 | it('Should return all products', (done) => { 17 | chai 18 | .request(app) 19 | .get('/api/products') 20 | .end((err, res) => { 21 | res.should.have.a('object') 22 | done() 23 | }) 24 | }) 25 | }) 26 | describe('/ GET /api/products/:id', () => { 27 | it('should return product by id', (done) => { 28 | const id = 1 29 | chai 30 | .request(app) 31 | .get(`/api/products${id}`) 32 | .end((err, res) => { 33 | res.should.have.a('object') 34 | done() 35 | }) 36 | }) 37 | }) 38 | describe('/ DELETE /api/products/:id', () => { 39 | it('should return product by id', (done) => { 40 | const id = 1 41 | chai 42 | .request(app) 43 | .delete(`/api/products${id}`) 44 | .end((err, res) => { 45 | res.should.have.a('object') 46 | done() 47 | }) 48 | }) 49 | }) 50 | describe('/ POST /api/products/', () => { 51 | it('should create a product', (done) => { 52 | let products = new Product({ 53 | name: 'Banana', 54 | price: 20, 55 | mrp: 1, 56 | user: 1, 57 | imageSrc: 'jkdekde', 58 | imageAlt: 'kdieduied', 59 | category: 'kdidldil', 60 | countInStock: 2, 61 | description: 'Test sweet', 62 | }) 63 | chai 64 | .request(app) 65 | .post('/api/products') 66 | .send(products) 67 | .end((err, res) => { 68 | res.should.have.a('object') 69 | done() 70 | }) 71 | }) 72 | }) 73 | describe('/ PUT /api/products/:id', () => { 74 | it('should update a product', (done) => { 75 | let id = 1 76 | let products = new Product({ 77 | name: 'orange', 78 | price: 27, 79 | mrp: 2, 80 | user: 1, 81 | imageSrc: 'jkdekdess', 82 | imageAlt: 'kdieduiedcsxdsx', 83 | category: 'kdidldilxs', 84 | countInStock: 3, 85 | description: 'Test sweeter', 86 | }) 87 | chai 88 | .request(app) 89 | .put(`/api/products/${id}`) 90 | .send(products) 91 | .end((err, res) => { 92 | res.should.have.a('object') 93 | done() 94 | }) 95 | }) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /frontend/src/screens/CarouselListScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Button, Col, Row, Table } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { LinkContainer } from 'react-router-bootstrap' 5 | import { listCarousels } from '../actions/carouselActions' 6 | import Loader from '../components/Loader' 7 | import Message from '../components/Message' 8 | 9 | const CarouselListScreen = ({ history }) => { 10 | const dispatch = useDispatch() 11 | 12 | const carouselList = useSelector((state) => state.carouselList) 13 | const { loading, error, carousels } = carouselList 14 | 15 | const userLogin = useSelector((state) => state.userLogin) 16 | const { userInfo } = userLogin 17 | 18 | useEffect(() => { 19 | if (userInfo && userInfo.isAdmin) { 20 | dispatch(listCarousels()) 21 | } else { 22 | history.push('/login') 23 | } 24 | }, [dispatch, history, userInfo]) 25 | 26 | const createCarouselHandler = () => { 27 | history.push('/admin/createcarousel') 28 | } 29 | 30 | return ( 31 | <> 32 | 33 | 34 |

Carousels

35 | 36 | 37 | 40 | 41 |
42 | {loading ? ( 43 | 44 | ) : error ? ( 45 | {error} 46 | ) : ( 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {carousels.map((carousel) => ( 59 | 60 | 61 | 62 | 67 | 68 | 75 | 76 | ))} 77 | 78 |
IDDATEIMAGE SRCIMAGE ALTOPTIONS
{carousel._id}{carousel.createdAt.substring(0, 10)} 63 | 64 | {carousel.imageSrc} 65 | 66 | {carousel.imageAlt} 69 | 70 | 73 | 74 |
79 | )} 80 | 81 | ) 82 | } 83 | 84 | export default CarouselListScreen 85 | -------------------------------------------------------------------------------- /frontend/src/components/Product.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | import { Card, Col, Row, Button, Form } from 'react-bootstrap' 5 | import calculateDiscount from '../utils/calculateDiscount' 6 | import formatter from '../utils/currencyFormatter' 7 | import { addToCart } from '../actions/cartActions' 8 | 9 | const Product = ({ product, history }) => { 10 | const [qty, setQty] = useState(1) 11 | const dispatch = useDispatch() 12 | const [discountPercentage] = calculateDiscount(product.mrp, product.price) 13 | 14 | const addToCartHandler = () => { 15 | dispatch(addToCart(product._id, qty)) 16 | history.push('/cart') 17 | } 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | {product.name} 32 |
33 | 34 |
35 |
{formatter(product.price)}
36 |

37 | M.R.P: 38 | 39 | {formatter(product.mrp)} 40 | 41 | ({discountPercentage}% off) 42 |

43 | {product.countInStock > 0 && ( 44 | 45 | 46 | setQty(e.target.value)} 51 | > 52 | {[...Array(product.countInStock).keys()].map((x) => ( 53 | 56 | ))} 57 | 58 | 59 | 60 | 67 | 68 | 69 | )} 70 |
71 |
72 | 73 |
74 |
75 | ) 76 | } 77 | 78 | export default Product 79 | -------------------------------------------------------------------------------- /frontend/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 | import formatter from '../utils/currencyFormatter' 9 | 10 | const OrderListScreen = ({ history }) => { 11 | const dispatch = useDispatch() 12 | 13 | const orderList = useSelector((state) => state.orderList) 14 | const { loading, error, orders } = orderList 15 | 16 | const userLogin = useSelector((state) => state.userLogin) 17 | const { userInfo } = userLogin 18 | 19 | useEffect(() => { 20 | if (userInfo && userInfo.isAdmin) { 21 | dispatch(listOrders()) 22 | } else { 23 | history.push('/login') 24 | } 25 | }, [dispatch, history, userInfo]) 26 | 27 | return ( 28 | <> 29 |

Orders

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

Users

38 | {loading ? ( 39 | 40 | ) : error ? ( 41 | {error} 42 | ) : ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {users.map((user) => ( 55 | 56 | 57 | 58 | 61 | 68 | 84 | 85 | ))} 86 | 87 |
USER IDNAMEEMAILADMINOPTIONS
{user._id}{user.name} 59 | {user.email} 60 | 62 | {user.isAdmin ? ( 63 | 64 | ) : ( 65 | 66 | )} 67 | 69 | 70 | 71 | 74 | 75 | 82 | 83 |
88 | )} 89 | 90 | ) 91 | } 92 | 93 | export default UsersListScreen 94 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | import { 5 | carouselCreateReducer, 6 | carouselDetailsReducer, 7 | carouselListReducer, 8 | carouselUpdateReducer, 9 | } from './reducers/carouselReducers' 10 | import { categoryListReducer } from './reducers/categoryReducers' 11 | import { 12 | productListReducer, 13 | productDetailsReducer, 14 | productListByCategoryReducer, 15 | productDeleteReducer, 16 | productCreateReducer, 17 | productUpdateReducer, 18 | productListBySearchReducer, 19 | } from './reducers/productReducers' 20 | import { cartReducer } from './reducers/cartReducers' 21 | import { 22 | userDeleteReducer, 23 | userDetailsReducer, 24 | userForgotPasswordReducer, 25 | userLoginReducer, 26 | userRegisterReducer, 27 | userResetPasswordReducer, 28 | usersListReducer, 29 | userUpdateProfileReducer, 30 | userUpdateReducer, 31 | } from './reducers/userReducers' 32 | import { 33 | orderCreateReducer, 34 | orderDeliverReducer, 35 | orderDetailsReducer, 36 | orderListMyReducer, 37 | orderListReducer, 38 | orderPayReducer, 39 | } from './reducers/orderReducers' 40 | 41 | const reducer = combineReducers({ 42 | carouselList: carouselListReducer, 43 | carouselDetails: carouselDetailsReducer, 44 | carouselCreate: carouselCreateReducer, 45 | carouselUpdate: carouselUpdateReducer, 46 | categoryList: categoryListReducer, 47 | productList: productListReducer, 48 | productListBySearch: productListBySearchReducer, 49 | productListByCategory: productListByCategoryReducer, 50 | productDetails: productDetailsReducer, 51 | productDelete: productDeleteReducer, 52 | productCreate: productCreateReducer, 53 | productUpdate: productUpdateReducer, 54 | cart: cartReducer, 55 | userLogin: userLoginReducer, 56 | userRegister: userRegisterReducer, 57 | userForgotPassword: userForgotPasswordReducer, 58 | userResetPassword: userResetPasswordReducer, 59 | userDetails: userDetailsReducer, 60 | userUpdateProfile: userUpdateProfileReducer, 61 | usersList: usersListReducer, 62 | userDelete: userDeleteReducer, 63 | userUpdate: userUpdateReducer, 64 | orderCreate: orderCreateReducer, 65 | orderDetails: orderDetailsReducer, 66 | orderListMy: orderListMyReducer, 67 | orderList: orderListReducer, 68 | orderPay: orderPayReducer, 69 | orderDeliver: orderDeliverReducer, 70 | }) 71 | 72 | const cartItemsFromStorage = localStorage.getItem('cartItems') 73 | ? JSON.parse(localStorage.getItem('cartItems')) 74 | : [] 75 | 76 | const userInfoFromStorage = localStorage.getItem('userInfo') 77 | ? JSON.parse(localStorage.getItem('userInfo')) 78 | : null 79 | 80 | const shippingAddressFromStorage = localStorage.getItem('shippingAddress') 81 | ? JSON.parse(localStorage.getItem('shippingAddress')) 82 | : {} 83 | 84 | const initialState = { 85 | cart: { 86 | cartItems: cartItemsFromStorage, 87 | shippingAddress: shippingAddressFromStorage, 88 | }, 89 | userLogin: { userInfo: userInfoFromStorage }, 90 | } 91 | 92 | const middleware = [thunk] 93 | 94 | const store = createStore( 95 | reducer, 96 | initialState, 97 | composeWithDevTools(applyMiddleware(...middleware)), 98 | ) 99 | 100 | export default store 101 | -------------------------------------------------------------------------------- /frontend/src/actions/carouselActions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | CAROUSEL_LIST_REQUEST, 4 | CAROUSEL_LIST_SUCCESS, 5 | CAROUSEL_LIST_FAIL, 6 | CAROUSEL_UPDATE_REQUEST, 7 | CAROUSEL_UPDATE_SUCCESS, 8 | CAROUSEL_UPDATE_FAIL, 9 | CAROUSEL_DETAILS_SUCCESS, 10 | CAROUSEL_DETAILS_REQUEST, 11 | CAROUSEL_DETAILS_FAIL, 12 | CAROUSEL_CREATE_REQUEST, 13 | CAROUSEL_CREATE_SUCCESS, 14 | CAROUSEL_CREATE_FAIL, 15 | } from '../constants/carouselConstants' 16 | import { logout } from './userActions' 17 | 18 | export const listCarousels = () => async (dispatch) => { 19 | try { 20 | dispatch({ type: CAROUSEL_LIST_REQUEST }) 21 | const { data } = await axios.get(`/api/carousels`) 22 | dispatch({ type: CAROUSEL_LIST_SUCCESS, payload: data }) 23 | } catch (error) { 24 | dispatch({ 25 | type: CAROUSEL_LIST_FAIL, 26 | payload: 27 | error.response && error.response.data.message 28 | ? error.response.data.message 29 | : error.message, 30 | }) 31 | } 32 | } 33 | 34 | export const listCarouselDetails = (id) => async (dispatch) => { 35 | try { 36 | dispatch({ type: CAROUSEL_DETAILS_REQUEST }) 37 | const { data } = await axios.get(`/api/carousels/${id}`) 38 | dispatch({ type: CAROUSEL_DETAILS_SUCCESS, payload: data }) 39 | } catch (error) { 40 | dispatch({ 41 | type: CAROUSEL_DETAILS_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 createCarousel = (carousel) => async (dispatch, getState) => { 51 | try { 52 | dispatch({ 53 | type: CAROUSEL_CREATE_REQUEST, 54 | }) 55 | 56 | const config = { 57 | headers: { 58 | 'Content-Type': 'application/json', 59 | }, 60 | } 61 | 62 | const { data } = await axios.post(`/api/carousels`, carousel, config) 63 | 64 | dispatch({ 65 | type: CAROUSEL_CREATE_SUCCESS, 66 | payload: data, 67 | }) 68 | } catch (error) { 69 | const message = 70 | error.response && error.response.data.message 71 | ? error.response.data.message 72 | : error.message 73 | if (message === 'Not authorized, token failed') { 74 | dispatch(logout()) 75 | } 76 | dispatch({ 77 | type: CAROUSEL_CREATE_FAIL, 78 | payload: message, 79 | }) 80 | } 81 | } 82 | 83 | export const updateCarousel = (carousel) => async (dispatch, getState) => { 84 | try { 85 | dispatch({ 86 | type: CAROUSEL_UPDATE_REQUEST, 87 | }) 88 | 89 | const config = { 90 | headers: { 91 | 'Content-Type': 'application/json', 92 | }, 93 | } 94 | 95 | const { data } = await axios.put( 96 | `/api/carousels/${carousel._id}`, 97 | carousel, 98 | config, 99 | ) 100 | 101 | dispatch({ 102 | type: CAROUSEL_UPDATE_SUCCESS, 103 | payload: data, 104 | }) 105 | dispatch({ type: CAROUSEL_DETAILS_SUCCESS, payload: data }) 106 | } catch (error) { 107 | const message = 108 | error.response && error.response.data.message 109 | ? error.response.data.message 110 | : error.message 111 | if (message === 'Not authorized, token failed') { 112 | dispatch(logout()) 113 | } 114 | dispatch({ 115 | type: CAROUSEL_UPDATE_FAIL, 116 | payload: message, 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /frontend/src/screens/CarouselEditScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Button, Form } from 'react-bootstrap' 3 | import { useDispatch, useSelector } from 'react-redux' 4 | import { Link } from 'react-router-dom' 5 | import { listCarouselDetails, updateCarousel } from '../actions/carouselActions' 6 | import FormContainer from '../components/FormContainer' 7 | import Loader from '../components/Loader' 8 | import Message from '../components/Message' 9 | import { 10 | CAROUSEL_DETAILS_RESET, 11 | CAROUSEL_UPDATE_RESET, 12 | } from '../constants/carouselConstants' 13 | 14 | const CarouselEditScreen = ({ match, history }) => { 15 | const carouselId = match.params.id 16 | 17 | const [imageSrc, setImageSrc] = useState('') 18 | const [imageAlt, setImageAlt] = useState('') 19 | 20 | const dispatch = useDispatch() 21 | 22 | const carouselDetails = useSelector((state) => state.carouselDetails) 23 | const { loading, error, carousel } = carouselDetails 24 | 25 | const carouselUpdate = useSelector((state) => state.carouselUpdate) 26 | const { 27 | loading: loadingUpdate, 28 | error: errorUpdate, 29 | success: successUpdate, 30 | } = carouselUpdate 31 | 32 | useEffect(() => { 33 | if (successUpdate) { 34 | dispatch({ type: CAROUSEL_UPDATE_RESET }) 35 | dispatch({ type: CAROUSEL_DETAILS_RESET }) 36 | history.push('/admin/carousellist') 37 | } else { 38 | if (carousel._id !== carouselId) { 39 | dispatch(listCarouselDetails(carouselId)) 40 | } else { 41 | setImageSrc(carousel.imageSrc) 42 | setImageAlt(carousel.imageAlt) 43 | } 44 | } 45 | }, [dispatch, history, carousel, carouselId, successUpdate]) 46 | 47 | const submitHandler = (e) => { 48 | e.preventDefault() 49 | dispatch( 50 | updateCarousel({ 51 | _id: carouselId, 52 | imageSrc, 53 | imageAlt, 54 | }), 55 | ) 56 | } 57 | 58 | return ( 59 | <> 60 | 61 |

Edit Carousel

62 | {loadingUpdate && } 63 | {errorUpdate && {errorUpdate}} 64 | {loading ? ( 65 | 66 | ) : error ? ( 67 | {error} 68 | ) : ( 69 |
70 | 71 | Image Src 72 | setImageSrc(e.target.value)} 77 | > 78 | 79 | 80 | 81 | Image Alt 82 | setImageAlt(e.target.value)} 87 | > 88 | 89 | 90 | 93 | 94 | Go Back 95 | 96 |
97 | )} 98 |
99 | 100 | ) 101 | } 102 | 103 | export default CarouselEditScreen 104 | -------------------------------------------------------------------------------- /frontend/src/screens/RegisterScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Form, Button, Row, Col } from 'react-bootstrap' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | import Message from '../components/Message' 6 | import Loader from '../components/Loader' 7 | import FormContainer from '../components/FormContainer' 8 | import { register } from '../actions/userActions' 9 | 10 | const RegisterScreen = ({ location, history }) => { 11 | const [name, setName] = useState('') 12 | const [email, setEmail] = useState('') 13 | const [password, setPassword] = useState('') 14 | const [confirmPassword, setConfirmPassword] = useState('') 15 | const [message, setMessage] = useState(null) 16 | 17 | const dispatch = useDispatch() 18 | 19 | const userRegister = useSelector((state) => state.userRegister) 20 | const { loading, error, userInfo } = userRegister 21 | 22 | const redirect = location.search ? location.search.split('=')[1] : '/' 23 | 24 | useEffect(() => { 25 | if (userInfo) { 26 | history.push(redirect) 27 | } 28 | }, [history, userInfo, redirect]) 29 | 30 | const submitHandler = (e) => { 31 | e.preventDefault() 32 | if (password !== confirmPassword) { 33 | setMessage('Passwords do not match') 34 | } else { 35 | dispatch(register(name, email, password)) 36 | } 37 | } 38 | 39 | return ( 40 | 41 |

Sign Up

42 | {message && {message}} 43 | {error && {error}} 44 | {loading && } 45 |
46 | 47 | Name 48 | setName(e.target.value)} 53 | > 54 | 55 | 56 | 57 | Email Address 58 | setEmail(e.target.value)} 63 | > 64 | 65 | 66 | 67 | Password 68 | setPassword(e.target.value)} 73 | > 74 | 75 | 76 | 77 | Confirm Password 78 | setConfirmPassword(e.target.value)} 83 | > 84 | 85 | 86 | 89 |
90 | 91 | 92 | 93 | Have an Account?{' '} 94 | 95 | Login 96 | 97 | 98 | 99 |
100 | ) 101 | } 102 | 103 | export default RegisterScreen 104 | -------------------------------------------------------------------------------- /frontend/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_LIST_BY_CATEGORY_REQUEST, 9 | PRODUCT_LIST_BY_CATEGORY_SUCCESS, 10 | PRODUCT_LIST_BY_CATEGORY_FAIL, 11 | PRODUCT_DELETE_REQUEST, 12 | PRODUCT_DELETE_SUCCESS, 13 | PRODUCT_DELETE_FAIL, 14 | PRODUCT_CREATE_REQUEST, 15 | PRODUCT_CREATE_SUCCESS, 16 | PRODUCT_CREATE_FAIL, 17 | PRODUCT_CREATE_RESET, 18 | PRODUCT_UPDATE_REQUEST, 19 | PRODUCT_UPDATE_SUCCESS, 20 | PRODUCT_UPDATE_FAIL, 21 | PRODUCT_UPDATE_RESET, 22 | PRODUCT_DETAILS_RESET, 23 | } from '../constants/productConstants' 24 | 25 | export const productListReducer = (state = { products: [] }, action) => { 26 | switch (action.type) { 27 | case PRODUCT_LIST_REQUEST: 28 | return { loading: true, products: [] } 29 | case PRODUCT_LIST_SUCCESS: 30 | return { loading: false, products: action.payload } 31 | case PRODUCT_LIST_FAIL: 32 | return { loading: false, error: action.payload } 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | export const productListBySearchReducer = ( 39 | state = { products: [] }, 40 | action, 41 | ) => { 42 | switch (action.type) { 43 | case PRODUCT_LIST_REQUEST: 44 | return { loading: true, products: [] } 45 | case PRODUCT_LIST_SUCCESS: 46 | return { loading: false, products: action.payload } 47 | case PRODUCT_LIST_FAIL: 48 | return { loading: false, error: action.payload } 49 | default: 50 | return state 51 | } 52 | } 53 | 54 | export const productListByCategoryReducer = ( 55 | state = { products: [] }, 56 | action, 57 | ) => { 58 | switch (action.type) { 59 | case PRODUCT_LIST_BY_CATEGORY_REQUEST: 60 | return { loading: true, products: [] } 61 | case PRODUCT_LIST_BY_CATEGORY_SUCCESS: 62 | return { loading: false, products: action.payload } 63 | case PRODUCT_LIST_BY_CATEGORY_FAIL: 64 | return { loading: false, error: action.payload } 65 | default: 66 | return state 67 | } 68 | } 69 | 70 | export const productDetailsReducer = (state = { product: {} }, action) => { 71 | switch (action.type) { 72 | case PRODUCT_DETAILS_REQUEST: 73 | return { loading: true, ...state } 74 | case PRODUCT_DETAILS_SUCCESS: 75 | return { loading: false, product: action.payload } 76 | case PRODUCT_DETAILS_FAIL: 77 | return { loading: false, error: action.payload } 78 | case PRODUCT_DETAILS_RESET: 79 | return { product: {} } 80 | default: 81 | return state 82 | } 83 | } 84 | 85 | export const productDeleteReducer = (state = {}, action) => { 86 | switch (action.type) { 87 | case PRODUCT_DELETE_REQUEST: 88 | return { loading: true } 89 | case PRODUCT_DELETE_SUCCESS: 90 | return { loading: false, success: true } 91 | case PRODUCT_DELETE_FAIL: 92 | return { loading: false, error: action.payload } 93 | default: 94 | return state 95 | } 96 | } 97 | 98 | export const productCreateReducer = (state = { product: {} }, action) => { 99 | switch (action.type) { 100 | case PRODUCT_CREATE_REQUEST: 101 | return { loading: true } 102 | case PRODUCT_CREATE_SUCCESS: 103 | return { loading: false, success: true, product: action.payload } 104 | case PRODUCT_CREATE_FAIL: 105 | return { loading: false, error: action.payload } 106 | case PRODUCT_CREATE_RESET: 107 | return {} 108 | default: 109 | return state 110 | } 111 | } 112 | 113 | export const productUpdateReducer = (state = { product: {} }, action) => { 114 | switch (action.type) { 115 | case PRODUCT_UPDATE_REQUEST: 116 | return { loading: true } 117 | case PRODUCT_UPDATE_SUCCESS: 118 | return { loading: false, success: true, product: action.payload } 119 | case PRODUCT_UPDATE_FAIL: 120 | return { loading: false, error: action.payload } 121 | case PRODUCT_UPDATE_RESET: 122 | return { product: {} } 123 | default: 124 | return state 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /frontend/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { LinkContainer } from 'react-router-bootstrap' 4 | import { Nav, Navbar, Container, NavDropdown } from 'react-bootstrap' 5 | import { logout } from '../actions/userActions' 6 | import { BsWhatsapp, BsCart } from 'react-icons/bs' 7 | import { FiPhoneCall, FiUser } from 'react-icons/fi' 8 | import { FaCircle } from 'react-icons/fa' 9 | import SearchBox from './SearchBox' 10 | import { Route } from 'react-router' 11 | import { useLocation } from 'react-router' 12 | 13 | const Header = () => { 14 | const dispatch = useDispatch() 15 | const userLogin = useSelector((state) => state.userLogin) 16 | const cart = useSelector((state) => state.cart) 17 | const { cartItems } = cart 18 | const { userInfo } = userLogin 19 | const logoutHandler = () => { 20 | dispatch(logout()) 21 | } 22 | const location = useLocation() 23 | 24 | return ( 25 |
26 | 27 | 28 | 29 | Freshbey 30 | 31 | 47 | 48 | 49 | 86 | 87 | 88 | 89 | 90 | {location.pathname === '/login' || 91 | location.pathname === '/register' || 92 | location.pathname === '/forgotpassword' || 93 | location.pathname === '/forgotpassword/:email' ? ( 94 | '' 95 | ) : ( 96 | } /> 97 | )} 98 |
99 | ) 100 | } 101 | 102 | export default Header 103 | -------------------------------------------------------------------------------- /frontend/src/screens/UserEditScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { Form, 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 FormContainer from '../components/FormContainer' 8 | import { getUserDetails, updateUser } from '../actions/userActions' 9 | // import { USER_UPDATE_RESET } from '../constants/userConstants' 10 | 11 | const UserEditScreen = ({ match, history }) => { 12 | const userId = match.params.id 13 | 14 | const [name, setName] = useState('') 15 | const [email, setEmail] = useState('') 16 | const [isAdmin, setIsAdmin] = useState(false) 17 | 18 | const dispatch = useDispatch() 19 | 20 | const userDetails = useSelector((state) => state.userDetails) 21 | const { loading, error, user } = userDetails 22 | 23 | const userUpdate = useSelector((state) => state.userUpdate) 24 | const { 25 | loading: loadingUpdate, 26 | error: errorUpdate, 27 | success: successUpdate, 28 | } = userUpdate 29 | 30 | const userLogin = useSelector((state) => state.userLogin) 31 | const { userInfo } = userLogin 32 | 33 | // useEffect(() => { 34 | // if (successUpdate) { 35 | // dispatch({ type: USER_UPDATE_RESET }) 36 | // history.push('/admin/users') 37 | // } else { 38 | // if (!user.name || user._id !== userId) { 39 | // dispatch(getUserDetails(userId)) 40 | // } else { 41 | // setName(user.name) 42 | // setEmail(user.email) 43 | // setIsAdmin(user.isAdmin) 44 | // } 45 | // } 46 | // }, [dispatch, history, userId, user, successUpdate]) 47 | 48 | useEffect(() => { 49 | if (userInfo && userInfo.isAdmin) { 50 | if (!user || user._id !== userId) { 51 | dispatch(getUserDetails(userId)) 52 | } else { 53 | setName(user.name) 54 | setEmail(user.email) 55 | setIsAdmin(user.isAdmin) 56 | } 57 | } else { 58 | history.push('/login') 59 | } 60 | }, [userId, dispatch, user, history, userInfo, successUpdate]) 61 | 62 | const submitHandler = (e) => { 63 | e.preventDefault() 64 | dispatch(updateUser({ _id: userId, name, email, isAdmin })) 65 | } 66 | 67 | return ( 68 | <> 69 | 70 |

Edit User

71 | {loadingUpdate && } 72 | {errorUpdate && {errorUpdate}} 73 | {loading ? ( 74 | 75 | ) : error ? ( 76 | {error} 77 | ) : ( 78 |
79 | 80 | Name 81 | setName(e.target.value)} 86 | > 87 | 88 | 89 | 90 | Email Address 91 | setEmail(e.target.value)} 96 | > 97 | 98 | 99 | 100 | setIsAdmin(e.target.checked)} 105 | > 106 | 107 | 108 | 111 | 112 | Go Back 113 | 114 |
115 | )} 116 |
117 | 118 | ) 119 | } 120 | 121 | export default UserEditScreen 122 | -------------------------------------------------------------------------------- /frontend/src/screens/ProductListScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { LinkContainer } from 'react-router-bootstrap' 3 | import { Table, Button, Row, Col, ButtonGroup } from 'react-bootstrap' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | import Message from '../components/Message' 6 | import Loader from '../components/Loader' 7 | import { deleteProduct, listProducts } from '../actions/productActions' 8 | import formatter from '../utils/currencyFormatter' 9 | import { PRODUCT_CREATE_RESET } from '../constants/productConstants' 10 | 11 | const ProductListScreen = ({ history, match }) => { 12 | const dispatch = useDispatch() 13 | 14 | const productList = useSelector((state) => state.productList) 15 | const { loading, error, products } = productList 16 | 17 | const productDelete = useSelector((state) => state.productDelete) 18 | const { 19 | loading: loadingDelete, 20 | error: errorDelete, 21 | success: successDelete, 22 | } = productDelete 23 | 24 | // const productCreate = useSelector((state) => state.productCreate) 25 | // const { 26 | // loading: loadingCreate, 27 | // error: errorCreate, 28 | // success: successCreate, 29 | // product: createdProduct, 30 | // } = productCreate 31 | 32 | const userLogin = useSelector((state) => state.userLogin) 33 | const { userInfo } = userLogin 34 | 35 | useEffect(() => { 36 | dispatch({ type: PRODUCT_CREATE_RESET }) 37 | 38 | if (!userInfo || !userInfo.isAdmin) { 39 | history.push('/login') 40 | } else { 41 | dispatch(listProducts()) 42 | } 43 | 44 | // if (successCreate) { 45 | // history.push(`/admin/product/${createdProduct._id}/edit`) 46 | // } else { 47 | // dispatch(listProducts()) 48 | // } 49 | }, [dispatch, history, userInfo, successDelete]) 50 | 51 | const deleteHandler = (id) => { 52 | if (window.confirm('Are you sure')) { 53 | dispatch(deleteProduct(id)) 54 | } 55 | } 56 | 57 | const createProductHandler = () => { 58 | history.push('/admin/createproduct') 59 | } 60 | 61 | return ( 62 | <> 63 | 64 | 65 |

Products

66 | 67 | 68 | 71 | 72 |
73 | {loadingDelete && } 74 | {errorDelete && {errorDelete}} 75 | {/* {loadingCreate && } 76 | {errorCreate && {errorCreate}} */} 77 | {loading ? ( 78 | 79 | ) : error ? ( 80 | {error} 81 | ) : ( 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {products.map((product) => ( 94 | 95 | 96 | 97 | 98 | 99 | 115 | 116 | ))} 117 | 118 |
PRODUCT IDNAMEPRICECATEGORYOPTIONS
{product._id}{product.name}{formatter(product.price)}{product.category} 100 | 101 | 102 | 105 | 106 | 113 | 114 |
119 | )} 120 | 121 | ) 122 | } 123 | 124 | export default ProductListScreen 125 | -------------------------------------------------------------------------------- /test/userController.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../backend/server') 2 | const mongoose = require('mongoose') 3 | const User = require('../backend/models/userModel') 4 | const chai = require('chai') 5 | const chaiHttp = require('chai-http') 6 | const expect = chai.expect 7 | chai.use(chaiHttp) 8 | chai.should() 9 | 10 | describe('USERS', () => { 11 | beforeEach((done) => { 12 | User.deleteMany({}, (err) => { 13 | done() 14 | }) 15 | }) 16 | describe('/POST user', () => { 17 | it('it should register user', (done) => { 18 | let user = { 19 | name: 'Felex onyango', 20 | email: 'felex@gmail.com', 21 | password: '@Felex2020', 22 | } 23 | chai 24 | .request(app) 25 | .post('/api/users') 26 | .send(user) 27 | .end((err, res) => { 28 | res.should.have.status(201) 29 | res.should.have.a('object') 30 | res.body.should.have.property('name') 31 | res.body.should.have.property('email') 32 | done() 33 | }) 34 | }) 35 | }) 36 | describe('/POST login user', () => { 37 | it('should login user', (done) => { 38 | let user = { 39 | email: 'felex@gmail.com', 40 | password: '@Felex2020', 41 | } 42 | chai 43 | .request(app) 44 | .post('/api/users/login') 45 | .send(user) 46 | .end((err, res) => { 47 | res.should.have.a('object') 48 | done() 49 | }) 50 | }) 51 | }) 52 | 53 | describe('/GET profile ', () => { 54 | it('it should return profile of user user by id', (done) => { 55 | let user = new User({ name: 'Felex onyango', email: 'felex@gmail.com' }) 56 | user.save((err, user) => { 57 | chai 58 | .request(app) 59 | .get('/api/users/profile') 60 | .send(user) 61 | .end((err, res) => { 62 | res.should.have.a('object') 63 | done() 64 | }) 65 | }) 66 | }) 67 | }) 68 | describe('PUT /api/users/profile', () => { 69 | it('should update user profile', (done) => { 70 | let user = new User({ 71 | name: 'Felex onyango', 72 | email: 'felexonyango@gmail.com', 73 | }) 74 | user.save((err, user) => { 75 | chai 76 | .request(app) 77 | .put('/api/users/profile') 78 | .send(user) 79 | .end((err, res) => { 80 | res.should.have.a('object') 81 | done() 82 | }) 83 | }) 84 | }) 85 | }) 86 | describe('/api/users/forgotpassword', () => { 87 | it('should forget password of a user', (done) => { 88 | let user = { 89 | email: 'felexonyango@gmail.com', 90 | } 91 | chai 92 | .request(app) 93 | .post('/api/users/forgotpassword') 94 | .send(user) 95 | .end((err, res) => { 96 | res.should.have.a('object') 97 | done() 98 | }) 99 | }) 100 | }) 101 | describe('/GET users ', () => { 102 | it('it should return all users', (done) => { 103 | chai 104 | .request(app) 105 | .get('/api/users') 106 | .end((err, res) => { 107 | res.should.have.a('object') 108 | done() 109 | }) 110 | }) 111 | }) 112 | describe('/DELETE UserId ', () => { 113 | it('it should delete the user by id', (done) => { 114 | const id = 1 115 | chai 116 | .request(app) 117 | .delete(`/api/users/${id}`) 118 | .end((err, res) => { 119 | res.should.have.a('object') 120 | done() 121 | }) 122 | }) 123 | }) 124 | describe('/GET UserId ', () => { 125 | it('it should get the user by id', (done) => { 126 | const id = 2 127 | chai 128 | .request(app) 129 | .delete(`/api/users/${id}`) 130 | .end((err, res) => { 131 | res.should.have.a('object') 132 | done() 133 | }) 134 | }) 135 | }) 136 | describe('PUT /api/users/:id', () => { 137 | it('it should update the user by id', (done) => { 138 | const id = 2 139 | let user = { 140 | name: 'Felex tabu', 141 | email: 'felex@gmail.com', 142 | } 143 | chai 144 | .request(app) 145 | .patch(`/api/users/${id}`) 146 | .send(user) 147 | .end((err, res) => { 148 | res.should.have.a('object') 149 | done() 150 | }) 151 | }) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /backend/controllers/productController.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = require('express-async-handler') 2 | const Product = require('../models/productModel') 3 | 4 | // @desc Fetch all products 5 | // @route GET /api/products 6 | // @access Public 7 | const getProducts = asyncHandler(async (req, res) => { 8 | const keyword = req.query.keyword 9 | ? { 10 | name: { 11 | $regex: req.query.keyword, 12 | $options: 'i', 13 | }, 14 | } 15 | : {} 16 | 17 | const products = await Product.find({ ...keyword }) 18 | if (products) { 19 | res.json(products) 20 | } else { 21 | res.status(404) 22 | throw new Error('No product found based on your search') 23 | } 24 | }) 25 | 26 | // @desc Fetch single product 27 | // @route GET /api/products/:id 28 | // @access Public 29 | const getProductById = asyncHandler(async (req, res) => { 30 | const product = await Product.findById(req.params.id) 31 | if (product) { 32 | res.json(product) 33 | } else { 34 | res.status(404) 35 | throw new Error('Product not found') 36 | } 37 | }) 38 | 39 | // @desc Delete a product 40 | // @route DELETE /api/products/:id 41 | // @access Private/Admin 42 | const deleteProduct = asyncHandler(async (req, res) => { 43 | const product = await Product.findById(req.params.id) 44 | 45 | if (product) { 46 | await product.remove() 47 | res.json({ message: 'Product removed' }) 48 | } else { 49 | res.status(404) 50 | throw new Error('Product not found') 51 | } 52 | }) 53 | 54 | // @desc Create a product 55 | // @route POST /api/products 56 | // @access Private/Admin 57 | const createProduct = asyncHandler(async (req, res) => { 58 | const { 59 | name, 60 | price, 61 | mrp, 62 | description, 63 | imageSrc, 64 | imageAlt, 65 | category, 66 | countInStock, 67 | } = req.body 68 | const product = new Product({ 69 | name, 70 | price, 71 | mrp, 72 | user: req.user._id, 73 | imageSrc, 74 | imageAlt, 75 | category, 76 | countInStock, 77 | description, 78 | }) 79 | 80 | try { 81 | const createdProduct = await product.save() 82 | res.status(201).json(createdProduct) 83 | } catch (error) { 84 | if (error.name === 'ValidationError') { 85 | let errors = '' 86 | Object.keys(error.errors).forEach((key) => { 87 | errors += error.errors[key].message + '.\n' 88 | }) 89 | res.status(500).json(errors) 90 | } 91 | } 92 | }) 93 | 94 | // @desc Update a product 95 | // @route PUT /api/products/:id 96 | // @access Private/Admin 97 | const updateProduct = asyncHandler(async (req, res) => { 98 | const { 99 | name, 100 | price, 101 | mrp, 102 | description, 103 | imageSrc, 104 | imageAlt, 105 | category, 106 | countInStock, 107 | } = req.body 108 | 109 | const product = await Product.findById(req.params.id) 110 | 111 | if (product) { 112 | product.name = name 113 | product.price = price 114 | product.mrp = mrp 115 | product.description = description 116 | product.imageSrc = imageSrc 117 | product.imageAlt = imageAlt 118 | product.category = category 119 | product.countInStock = countInStock 120 | try { 121 | const updatedProduct = await product.save() 122 | res.json(updatedProduct) 123 | } catch (error) { 124 | if (error.name === 'ValidationError') { 125 | let errors = '' 126 | Object.keys(error.errors).forEach((key) => { 127 | errors += error.errors[key].message + '.\n' 128 | }) 129 | res.status(500).json(errors) 130 | } 131 | } 132 | } else { 133 | res.status(404) 134 | throw new Error('Product not found') 135 | } 136 | }) 137 | 138 | const updateStockCount = asyncHandler(async (req, res, id, qty) => { 139 | try { 140 | var product = await Product.findById(id) 141 | } catch (err) { 142 | throw new Error(err.message) 143 | } 144 | 145 | if (product.countInStock < qty) { 146 | throw new Error( 147 | 'We are really sorry. We have not ' + 148 | qty + 149 | ' amount of ' + 150 | product.name + 151 | ' currently on stock. Either you wait for stock to fullfilled or you can decrease your ordered quantity of ' + 152 | product.name, 153 | ) 154 | } 155 | product.countInStock -= qty 156 | const updatedProduct = await product.save() 157 | return updateProduct 158 | }) 159 | 160 | module.exports = { 161 | getProducts, 162 | getProductById, 163 | deleteProduct, 164 | createProduct, 165 | updateProduct, 166 | updateStockCount, 167 | } 168 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 3 | import { Container } from 'react-bootstrap' 4 | import Header from './components/Header' 5 | import Footer from './components/Footer' 6 | import HomeScreen from './screens/HomeScreen' 7 | import ProductScreen from './screens/ProductScreen' 8 | import CategoryScreen from './screens/CategoryScreen' 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 ForgotPasswordScreen from './screens/ForgotPasswordScreen' 15 | import ResetPasswordScreen from './screens/ResetPasswordScreen' 16 | import PaymentScreen from './screens/PaymentScreen' 17 | import PlaceOrderScreen from './screens/PlaceOrderScreen' 18 | import OrderScreen from './screens/OrderScreen' 19 | import UsersListScreen from './screens/UsersListScreen' 20 | import UserEditScreen from './screens/UserEditScreen' 21 | import ProductListScreen from './screens/ProductListScreen' 22 | import CreateProductScreen from './screens/CreateProductScreen' 23 | import ProductEditScreen from './screens/ProductEditScreen' 24 | import OrderListScreen from './screens/OrderListScreen' 25 | import SearchScreen from './screens/SearchScreen' 26 | import CarouselListScreen from './screens/CarouselListScreen' 27 | import CarouselEditScreen from './screens/CarouselEditScreen' 28 | import CreateCarouselScreen from './screens/CreateCarouselScreen' 29 | import { CategoryListScreen } from './screens/CategoryListScreen' 30 | import Error404Screen from './screens/Error404Screen' 31 | import Terms from './pages/Terms' 32 | import Returns from './pages/Returns' 33 | import Privacy from './pages/Privacy' 34 | import Help from './pages/Help' 35 | 36 | function App() { 37 | return ( 38 | 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 70 | 74 | 75 | 76 | 80 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 |