├── client
├── vercel.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── images
│ │ ├── pizza.png
│ │ ├── screen1.png
│ │ ├── chef-pizza.png
│ │ └── pizza-jumbo-bg.jpg
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── vite.svg
│ └── site.webmanifest
├── tailwind.config.js
├── src
│ ├── App.jsx
│ ├── components
│ │ ├── layout
│ │ │ ├── Layout.jsx
│ │ │ └── Footer.jsx
│ │ └── ui
│ │ │ ├── PizzaMenu
│ │ │ ├── PizzaList.jsx
│ │ │ └── PizzaItem.jsx
│ │ │ ├── Admin
│ │ │ ├── Dashboard
│ │ │ │ ├── SideBar
│ │ │ │ │ ├── SideBarToggleButton.jsx
│ │ │ │ │ └── SideBar.jsx
│ │ │ │ ├── InventoryTable.jsx
│ │ │ │ ├── MainContent.jsx
│ │ │ │ └── Lists
│ │ │ │ │ ├── UsersList.jsx
│ │ │ │ │ ├── StaffList.jsx
│ │ │ │ │ ├── PizzasList.jsx
│ │ │ │ │ └── InventoryList.jsx
│ │ │ └── Auth
│ │ │ │ ├── LoginForm.jsx
│ │ │ │ └── RegisterForm.jsx
│ │ │ ├── Cart
│ │ │ ├── CartButton.jsx
│ │ │ ├── AddToCartButton.jsx
│ │ │ └── CartModal
│ │ │ │ ├── CartItemList.jsx
│ │ │ │ └── CartModal.jsx
│ │ │ ├── Loader.jsx
│ │ │ ├── CheckoutSteps
│ │ │ ├── CurrentCheckoutStep.jsx
│ │ │ ├── RazorPayPaymentButton.jsx
│ │ │ ├── PaymentStep.jsx
│ │ │ └── ShippingStep.jsx
│ │ │ ├── Profile
│ │ │ ├── Profile.jsx
│ │ │ ├── ProfileBtnAndDropOnNav.jsx
│ │ │ └── EditProfileForm.jsx
│ │ │ ├── Button.jsx
│ │ │ ├── Home
│ │ │ ├── Jumbotron.jsx
│ │ │ ├── FeaturedPizzasSection.jsx
│ │ │ └── HowItWorksSection.jsx
│ │ │ ├── Message.jsx
│ │ │ ├── Auth
│ │ │ ├── ForgetPassword
│ │ │ │ ├── EmailForm.jsx
│ │ │ │ ├── OTPForm.jsx
│ │ │ │ └── PasswordForm.jsx
│ │ │ ├── UserLoginForm.jsx
│ │ │ └── VerficationModal.jsx
│ │ │ └── UserOrdersTable.jsx
│ ├── index.css
│ ├── redux
│ │ ├── store.js
│ │ ├── slices
│ │ │ ├── pizzaSlice.js
│ │ │ ├── inventorySlice.js
│ │ │ └── orderSlice.js
│ │ └── asyncThunks
│ │ │ ├── pizzaThunks.js
│ │ │ ├── inventoryThunks.js
│ │ │ └── orderThunks.js
│ ├── screens
│ │ ├── HomeScreen.jsx
│ │ ├── User
│ │ │ ├── ForgetPasswordScreen.jsx
│ │ │ ├── UserLoginScreen.jsx
│ │ │ ├── UserRegisterScreen.jsx
│ │ │ ├── CheckoutScreen.jsx
│ │ │ ├── UserOrdersScreen.jsx
│ │ │ └── ProfileScreen.jsx
│ │ ├── Admin
│ │ │ ├── AdminLoginScreen.jsx
│ │ │ ├── AdminRegisterScreen.jsx
│ │ │ └── AdminDashboardScreen.jsx
│ │ ├── AboutScreen.jsx
│ │ └── MenuScreen.jsx
│ └── main.jsx
├── README.md
├── .eslintrc.cjs
├── package.json
├── index.html
└── vite.config.js
├── server
├── vercel.json
├── utils
│ ├── generateToken.js
│ └── inventoryUtils.js
├── schemas
│ ├── adminUserSchema.js
│ ├── pizzaSchema.js
│ ├── userSchema.js
│ ├── inventorySchema.js
│ └── orderSchema.js
├── config
│ └── db.js
├── middlewares
│ ├── errorMiddlewares.js
│ ├── nodemailerMiddleware.js
│ └── authMiddlewares.js
├── routes
│ ├── pizzaRoutes.js
│ ├── inventoryRoutes.js
│ ├── orderRoutes.js
│ ├── adminUserRoutes.js
│ └── userRoutes.js
├── package.json
├── data
│ ├── users.js
│ ├── pizzas.js
│ └── inventory.js
├── seeder.js
└── index.js
├── .gitignore
└── package.json
/client/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
3 | }
4 |
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/favicon-16x16.png
--------------------------------------------------------------------------------
/client/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/favicon-32x32.png
--------------------------------------------------------------------------------
/client/public/images/pizza.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/images/pizza.png
--------------------------------------------------------------------------------
/client/public/images/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/images/screen1.png
--------------------------------------------------------------------------------
/client/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/client/public/images/chef-pizza.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/images/chef-pizza.png
--------------------------------------------------------------------------------
/client/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/client/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/client/public/images/pizza-jumbo-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itxSaaad/pizza-palette-app-mern-OIBSIP-task-1/HEAD/client/public/images/pizza-jumbo-bg.jpg
--------------------------------------------------------------------------------
/server/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [{ "src": "index.js", "use": "@vercel/node" }],
4 | "routes": [{ "src": "/(.*)", "dest": "/index.js" }]
5 | }
6 |
--------------------------------------------------------------------------------
/client/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from 'react-router-dom';
2 |
3 | import Layout from './components/layout/Layout';
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/client/src/components/layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Footer from './Footer';
4 | import MainNavBar from './MainNavBar';
5 |
6 | function Layout({ children }) {
7 | return (
8 | <>
9 |
10 | {children}
11 |
12 | >
13 | );
14 | }
15 |
16 | Layout.propTypes = {
17 | children: PropTypes.node.isRequired,
18 | };
19 |
20 | export default Layout;
21 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/client/src/components/ui/PizzaMenu/PizzaList.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes, { object } from 'prop-types';
2 | import PizzaItem from './PizzaItem';
3 |
4 | function PizzaList({ pizzaList }) {
5 | return (
6 |
7 | {pizzaList.map((pizza) => (
8 |
9 | ))}
10 |
11 | );
12 | }
13 |
14 | PizzaList.propTypes = {
15 | pizzaList: PropTypes.arrayOf(object).isRequired,
16 | };
17 |
18 | export default PizzaList;
19 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | * {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | font-family: 'Nunito', sans-serif;
10 | }
11 |
12 | /* Custom Scrollbar */
13 | ::-webkit-scrollbar {
14 | width: 5px;
15 | }
16 |
17 | ::-webkit-scrollbar-track {
18 | background: #f1f1f1;
19 | }
20 |
21 | ::-webkit-scrollbar-thumb {
22 | background: #ff7f00; /* Use the same color as your navbar */
23 | border-radius: 4px;
24 | }
25 |
26 | ::-webkit-scrollbar-thumb:hover {
27 | background: #ff6600;
28 | }
29 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/server/utils/generateToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | /**
4 | * Generates a JWT token for the given user ID.
5 | * @param {string} id - User ID for which the token will be generated.
6 | * @returns {string} - JWT token.
7 | */
8 |
9 | const generateToken = (id) => {
10 | // Uncomment the following line to log the JWT secret (for debugging purposes)
11 | // console.log('SECRET', process.env.JWT_SECRET);
12 |
13 | // Generate a JWT token with the user ID payload and the provided JWT secret
14 | // Set the token to expire in 15 days
15 | return jwt.sign({ id }, process.env.JWT_SECRET, {
16 | expiresIn: '15d',
17 | });
18 | };
19 |
20 | module.exports = generateToken;
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node.js / NPM
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Build output
8 | build/
9 | dist/
10 | out/
11 | *.log
12 |
13 | # Environment variables and configurations
14 | .env
15 | .env.local
16 | .env.development
17 | .env.test
18 | .env.production
19 |
20 | # Editor/IDE specific files
21 | .vscode/
22 | .idea/
23 |
24 | # Logs
25 | *.log
26 |
27 | # Dependency lock files
28 | yarn.lock
29 | package-lock.json
30 |
31 |
32 | # Compiled binary files
33 | *.bin
34 |
35 | # macOS specific files
36 | .DS_Store
37 |
38 | # JetBrains IDEs
39 | .idea/
40 |
41 | # Testing files
42 | coverage/
43 | .nyc_output/
44 |
45 | # User-specific files
46 | *.iml
47 | .idea/
48 | *.DS_Store
--------------------------------------------------------------------------------
/server/schemas/adminUserSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const adminUserSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | email: {
10 | type: String,
11 | required: true,
12 | unique: true,
13 | },
14 | password: {
15 | type: String,
16 | required: true,
17 | },
18 | role: {
19 | type: String,
20 | required: true,
21 | },
22 | permissions: [{ type: String }],
23 | isApproved: {
24 | type: Boolean,
25 | default: false,
26 | },
27 | },
28 | {
29 | timestamps: true,
30 | }
31 | );
32 |
33 | module.exports = mongoose.model('Admin', adminUserSchema);
34 |
--------------------------------------------------------------------------------
/server/config/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // Establish connection to the MongoDB database
4 | const connectDb = async () => {
5 | try {
6 | const conn = await mongoose.connect(`${process.env.MONGO_URI}`, {
7 | useNewUrlParser: true, // Use new URL parser
8 | useUnifiedTopology: true, // Use new server discovery and monitoring engine
9 | });
10 |
11 | // Connection successful; log the host information
12 | console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline);
13 | } catch (error) {
14 | // Connection failed; log the error and exit
15 | console.error(`Error: ${error.message}`.red.underline.bold);
16 | process.exit(1);
17 | }
18 | };
19 |
20 | module.exports = connectDb;
21 |
--------------------------------------------------------------------------------
/server/middlewares/errorMiddlewares.js:
--------------------------------------------------------------------------------
1 | const notFound = (req, res, next) => {
2 | const error = new Error(`Not Found - ${req.originalUrl}`);
3 | res.status(404);
4 | next(error); // Pass the error to the next middleware
5 | };
6 |
7 | const errorHandler = (err, req, res, next) => {
8 | // Determine the appropriate status code
9 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
10 | res.status(statusCode);
11 |
12 | const response = {
13 | message: err.message, // Error message
14 | };
15 |
16 | if (process.env.NODE_ENV !== 'production') {
17 | response.stack = err.stack; // Error stack trace
18 | }
19 |
20 | res.json(response); // Send JSON response
21 | };
22 |
23 | module.exports = { notFound, errorHandler };
24 |
--------------------------------------------------------------------------------
/client/src/components/layout/Footer.jsx:
--------------------------------------------------------------------------------
1 | function Footer() {
2 | return (
3 |
19 | );
20 | }
21 |
22 | export default Footer;
23 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 |
3 | // Import Reducers
4 | import adminReducer from './slices/adminSlice.js';
5 | import cartReducer from './slices/cartSlice.js';
6 | import orderReducer from './slices/orderSlice.js';
7 | import pizzaReducer from './slices/pizzaSlice.js';
8 | import userReducer from './slices/userSlice.js';
9 | import inventoryReducer from './slices/inventorySlice.js';
10 |
11 | // Create Store
12 | const store = configureStore({
13 | reducer: {
14 | admin: adminReducer,
15 | cart: cartReducer,
16 | order: orderReducer,
17 | pizza: pizzaReducer,
18 | user: userReducer,
19 | inventory: inventoryReducer,
20 | },
21 | devTools: true,
22 | });
23 |
24 | // Export Store
25 | export default store;
26 |
--------------------------------------------------------------------------------
/server/routes/pizzaRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Initialize Express Router
4 | const router = express.Router();
5 |
6 | // Import Middlewares
7 | const { protect, admin } = require('../middlewares/authMiddlewares');
8 |
9 | // Import Controllers
10 | const {
11 | getAllPizzas,
12 | getPizzaById,
13 | createPizza,
14 | updatePizzaById,
15 | deletePizzaById,
16 | } = require('../controllers/pizzaControllers');
17 |
18 | // Initialize Routes
19 |
20 | // Public Routes
21 | router.route('/').get(getAllPizzas);
22 | router.get('/:id', getPizzaById);
23 |
24 | // Private Routes
25 | router.post('/', protect, createPizza);
26 |
27 | // Admin + Private Routes
28 | router
29 | .route('/:id')
30 | .put(protect, admin, updatePizzaById)
31 | .delete(protect, admin, deletePizzaById);
32 |
33 | // Export Router
34 | module.exports = router;
35 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/SideBar/SideBarToggleButton.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
3 |
4 | function SideBarToggleButton({ onClick, isOpen }) {
5 | return (
6 |
10 | {isOpen ? : }
11 |
12 | {isOpen ? ' Collapse' : ' Expand'}
13 |
14 |
15 | );
16 | }
17 |
18 | SideBarToggleButton.propTypes = {
19 | onClick: PropTypes.func.isRequired,
20 | isOpen: PropTypes.bool.isRequired,
21 | };
22 |
23 | export default SideBarToggleButton;
24 |
--------------------------------------------------------------------------------
/client/src/components/ui/Cart/CartButton.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { FaShoppingCart } from 'react-icons/fa';
3 |
4 | import Button from '../Button';
5 |
6 | function CartButton({ children, ...props }) {
7 | return (
8 |
13 |
14 | Your Cart
15 |
16 | {children}
17 |
18 |
19 | );
20 | }
21 |
22 | CartButton.propTypes = {
23 | children: PropTypes.node.isRequired,
24 | };
25 |
26 | export default CartButton;
27 |
--------------------------------------------------------------------------------
/server/routes/inventoryRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Initialize Express Router
4 | const router = express.Router();
5 |
6 | // Import Middlewares
7 | const { protect, admin } = require('../middlewares/authMiddlewares');
8 |
9 | // Import Controllers
10 | const {
11 | getAllStocks,
12 | getStockById,
13 | createStock,
14 | updateStockById,
15 | deleteStockById,
16 | } = require('../controllers/inventoryControllers');
17 |
18 | // Initialize Routes
19 |
20 | // Public Routes
21 |
22 | // Private Routes
23 | router.get('/', protect, getAllStocks);
24 | router.get('/:id', protect, getStockById);
25 |
26 | // Admin + Private Routes
27 | router.post('/', protect, admin, createStock);
28 | router
29 | .route('/:id')
30 | .put(protect, admin, updateStockById)
31 | .delete(protect, admin, deleteStockById);
32 |
33 | // Export Router
34 | module.exports = router;
35 |
--------------------------------------------------------------------------------
/server/routes/orderRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Initialize Express Router
4 | const router = express.Router();
5 |
6 | // Import Middlewares
7 | const { protect, admin } = require('../middlewares/authMiddlewares');
8 |
9 | // Import Controllers
10 | const {
11 | createOrder,
12 | createRazorpayOrder,
13 | getOrdersByUserId,
14 | getAllOrders,
15 | getOrderById,
16 | updateOrderById,
17 | deleteOrderById,
18 | } = require('../controllers/orderControllers');
19 |
20 | // Initialize Routes
21 |
22 | // Public Routes
23 |
24 | // Private Routes
25 | router.route('/').post(protect, createOrder);
26 | router.route('/user').get(protect, getOrdersByUserId);
27 | router.route('/checkout').post(protect, createRazorpayOrder);
28 |
29 | // Admin + Private Routes
30 | router.route('/').get(protect, admin, getAllOrders);
31 | router
32 | .route('/:id')
33 | .get(protect, admin, getOrderById)
34 | .put(protect, admin, updateOrderById)
35 | .delete(protect, admin, deleteOrderById);
36 |
37 | // Export Router
38 | module.exports = router;
39 |
--------------------------------------------------------------------------------
/server/routes/adminUserRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Initialize Express Router
4 | const router = express.Router();
5 |
6 | // Import Middlewares
7 | const { protect, admin } = require('../middlewares/authMiddlewares');
8 |
9 | // Import Controllers
10 | const {
11 | authAdmin,
12 | registerAdmin,
13 | getAdminProfile,
14 | updateAdminProfile,
15 | getAllAdmins,
16 | getAdminById,
17 | updateAdminById,
18 | deleteAdminById,
19 | } = require('../controllers/adminUserControllers');
20 |
21 | // Initialize Routes
22 |
23 | // Public Routes
24 | router.post('/login', authAdmin);
25 | router.post('/register', registerAdmin);
26 |
27 | // Private Routes
28 | router
29 | .route('/profile')
30 | .get(protect, getAdminProfile)
31 | .put(protect, updateAdminProfile);
32 |
33 | // Admin + Private Routes
34 | router.get('/', protect, admin, getAllAdmins);
35 | router
36 | .route('/:id')
37 | .get(protect, admin, getAdminById)
38 | .put(protect, admin, updateAdminById)
39 | .delete(protect, admin, deleteAdminById);
40 |
41 | // Export Router
42 | module.exports = router;
43 |
--------------------------------------------------------------------------------
/client/src/components/ui/Loader.jsx:
--------------------------------------------------------------------------------
1 | import { FaPizzaSlice } from 'react-icons/fa';
2 |
3 | function Loader() {
4 | return (
5 | <>
6 |
7 |
14 |
15 |
16 |
Loading...
17 |
18 |
19 | >
20 | );
21 | }
22 |
23 | export default Loader;
24 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pizza-delivery-system-app-client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.5",
14 | "axios": "^1.4.0",
15 | "prop-types": "^15.8.1",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "react-icons": "^4.10.1",
19 | "react-redux": "^8.1.2",
20 | "react-router-dom": "^6.15.0",
21 | "react-scroll": "^1.8.9"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.2.15",
25 | "@types/react-dom": "^18.2.7",
26 | "@vitejs/plugin-react": "^4.0.3",
27 | "autoprefixer": "^10.4.15",
28 | "eslint": "^8.45.0",
29 | "eslint-plugin-react": "^7.32.2",
30 | "eslint-plugin-react-hooks": "^4.6.0",
31 | "eslint-plugin-react-refresh": "^0.4.3",
32 | "postcss": "^8.4.28",
33 | "tailwindcss": "^3.3.3",
34 | "vite": "^4.4.5",
35 | "vite-plugin-pwa": "^0.17.4",
36 | "workbox-window": "^7.0.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pizza-delivery-system-app-server",
3 | "version": "1.0.0",
4 | "description": "Backend for Pizza Delivery System.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "dev": "nodemon index.js",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP.git"
14 | },
15 | "author": "Muhammad Saad",
16 | "license": "CC0-1.0",
17 | "bugs": {
18 | "url": "https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP/issues"
19 | },
20 | "homepage": "https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP#readme",
21 | "dependencies": {
22 | "@getbrevo/brevo": "^1.0.1",
23 | "bcryptjs": "^2.4.3",
24 | "body-parser": "^1.20.2",
25 | "colors": "^1.4.0",
26 | "cors": "^2.8.5",
27 | "dotenv": "^16.3.1",
28 | "email-validator": "^2.0.4",
29 | "express": "^4.18.2",
30 | "express-async-handler": "^1.2.0",
31 | "jsonwebtoken": "^9.0.1",
32 | "mongoose": "^7.4.3",
33 | "morgan": "^1.10.0",
34 | "nodemailer": "^6.9.4",
35 | "razorpay": "^2.9.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | // Initialize Express Router
4 | const router = express.Router();
5 |
6 | // Import Middlewares
7 | const { protect, admin } = require('../middlewares/authMiddlewares');
8 |
9 | // Import Controllers
10 | const {
11 | authUser,
12 | registerUser,
13 | verifyUser,
14 | forgotPassword,
15 | resetPassword,
16 | getUserProfile,
17 | updateUserProfile,
18 | getAllUsers,
19 | getUserById,
20 | updateUserById,
21 | deleteUserById,
22 | } = require('../controllers/userControllers');
23 |
24 | // Initialize Routes
25 |
26 | // Public Routes
27 | router.post('/login', authUser);
28 | router.post('/register', registerUser);
29 | router.post('/forgotpassword', forgotPassword);
30 | router.put('/resetpassword', resetPassword);
31 |
32 | // Private Routes
33 | router.post('/verify', protect, verifyUser);
34 | router
35 | .route('/profile')
36 | .get(protect, getUserProfile)
37 | .put(protect, updateUserProfile);
38 |
39 | // Admin + Private Routes
40 | router.get('/', protect, admin, getAllUsers);
41 | router
42 | .route('/:id')
43 | .get(protect, admin, getUserById)
44 | .put(protect, admin, updateUserById)
45 | .delete(protect, admin, deleteUserById);
46 |
47 | // Export Router
48 | module.exports = router;
49 |
--------------------------------------------------------------------------------
/server/data/users.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcryptjs');
2 |
3 | const admins = [
4 | {
5 | name: 'Admin User 1',
6 | email: 'admin1@pizzapalette.com',
7 | password: bcrypt.hashSync('123456', 10),
8 | role: 'admin',
9 | permissions: ['admin'],
10 | isApproved: true,
11 | },
12 | {
13 | name: 'Admin User 2',
14 | email: 'admin2@pizzapalette.com',
15 | password: bcrypt.hashSync('123456', 10),
16 | role: 'admin',
17 | permissions: ['admin'],
18 | isApproved: true,
19 | },
20 | ];
21 |
22 | const users = [
23 | {
24 | name: 'John Doe',
25 | email: 'john@example.com',
26 | password: bcrypt.hashSync('123456', 10),
27 | phoneNumber: '123-456-7890',
28 | address: '123 Main St, City',
29 | orders: [],
30 | isVerified: true,
31 | verificationCode: '123456',
32 | resetPasswordToken: null,
33 | resetPasswordExpire: null,
34 | },
35 | {
36 | name: 'Jane Doe',
37 | email: 'jane@example.com',
38 | password: bcrypt.hashSync('123456', 10),
39 | phoneNumber: '123-456-7890',
40 | address: '123 Main St, City',
41 | orders: [],
42 | isVerified: true,
43 | verificationCode: '098765',
44 | resetPasswordToken: null,
45 | resetPasswordExpire: null,
46 | },
47 | ];
48 |
49 | module.exports = { admins, users };
50 |
--------------------------------------------------------------------------------
/client/src/components/ui/CheckoutSteps/CurrentCheckoutStep.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { FaChevronRight } from 'react-icons/fa';
3 |
4 | function CurrentCheckoutStep({ currentStep, AllSteps }) {
5 | return (
6 |
7 | {AllSteps.map((step, index) => (
8 |
9 |
16 | {step.icon}
17 | {step.step}
18 |
19 | {index !== AllSteps.length - 1 && (
20 | <>
21 |
22 | >
23 | )}
24 |
25 | ))}
26 |
27 | );
28 | }
29 |
30 | CurrentCheckoutStep.propTypes = {
31 | currentStep: PropTypes.string.isRequired,
32 | AllSteps: PropTypes.array.isRequired,
33 | };
34 |
35 | export default CurrentCheckoutStep;
36 |
--------------------------------------------------------------------------------
/client/src/screens/HomeScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | // Import Thunks
5 | import { getUserDetails } from '../redux/asyncThunks/userThunks';
6 |
7 | // Import Components
8 | import FeaturedPizzasSection from '../components/ui/Home/FeaturedPizzasSection';
9 | import HowItWorksSection from '../components/ui/Home/HowItWorksSection';
10 | import Jumbotron from '../components/ui/Home/Jumbotron';
11 | import VerficationModal from '../components/ui/Auth/VerficationModal';
12 |
13 | function HomeScreen() {
14 | const [modalVisible, setModalVisible] = useState(false);
15 |
16 | const dispatch = useDispatch();
17 |
18 | const user = useSelector((state) => state.user);
19 | const { userDetails } = user;
20 |
21 | useEffect(() => {
22 | if (!userDetails) {
23 | dispatch(getUserDetails({}));
24 | }
25 |
26 | if (userDetails && !userDetails.isVerified) {
27 | setModalVisible(true);
28 | }
29 | }, [dispatch, userDetails]);
30 |
31 | return (
32 | <>
33 |
34 |
35 |
36 | {modalVisible && (
37 | setModalVisible(false)} />
38 | )}
39 | >
40 | );
41 | }
42 |
43 | export default HomeScreen;
44 |
--------------------------------------------------------------------------------
/client/src/components/ui/Profile/Profile.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | const Profile = ({ user }) => {
4 | const { name, email, address, phoneNumber, isVerified } = user;
5 |
6 | return (
7 |
8 |
9 | Hello! {name}
10 |
11 |
12 | Email: {' '}
13 | {email}
14 |
15 |
16 | Address: {' '}
17 | {address}
18 |
19 |
20 | Phone: {' '}
21 | {phoneNumber}
22 |
23 |
24 |
25 | Account Status:
26 | {' '}
27 |
28 | {isVerified ? 'Verified' : 'Not Verified'}
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | Profile.propTypes = {
36 | user: PropTypes.object.isRequired,
37 | };
38 |
39 | export default Profile;
40 |
--------------------------------------------------------------------------------
/server/schemas/pizzaSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const pizzaSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | description: {
10 | type: String,
11 | required: true,
12 | },
13 | bases: [
14 | {
15 | type: mongoose.Schema.Types.ObjectId,
16 | ref: 'Base',
17 | },
18 | ],
19 | sauces: [
20 | {
21 | type: mongoose.Schema.Types.ObjectId,
22 | ref: 'Sauce',
23 | },
24 | ],
25 | cheeses: [
26 | {
27 | type: mongoose.Schema.Types.ObjectId,
28 | ref: 'Cheese',
29 | },
30 | ],
31 | veggies: [
32 | {
33 | type: mongoose.Schema.Types.ObjectId,
34 | ref: 'Veggie',
35 | },
36 | ],
37 | price: {
38 | type: Number,
39 | required: true,
40 | },
41 | size: {
42 | type: String,
43 | enum: ['small', 'medium', 'large', 'extra-large'],
44 | required: true,
45 | },
46 | createdBy: {
47 | type: String,
48 | enum: ['admin', 'user'],
49 | required: true,
50 | },
51 | imageUrl: {
52 | type: String,
53 | required: true,
54 | },
55 | },
56 | {
57 | timestamps: true,
58 | }
59 | );
60 |
61 | module.exports = mongoose.model('Pizza', pizzaSchema);
62 |
--------------------------------------------------------------------------------
/client/src/components/ui/Button.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | function Button({
4 | variant,
5 | children,
6 | className: additionalClassNames,
7 | ...props
8 | }) {
9 | let classes = '';
10 |
11 | switch (variant) {
12 | case 'primary':
13 | classes = 'bg-orange-500 hover:bg-orange-600 text-white py-3 px-4 my-2';
14 | break;
15 | case 'secondary':
16 | classes = 'bg-white hover:bg-gray-100 text-orange-500 py-3 px-4 my-2';
17 | break;
18 | case 'outline':
19 | classes =
20 | 'bg-transparent hover:bg-orange-500 text-orange-500 hover:text-white border-2 border-orange-500 py-3 px-4 my-2 transition-all duration-300';
21 | break;
22 | case 'danger':
23 | classes = 'bg-red-500 hover:bg-red-600 text-white py-3 px-4 my-2';
24 | break;
25 | default:
26 | classes = 'bg-orange-500 hover:bg-orange-600 py-3 px-4 my-2';
27 | break;
28 | }
29 |
30 | if (additionalClassNames) {
31 | classes += ` ${additionalClassNames}`;
32 | }
33 |
34 | return (
35 |
36 | {children}
37 |
38 | );
39 | }
40 |
41 | Button.propTypes = {
42 | variant: PropTypes.oneOf(['primary', 'secondary', 'outline', 'danger']),
43 | children: PropTypes.node.isRequired,
44 | className: PropTypes.string,
45 | };
46 |
47 | export default Button;
48 |
--------------------------------------------------------------------------------
/server/middlewares/nodemailerMiddleware.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 | const dotenv = require('dotenv');
3 | const emailBaseTemplate = require('../utils/emailBaseTemplate');
4 |
5 | dotenv.config();
6 |
7 | const transporter = nodemailer.createTransport({
8 | service: 'gmail',
9 | host: 'smtp.gmail.com',
10 | port: 465,
11 | secure: true,
12 | auth: {
13 | user: `${process.env.SENDER_EMAIL}`,
14 | pass: `${process.env.SENDER_PASSWORD}`,
15 | },
16 | });
17 |
18 | /**
19 | * sendEmail - Sends a professional HTML email using the base template
20 | * @param {Object} options - { to, subject, templateOptions, from, text }
21 | * templateOptions: { title, greeting, message, actionText, actionUrl, closing, signature, extra }
22 | */
23 | const sendEmail = async ({ to, subject, templateOptions = {}, from, text }) => {
24 | try {
25 | const html = emailBaseTemplate(templateOptions);
26 | const mailOptions = {
27 | from: from || process.env.SENDER_EMAIL,
28 | to,
29 | subject,
30 | html,
31 | text: text || templateOptions.message || '',
32 | };
33 | const response = await transporter.sendMail(mailOptions);
34 | return response;
35 | } catch (error) {
36 | console.error('Error sending email:', error);
37 | throw new Error('Email could not be sent!');
38 | }
39 | };
40 |
41 | module.exports = sendEmail;
42 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/InventoryTable.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | function InventoryTable({ title, items }) {
4 | return (
5 |
6 |
7 | {title.charAt(0).toUpperCase() + title.slice(1)}
8 |
9 |
10 |
11 |
12 | Stock Item
13 | Quantity
14 |
15 |
16 |
17 | {items.map((item) => (
18 |
19 |
20 | {item.item}
21 |
22 |
23 | {item.quantity}
24 |
25 |
26 | ))}
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | InventoryTable.propTypes = {
34 | title: PropTypes.string.isRequired,
35 | items: PropTypes.arrayOf(PropTypes.object).isRequired,
36 | };
37 |
38 | export default InventoryTable;
39 |
--------------------------------------------------------------------------------
/server/data/pizzas.js:
--------------------------------------------------------------------------------
1 | const pizzas = [
2 | {
3 | name: 'Margherita',
4 | description: 'Tomato sauce, mozzarella, and oregano',
5 | base: 'Classic',
6 | sauces: ['Tomato'],
7 | cheeses: ['Mozzarella'],
8 | veggies: ['Oregano'],
9 | price: 6.95,
10 | size: 'small',
11 | imageUrl: '/images/margherita.jpg',
12 | },
13 | {
14 | name: 'Marinara',
15 | description: 'Tomato sauce, garlic and basil',
16 | base: 'Classic',
17 | sauces: ['Tomato'],
18 | cheeses: [],
19 | veggies: ['Garlic', 'Basil'],
20 | price: 6.95,
21 | size: 'small',
22 | imageUrl: '/images/marinara.jpg',
23 | },
24 | {
25 | name: 'Quattro Formaggi',
26 | description:
27 | 'Tomato sauce, mozzarella, parmesan, gorgonzola cheese, artichokes and basil',
28 | base: 'Classic',
29 | sauces: ['Tomato'],
30 | cheeses: ['Mozzarella', 'Parmesan', 'Gorgonzola'],
31 | veggies: ['Artichokes', 'Basil'],
32 | price: 8.95,
33 | size: 'small',
34 | imageUrl: '/images/quattro-formaggi.jpg',
35 | },
36 | {
37 | name: 'Carbonara',
38 | description: 'Tomato sauce, mozzarella, parmesan, eggs, and bacon',
39 | base: 'Classic',
40 | sauces: ['Tomato'],
41 | cheeses: ['Mozzarella', 'Parmesan'],
42 | veggies: ['Eggs', 'Bacon'],
43 | price: 8.95,
44 | size: 'small',
45 | imageUrl: '/images/carbonara.jpg',
46 | },
47 | ];
48 |
49 | module.exports = pizzas;
50 |
--------------------------------------------------------------------------------
/server/schemas/userSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // Define the user schema
4 | const userSchema = new mongoose.Schema(
5 | {
6 | // Basic Information
7 | name: {
8 | type: String,
9 | required: true,
10 | },
11 | email: {
12 | type: String,
13 | required: true,
14 | unique: true,
15 | },
16 | password: {
17 | type: String,
18 | required: true,
19 | },
20 |
21 | // Optional Information
22 | phoneNumber: { type: String }, // User's phone number (optional)
23 | address: { type: String }, // User's address (optional)
24 |
25 | // Orders
26 | orders: [
27 | {
28 | type: mongoose.Schema.Types.ObjectId,
29 | ref: 'Order', // Referencing the 'Order' model
30 | },
31 | ],
32 |
33 | // Email Verification
34 | isVerified: {
35 | type: Boolean,
36 | default: false, // Email verification status (default is false)
37 | },
38 |
39 | verificationCode: {
40 | type: String,
41 | default: null,
42 | },
43 |
44 | // Password Reset
45 | resetPasswordToken: {
46 | type: String,
47 | },
48 | resetPasswordExpire: {
49 | type: Date,
50 | },
51 | },
52 | {
53 | // Timestamps
54 | timestamps: true, // Adds createdAt and updatedAt timestamps
55 | }
56 | );
57 |
58 | // Export the user schema model
59 | module.exports = mongoose.model('User', userSchema);
60 |
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pizza-delivery-system-app-mern",
3 | "version": "1.0.0",
4 | "description": "A Full-Stack Web Application that simulates a Dynamic Pizza Delivery System.",
5 | "scripts": {
6 | "start": "node server/index --prefix server",
7 | "server": "nodemon server/index --prefix server",
8 | "client": "npm run dev --prefix client",
9 | "build": "cd client && npm run build",
10 | "dev": "concurrently \"npm run server\" \"npm run client\"",
11 | "install": "cd client && npm install && cd .. && cd server && npm install && cd ..",
12 | "data:import": "node server/seeder",
13 | "data:destroy": "node server/seeder -d"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP.git"
18 | },
19 | "keywords": [
20 | "nodejs",
21 | "mongodb",
22 | "reactjs",
23 | "expressjs",
24 | "mern-stack",
25 | "pizza-delivery-system",
26 | "internship-task",
27 | "oasis-internship",
28 | "oibsip"
29 | ],
30 | "author": "Muhammad Saad",
31 | "license": "CC0-1.0",
32 | "bugs": {
33 | "url": "https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP/issues"
34 | },
35 | "homepage": "https://github.com/itxSaaad/pizza-delivery-system-app-mern-OIBSIP#readme",
36 | "devDependencies": {
37 | "concurrently": "^8.2.0"
38 | },
39 | "dependencies": {
40 | "eslint": "^8.47.0",
41 | "nodemon": "^3.0.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/src/components/ui/Cart/AddToCartButton.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect, useState } from 'react';
3 | import { FaCartPlus } from 'react-icons/fa';
4 | import { useDispatch, useSelector } from 'react-redux';
5 |
6 | // Import Thunks
7 | import { getUserDetails } from '../../../redux/asyncThunks/userThunks';
8 | import { addToCart } from '../../../redux/slices/cartSlice';
9 |
10 | // Import Components
11 | import Button from '../Button';
12 |
13 | function AddToCartButton({ id, qty }) {
14 | const dispatch = useDispatch();
15 |
16 | const user = useSelector((state) => state.user);
17 | const { userDetails } = user;
18 |
19 | const handleAddToCart = () => {
20 | if (userDetails && !userDetails.isVerified) {
21 | alert('Please verify your email address first!');
22 | } else {
23 | dispatch(addToCart({ id, qty }));
24 | }
25 | };
26 |
27 | useEffect(() => {
28 | if (!userDetails) {
29 | dispatch(getUserDetails({}));
30 | }
31 | }, [dispatch, userDetails]);
32 |
33 | return (
34 | <>
35 |
40 |
41 | Add to Cart
42 |
43 | >
44 | );
45 | }
46 |
47 | AddToCartButton.propTypes = {
48 | id: PropTypes.string.isRequired,
49 | qty: PropTypes.number.isRequired,
50 | };
51 |
52 | export default AddToCartButton;
53 |
--------------------------------------------------------------------------------
/client/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Pizza Palette",
3 | "short_name": "Pizza Palette",
4 | "description": "A comprehensive web application that enables users to customize and order pizzas online.",
5 | "icons": [
6 | {
7 | "src": "/android-chrome-512x512.png",
8 | "sizes": "512x512",
9 | "type": "image/png",
10 | "purpose": "any"
11 | },
12 | {
13 | "src": "/android-chrome-192x192.png",
14 | "sizes": "192x192",
15 | "type": "image/png",
16 | "purpose": "any"
17 | },
18 | {
19 | "src": "/apple-touch-icon.png",
20 | "sizes": "180x180",
21 | "type": "image/png",
22 | "purpose": "any"
23 | },
24 | {
25 | "src": "/favicon-32x32.png",
26 | "sizes": "32x32",
27 | "type": "image/png",
28 | "purpose": "any"
29 | },
30 | {
31 | "src": "/favicon-16x16.png",
32 | "sizes": "16x16",
33 | "type": "image/png",
34 | "purpose": "any"
35 | },
36 | {
37 | "src": "/favicon.ico",
38 | "sizes": "32x32 16x16",
39 | "type": "image/x-icon",
40 | "purpose": "any"
41 | }
42 | ],
43 | "screenshots": [
44 | {
45 | "src": "/images/screen1.png",
46 | "sizes": "2880x1800",
47 | "type": "image/png",
48 | "description": "A Screenshot of the Homepage"
49 | }
50 | ],
51 | "theme_color": "#ffffff",
52 | "background_color": "#ffffff",
53 | "orientation": "portrait",
54 | "display": "standalone",
55 | "scope": "/",
56 | "start_url": "/"
57 | }
58 |
--------------------------------------------------------------------------------
/server/utils/inventoryUtils.js:
--------------------------------------------------------------------------------
1 | // Import Schema
2 | const { Base, Sauce, Cheese, Veggie } = require('../schemas/inventorySchema');
3 |
4 | // Update inventory quantity for a given item
5 | const updateInventoryQuantity = async (pizza, qty) => {
6 | const { bases, sauces, cheeses, veggies } = pizza;
7 |
8 | const updateQuantity = async (item) => {
9 | if (item) {
10 | if (item.quantity >= qty) {
11 | item.quantity -= qty;
12 | const updateditem = await item.save();
13 | return updateditem;
14 | } else {
15 | throw new Error(
16 | `Not enough ${item.name} in inventory! Please update inventory!`
17 | );
18 | }
19 | } else {
20 | throw new Error('Item Not Found!');
21 | }
22 | };
23 |
24 | // Update base quantity
25 | for (const baseId of bases) {
26 | const baseItem = await Base.findById(baseId);
27 | await updateQuantity(baseItem);
28 | }
29 |
30 | // Update sauce quantity
31 | for (const sauceId of sauces) {
32 | const sauceItem = await Sauce.findById(sauceId);
33 | await updateQuantity(sauceItem);
34 | }
35 |
36 | // Update cheese quantity
37 | for (const cheeseId of cheeses) {
38 | const cheeseItem = await Cheese.findById(cheeseId);
39 | await updateQuantity(cheeseItem);
40 | }
41 | // Update veggie quantity
42 | for (const veggieId of veggies) {
43 | const veggieItem = await Veggie.findById(veggieId);
44 | await updateQuantity(veggieItem);
45 | }
46 | };
47 |
48 | module.exports = { updateInventoryQuantity };
49 |
--------------------------------------------------------------------------------
/server/schemas/inventorySchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const baseSchema = new mongoose.Schema(
4 | {
5 | item: { type: String, required: true },
6 | quantity: { type: Number, required: true },
7 | price: { type: Number, required: true },
8 | threshold: { type: Number, default: 10 },
9 | },
10 | {
11 | timestamps: true,
12 | }
13 | );
14 |
15 | const sauceSchema = new mongoose.Schema(
16 | {
17 | item: { type: String, required: true },
18 | quantity: { type: Number, required: true },
19 | price: { type: Number, required: true },
20 | threshold: { type: Number, default: 10 },
21 | },
22 | {
23 | timestamps: true,
24 | }
25 | );
26 |
27 | const cheeseSchema = new mongoose.Schema(
28 | {
29 | item: { type: String, required: true },
30 | quantity: { type: Number, required: true },
31 | price: { type: Number, required: true },
32 | threshold: { type: Number, default: 10 },
33 | },
34 | {
35 | timestamps: true,
36 | }
37 | );
38 |
39 | const veggieSchema = new mongoose.Schema(
40 | {
41 | item: { type: String, required: true },
42 | quantity: { type: Number, required: true },
43 | price: { type: Number, required: true },
44 | threshold: { type: Number, default: 10 },
45 | },
46 | {
47 | timestamps: true,
48 | }
49 | );
50 |
51 | module.exports = {
52 | Base: mongoose.model('Base', baseSchema),
53 | Sauce: mongoose.model('Sauce', sauceSchema),
54 | Cheese: mongoose.model('Cheese', cheeseSchema),
55 | Veggie: mongoose.model('Veggie', veggieSchema),
56 | };
57 |
--------------------------------------------------------------------------------
/client/src/components/ui/Home/Jumbotron.jsx:
--------------------------------------------------------------------------------
1 | import { BsChevronDoubleDown } from 'react-icons/bs';
2 | import { Link } from 'react-router-dom';
3 | import { Link as ScrollLink } from 'react-scroll';
4 |
5 | import BGImage from '/images/pizza-jumbo-bg.jpg';
6 |
7 | function Jumbotron() {
8 | return (
9 |
18 | It's Pizza Time
19 |
20 | Craving for a pizza? You are in the right place!
21 |
22 |
26 | Order Now
27 |
28 |
29 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | export default Jumbotron;
43 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/SideBar/SideBar.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | function SideBar({
4 | menuItems,
5 | activeMenuItem,
6 | handleMenuItemClick,
7 | collapsible,
8 | }) {
9 | return (
10 |
15 |
16 |
17 | Dashboard
18 |
19 |
20 |
21 |
22 | {menuItems.map((item, index) => (
23 |
24 | handleMenuItemClick(item.name)}
26 | className={`flex flex-row items-center justify-center border border-orange-600 text-white w-full px-3 py-2 rounded-md text-base hover:font-bold ${
27 | activeMenuItem === item.name
28 | ? 'bg-orange-900'
29 | : 'hover:bg-orange-900'
30 | }`}
31 | >
32 | {item.icon}
33 | {item.name}
34 |
35 |
36 | ))}
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | SideBar.propTypes = {
44 | menuItems: PropTypes.array.isRequired,
45 | activeMenuItem: PropTypes.string.isRequired,
46 | handleMenuItemClick: PropTypes.func.isRequired,
47 | collapsible: PropTypes.bool.isRequired,
48 | };
49 |
50 | export default SideBar;
51 |
--------------------------------------------------------------------------------
/server/seeder.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const dotenv = require('dotenv');
3 | const colors = require('colors');
4 |
5 | const connectDb = require('./config/db');
6 |
7 | const Admin = require('./schemas/adminUserSchema');
8 | const User = require('./schemas/userSchema');
9 | const Pizza = require('./schemas/pizzaSchema');
10 | const Order = require('./schemas/orderSchema');
11 | const { Base, Sauce, Cheese, Veggie } = require('./schemas/inventorySchema');
12 |
13 | const { users, admins } = require('./data/users');
14 | const pizzas = require('./data/pizzas');
15 | const { base, sauce, cheese, veggie } = require('./data/inventory');
16 |
17 | dotenv.config();
18 |
19 | // Connect to MongoDB
20 | connectDb();
21 |
22 | const importData = async () => {
23 | try {
24 | await User.insertMany(users);
25 | await Admin.insertMany(admins);
26 | await Base.insertMany(base);
27 | await Sauce.insertMany(sauce);
28 | await Cheese.insertMany(cheese);
29 | await Veggie.insertMany(veggie);
30 | await Pizza.insertMany(pizzas);
31 |
32 | console.log('Dummy Data Created!'.green.inverse);
33 | process.exit();
34 | } catch (error) {
35 | console.error(`${error}`.red.inverse);
36 | process.exit(1);
37 | }
38 | };
39 |
40 | const destroyData = async () => {
41 | try {
42 | await Admin.deleteMany();
43 | await Order.deleteMany();
44 | await Pizza.deleteMany();
45 | await User.deleteMany();
46 | await Base.deleteMany();
47 | await Sauce.deleteMany();
48 | await Cheese.deleteMany();
49 | await Veggie.deleteMany();
50 |
51 | console.log('Data Destroyed!'.red.inverse);
52 | process.exit();
53 | } catch (error) {
54 | console.error(`${error}`.red.inverse);
55 | process.exit(1);
56 | }
57 | };
58 |
59 | if (process.argv[2] === '-d') {
60 | destroyData();
61 | } else {
62 | importData();
63 | }
64 |
--------------------------------------------------------------------------------
/server/middlewares/authMiddlewares.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const asyncHandler = require('express-async-handler');
3 |
4 | const User = require('../schemas/userSchema');
5 | const Admin = require('../schemas/adminUserSchema');
6 |
7 | // Middleware to protect routes - checks for a valid JWT token in the request header
8 | const protect = asyncHandler(async (req, res, next) => {
9 | let token;
10 |
11 | // Check if Authorization header with 'Bearer' token is present
12 | if (
13 | req.headers.authorization &&
14 | req.headers.authorization.startsWith('Bearer')
15 | ) {
16 | try {
17 | // Extract token from the header
18 | token = req.headers.authorization.split(' ')[1];
19 |
20 | // Verify the token using JWT
21 | const decoded = jwt.verify(token, `${process.env.JWT_SECRET}`);
22 |
23 | // Find the user in the database using the decoded token
24 | req.user =
25 | (await User.findById(decoded.id).select('-password')) ||
26 | (await Admin.findById(decoded.id).select('-password'));
27 |
28 | // Continue to the next middleware
29 | next();
30 | } catch (error) {
31 | console.error(error);
32 | res.status(401);
33 | throw new Error('Not Authorized, Token Failed!');
34 | }
35 | }
36 |
37 | // If no token is present
38 | if (!token) {
39 | res.status(401);
40 | throw new Error('Not Authorized, No Token!');
41 | }
42 | });
43 |
44 | // Middleware to check if the user is an admin
45 | const admin = asyncHandler(async (req, res, next) => {
46 | // Check if the user is authenticated and has admin privileges
47 | if (req.user && req.user.role === 'admin') {
48 | // User is an admin, continue to the next middleware
49 | next();
50 | } else {
51 | res.status(401);
52 | throw new Error('Not Authorized As An Admin!');
53 | }
54 | });
55 |
56 | module.exports = { protect, admin };
57 |
--------------------------------------------------------------------------------
/client/src/screens/User/ForgetPasswordScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | // Import Components
4 | import EmailForm from '../../components/ui/Auth/ForgetPassword/EmailForm';
5 | import PasswordForm from '../../components/ui/Auth/ForgetPassword/PasswordForm';
6 | import OTPForm from '../../components/ui/Auth/ForgetPassword/OTPForm';
7 | import Logo from '/android-chrome-512x512.png';
8 |
9 | function ForgetPasswordScreen() {
10 | const [currentStep, setCurrentStep] = useState('EmailForm'); // 'EmailForm' | 'OTPForm' | 'PasswordForm
11 | return (
12 |
13 |
14 |
15 |
20 |
21 | Forgot Password?
22 |
23 | No Worries!
24 |
25 |
26 |
27 |
28 | {currentStep === 'EmailForm' && (
29 |
30 | )}
31 | {currentStep === 'OTPForm' && (
32 |
33 | )}
34 | {currentStep === 'PasswordForm' && (
35 |
36 | )}
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default ForgetPasswordScreen;
44 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/MainContent.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useSelector } from 'react-redux';
3 |
4 | // Import Components
5 | import Home from './Home';
6 | import InventoryList from './Lists/InventoryList';
7 | import OrdersList from './Lists/OrdersList';
8 | import PizzasList from './Lists/PizzasList';
9 | import StaffList from './Lists/StaffList';
10 | import UsersList from './Lists/UsersList';
11 | import SideBarToggleButton from './SideBar/SideBarToggleButton';
12 |
13 | function MainContent({ activeMenuItem, collapsible, onToggleSidebar }) {
14 | const admin = useSelector((state) => state.admin);
15 | const { adminUserInfo } = admin;
16 |
17 | const formattedUserName = adminUserInfo.name
18 | .split(' ')
19 | .map((name) => name.charAt(0).toUpperCase() + name.slice(1))
20 | .join(' ');
21 |
22 | return (
23 |
28 |
29 |
30 |
31 | Welcome to Admin Panel
32 | {formattedUserName} !
33 |
34 | {activeMenuItem === 'Home' &&
}
35 | {activeMenuItem === 'Staff' &&
}
36 | {activeMenuItem === 'Users' &&
}
37 | {activeMenuItem === 'Pizzas' &&
}
38 | {activeMenuItem === 'Orders' &&
}
39 | {activeMenuItem === 'Inventory' &&
}
40 |
41 |
42 | );
43 | }
44 |
45 | MainContent.propTypes = {
46 | activeMenuItem: PropTypes.string.isRequired,
47 | collapsible: PropTypes.bool.isRequired,
48 | onToggleSidebar: PropTypes.func.isRequired,
49 | };
50 |
51 | export default MainContent;
52 |
--------------------------------------------------------------------------------
/client/src/screens/User/UserLoginScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { Link, useNavigate } from 'react-router-dom';
4 |
5 | import UserLoginForm from '../../components/ui/Auth/UserLoginForm';
6 | import Logo from '/android-chrome-512x512.png';
7 |
8 | function UserLoginScreen() {
9 | const navigate = useNavigate();
10 |
11 | const user = useSelector((state) => state.user);
12 | const { userInfo } = user;
13 |
14 | const admin = useSelector((state) => state.admin);
15 | const { adminUserInfo } = admin;
16 |
17 | useEffect(() => {
18 | if (userInfo || adminUserInfo) {
19 | navigate('/');
20 | }
21 | }, [navigate, userInfo, adminUserInfo]);
22 |
23 | return (
24 |
25 |
26 |
27 |
32 |
33 | Login! to get started.
34 |
35 |
36 |
37 |
38 |
39 |
40 | No Account?{' '}
41 |
45 | Sign up
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default UserLoginScreen;
55 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Pizza Palette | MERN
13 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/server/schemas/orderSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const orderSchema = new mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | ref: 'User',
8 | required: true,
9 | },
10 | orderItems: [
11 | {
12 | pizza: {
13 | type: mongoose.Schema.Types.ObjectId,
14 | ref: 'Pizza',
15 | required: true,
16 | },
17 | qty: {
18 | type: Number,
19 | required: true,
20 | },
21 | price: {
22 | type: Number,
23 | required: true,
24 | },
25 | },
26 | ],
27 | deliveryAddress: {
28 | phoneNumber: {
29 | type: String,
30 | required: true,
31 | },
32 | address: {
33 | type: String,
34 | required: true,
35 | },
36 | city: {
37 | type: String,
38 | required: true,
39 | },
40 | postalCode: {
41 | type: String,
42 | required: true,
43 | },
44 | country: {
45 | type: String,
46 | required: true,
47 | },
48 | },
49 | salesTax: {
50 | type: Number,
51 | default: 0,
52 | },
53 | deliveryCharges: {
54 | type: Number,
55 | default: 0,
56 | },
57 | totalPrice: {
58 | type: Number,
59 | required: true,
60 | },
61 | payment: {
62 | method: {
63 | type: String,
64 | enum: ['stripe', 'razorpay'],
65 | required: true,
66 | },
67 | stripePaymentIntentId: { type: String }, // For Stripe payments
68 | razorpayOrderId: { type: String }, // For Razorpay payments
69 | status: { type: String }, // Payment status
70 | },
71 | status: {
72 | type: String,
73 | enum: ['Received', 'In the Kitchen', 'Sent for Delivery', 'Delivered'],
74 | default: 'Received',
75 | },
76 | deliveredAt: {
77 | type: Date,
78 | },
79 | },
80 | {
81 | timestamps: true,
82 | }
83 | );
84 |
85 | module.exports = mongoose.model('Order', orderSchema);
86 |
--------------------------------------------------------------------------------
/client/src/screens/User/UserRegisterScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { Link, useNavigate } from 'react-router-dom';
4 |
5 | import UserRegisterForm from '../../components/ui/Auth/UserRegisterForm';
6 | import Logo from '/android-chrome-512x512.png';
7 |
8 | function UserRegisterScreen() {
9 | const navigate = useNavigate();
10 |
11 | const user = useSelector((state) => state.user);
12 | const { userInfo } = user;
13 |
14 | const admin = useSelector((state) => state.admin);
15 | const { adminUserInfo } = admin;
16 |
17 | useEffect(() => {
18 | if (userInfo || adminUserInfo) {
19 | navigate('/');
20 | }
21 | }, [navigate, userInfo, adminUserInfo]);
22 | return (
23 |
24 |
25 |
26 |
31 |
32 | Register Now! to enjoy our
33 | services.
34 |
35 |
36 |
37 |
38 |
39 |
40 | Already have Account?{' '}
41 |
45 | Sign in Here
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default UserRegisterScreen;
55 |
--------------------------------------------------------------------------------
/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 | import { VitePWA } from 'vite-plugin-pwa';
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | react(),
9 | VitePWA({
10 | registerType: 'prompt',
11 | includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
12 | manifest: {
13 | name: 'Pizza Palette | MERN',
14 | short_name: 'Pizza Palette',
15 | description:
16 | 'Order your favorite pizza Now! We deliver the best pizza in town.',
17 | icons: [
18 | {
19 | src: '/android-chrome-512x512.png',
20 | sizes: '512x512',
21 | type: 'image/png',
22 | purpose: 'any',
23 | },
24 | {
25 | src: '/android-chrome-192x192.png',
26 | sizes: '192x192',
27 | type: 'image/png',
28 | purpose: 'any',
29 | },
30 | {
31 | src: '/apple-touch-icon.png',
32 | sizes: '180x180',
33 | type: 'image/png',
34 | purpose: 'any',
35 | },
36 | {
37 | src: '/favicon-32x32.png',
38 | sizes: '32x32',
39 | type: 'image/png',
40 | purpose: 'any',
41 | },
42 | {
43 | src: '/favicon-16x16.png',
44 | sizes: '16x16',
45 | type: 'image/png',
46 | purpose: 'any',
47 | },
48 | {
49 | src: '/favicon.ico',
50 | sizes: '32x32 16x16',
51 | type: 'image/x-icon',
52 | purpose: 'any',
53 | },
54 | ],
55 | screenshots: [
56 | {
57 | src: '/images/screen1.png',
58 | sizes: '2880x1800',
59 | type: 'image/png',
60 | },
61 | ],
62 | theme_color: '#ffffff',
63 | background_color: '#ffffff',
64 | display: 'standalone',
65 | scope: '/',
66 | start_url: '/',
67 | orientation: 'portrait',
68 | },
69 | }),
70 | ],
71 | });
72 |
--------------------------------------------------------------------------------
/server/data/inventory.js:
--------------------------------------------------------------------------------
1 | const base = [
2 | {
3 | item: 'Thin',
4 | quantity: 100,
5 | price: 0.1,
6 | threshold: 10,
7 | },
8 | {
9 | item: 'Thick',
10 | quantity: 100,
11 | price: 0.1,
12 | threshold: 10,
13 | },
14 | {
15 | item: 'Cheese Stuffed',
16 | quantity: 100,
17 | price: 0.1,
18 | threshold: 10,
19 | },
20 | {
21 | item: 'Deep Dish',
22 | quantity: 100,
23 | price: 0.1,
24 | threshold: 10,
25 | },
26 | ];
27 |
28 | const sauce = [
29 | {
30 | item: 'Tomato',
31 | quantity: 100,
32 | price: 0.1,
33 | threshold: 10,
34 | },
35 | {
36 | item: 'Pesto',
37 | quantity: 100,
38 | price: 0.1,
39 | threshold: 10,
40 | },
41 | {
42 | item: 'Alfredo',
43 | quantity: 100,
44 | price: 0.1,
45 | threshold: 10,
46 | },
47 | {
48 | item: 'BBQ',
49 | quantity: 100,
50 | price: 0.1,
51 | threshold: 10,
52 | },
53 | {
54 | item: 'Buffalo',
55 | quantity: 100,
56 | price: 0.1,
57 | threshold: 10,
58 | },
59 | ];
60 |
61 | const cheese = [
62 | {
63 | item: 'Mozzarella',
64 | quantity: 100,
65 | price: 0.1,
66 | threshold: 10,
67 | },
68 | {
69 | item: 'Parmesan',
70 | quantity: 100,
71 | price: 0.1,
72 | threshold: 10,
73 | },
74 | {
75 | item: 'Cheddar',
76 | quantity: 100,
77 | price: 0.1,
78 | threshold: 10,
79 | },
80 | {
81 | item: 'Feta',
82 | quantity: 100,
83 | price: 0.1,
84 | threshold: 10,
85 | },
86 | ];
87 |
88 | const veggie = [
89 | {
90 | item: 'Artichokes',
91 | quantity: 100,
92 | price: 0.1,
93 | threshold: 10,
94 | },
95 | {
96 | item: 'Basil',
97 | quantity: 100,
98 | price: 0.1,
99 | threshold: 10,
100 | },
101 | {
102 | item: 'Black Olives',
103 | quantity: 100,
104 | price: 0.1,
105 | threshold: 10,
106 | },
107 | {
108 | item: 'Broccoli',
109 | quantity: 100,
110 | price: 0.1,
111 | threshold: 10,
112 | },
113 | ];
114 |
115 | module.exports = { base, sauce, cheese, veggie };
116 |
--------------------------------------------------------------------------------
/client/src/screens/Admin/AdminLoginScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { Link, useNavigate } from 'react-router-dom';
4 |
5 | import Logo from '/android-chrome-512x512.png';
6 | import AdminLoginForm from '../../components/ui/Admin/Auth/LoginForm';
7 |
8 | function AdminLoginScreen() {
9 | const navigate = useNavigate();
10 |
11 | const user = useSelector((state) => state.user);
12 | const { userInfo } = user;
13 |
14 | const admin = useSelector((state) => state.admin);
15 | const { adminUserInfo } = admin;
16 |
17 | useEffect(() => {
18 | if (userInfo) {
19 | navigate('/');
20 | }
21 | }, [navigate, userInfo]);
22 |
23 | useEffect(() => {
24 | if (adminUserInfo) {
25 | navigate('/admin/dashboard');
26 | }
27 | }, [navigate, adminUserInfo]);
28 |
29 | return (
30 |
31 |
32 |
33 |
38 |
39 | Welcome Staff! Login to get
40 | started.
41 |
42 |
43 |
44 |
45 |
46 |
47 | No Account?{' '}
48 |
52 | Sign up
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default AdminLoginScreen;
62 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/Lists/UsersList.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | // Import Thunks
5 | import {
6 | deleteUserById,
7 | listUsers,
8 | } from '../../../../../redux/asyncThunks/userThunks';
9 |
10 | // Import Components
11 | import Loader from '../../../Loader';
12 | import Message from '../../../Message';
13 | import Table from '../Table';
14 |
15 | function UsersList() {
16 | const userColumns = ['_id', 'name', 'email', 'numberOfOrders', 'isVerified'];
17 |
18 | const dispatch = useDispatch();
19 |
20 | const user = useSelector((state) => state.user);
21 | const {
22 | loading,
23 | userList,
24 | userListError,
25 | userDeleteByIdError,
26 | userDeleteByIdSuccess,
27 | } = user;
28 |
29 | const handleDelete = (id) => {
30 | dispatch(deleteUserById(id)).then(() => dispatch(listUsers({})));
31 | };
32 |
33 | const successMessageDelete = userDeleteByIdSuccess && {
34 | status: '200',
35 | message: 'User Deleted Successfully!',
36 | };
37 |
38 | useEffect(() => {
39 | if (!userList) {
40 | dispatch(listUsers({}));
41 | }
42 | }, [dispatch, userList]);
43 |
44 | return (
45 |
46 |
All Users
47 | {loading ? (
48 |
49 | ) : (
50 | <>
51 | {(userListError || userDeleteByIdError) && (
52 |
{userListError || userDeleteByIdError}
53 | )}
54 | {successMessageDelete &&
{successMessageDelete} }
55 |
56 | {userList.length > 0 ? (
57 |
62 | ) : (
63 |
64 | No Users Found..
65 |
66 | )}
67 |
68 | >
69 | )}
70 |
71 | );
72 | }
73 |
74 | export default UsersList;
75 |
--------------------------------------------------------------------------------
/client/src/screens/Admin/AdminRegisterScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { Link, useNavigate } from 'react-router-dom';
4 |
5 | import RegisterForm from '../../components/ui/Admin/Auth/RegisterForm';
6 | import Logo from '/android-chrome-512x512.png';
7 |
8 | function AdminRegisterScreen() {
9 | const navigate = useNavigate();
10 |
11 | const user = useSelector((state) => state.user);
12 | const { userInfo } = user;
13 |
14 | const admin = useSelector((state) => state.admin);
15 | const { adminUserInfo } = admin;
16 |
17 | useEffect(() => {
18 | if (userInfo) {
19 | navigate('/');
20 | }
21 | }, [navigate, userInfo]);
22 |
23 | useEffect(() => {
24 | if (adminUserInfo) {
25 | navigate('/admin/dashboard');
26 | }
27 | }, [navigate, adminUserInfo]);
28 |
29 | return (
30 |
31 |
32 |
33 |
38 |
39 | Register Now! to be part of
40 | our Team
41 |
42 |
43 |
44 |
45 |
46 |
47 | Already have Account?{' '}
48 |
52 | Sign in Here
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default AdminRegisterScreen;
62 |
--------------------------------------------------------------------------------
/client/src/components/ui/Home/FeaturedPizzasSection.jsx:
--------------------------------------------------------------------------------
1 | function FeaturedPizzasSection() {
2 | const featuredPizzas = [
3 | {
4 | id: 1,
5 | name: 'Pepperoni Feast',
6 | description: 'Classic pepperoni pizza with extra cheese.',
7 | price: 12.99,
8 | imageUrl: 'https://www.cicis.com/media/gvedawsa/pepperoni-pizza.png',
9 | },
10 | {
11 | id: 2,
12 | name: 'Margherita Delight',
13 | description: 'Fresh tomatoes, mozzarella, and basil leaves.',
14 | price: 10.99,
15 | imageUrl: 'https://www.cicis.com/media/5jzgsmbq/supreme-pizza.png',
16 | },
17 | {
18 | id: 3,
19 | name: 'Veggie Supreme',
20 | description: 'Loaded with a variety of fresh vegetables.',
21 | price: 11.99,
22 | imageUrl: 'https://www.cicis.com/media/nctfaewb/veggie-pizza.png',
23 | },
24 | ];
25 | return (
26 |
30 | Featured Pizzas
31 |
32 | {featuredPizzas.map((pizza) => (
33 |
37 |
42 |
43 |
44 |
45 |
46 | {pizza.name}
47 |
48 |
49 | ${pizza.price}
50 |
51 |
52 |
{pizza.description}
53 |
54 |
55 | ))}
56 |
57 |
58 | );
59 | }
60 |
61 | export default FeaturedPizzasSection;
62 |
--------------------------------------------------------------------------------
/client/src/components/ui/PizzaMenu/PizzaItem.jsx:
--------------------------------------------------------------------------------
1 | import Proptypes from 'prop-types';
2 | import { useState } from 'react';
3 | import { FaMinus, FaPlus } from 'react-icons/fa';
4 |
5 | // Import Components
6 | import AddtoCartButton from '../Cart/AddToCartButton';
7 |
8 | function PizzaItem({ pizza }) {
9 | const [qty, setQty] = useState(1);
10 | return (
11 |
15 |
20 |
21 |
22 |
23 |
{pizza.name}
24 |
25 | ${pizza.price}
26 |
27 |
28 |
{pizza.description}
29 |
30 | Size:
31 | {pizza.size.charAt(0).toUpperCase() + pizza.size.slice(1)}
32 |
33 |
34 |
35 |
36 |
{
39 | if (qty > 1) {
40 | setQty(qty - 1);
41 | }
42 | }}
43 | >
44 |
45 |
46 |
Qty: {qty}
47 |
{
50 | if (qty < 10) {
51 | setQty(qty + 1);
52 | }
53 | }}
54 | >
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | PizzaItem.propTypes = {
65 | pizza: Proptypes.object.isRequired,
66 | };
67 |
68 | export default PizzaItem;
69 |
--------------------------------------------------------------------------------
/client/src/screens/User/CheckoutScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { FaCheck, FaMoneyCheckAlt, FaTruck } from 'react-icons/fa';
3 | import { useSelector } from 'react-redux';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | // Import Components
7 | import CurrentCheckoutStep from '../../components/ui/CheckoutSteps/CurrentCheckoutStep';
8 | import PaymentStep from '../../components/ui/CheckoutSteps/PaymentStep';
9 | import PlaceOrderStep from '../../components/ui/CheckoutSteps/PlaceOrderStep';
10 | import ShippingStep from '../../components/ui/CheckoutSteps/ShippingStep';
11 |
12 | function CheckoutScreen() {
13 | const [currentStep, setCurrentStep] = useState('Shipping');
14 |
15 | const AllSteps = [
16 | {
17 | step: 'Shipping',
18 | icon: ,
19 | },
20 | {
21 | step: 'Payment',
22 | icon: ,
23 | },
24 | {
25 | step: 'Place Order',
26 | icon: ,
27 | },
28 | ];
29 |
30 | const navigate = useNavigate();
31 |
32 | const user = useSelector((state) => state.user);
33 | const { userInfo } = user;
34 |
35 | const admin = useSelector((state) => state.admin);
36 | const { adminUserInfo } = admin;
37 |
38 | const cart = useSelector((state) => state.cart);
39 | const { cartItems } = cart;
40 |
41 | useEffect(() => {
42 | if (!userInfo && !adminUserInfo) {
43 | navigate('/login');
44 | }
45 | // else if (!cartItems) {
46 | // navigate('/menu');
47 | // }
48 | }, [navigate, userInfo, adminUserInfo, cartItems]);
49 |
50 | return (
51 |
52 |
53 |
Checkout
54 |
55 |
56 | {currentStep === 'Shipping' && (
57 |
58 | )}
59 | {currentStep === 'Payment' && (
60 |
61 | )}
62 | {currentStep === 'Place Order' && (
63 |
64 | )}
65 |
66 |
67 | );
68 | }
69 |
70 | export default CheckoutScreen;
71 |
--------------------------------------------------------------------------------
/client/src/screens/AboutScreen.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Logo from '/android-chrome-512x512.png';
3 |
4 | function AboutScreen() {
5 | return (
6 |
7 |
8 |
13 |
14 | About
15 | Pizza Palette
16 |
17 |
18 |
19 |
20 |
21 |
22 | Introduction
23 |
24 |
25 | Pizza Palette is a pizza ordering app that allows you to create your
26 | own custom pizza. You can choose from a variety of toppings and
27 | sauces to create your dream pizza. You can also choose from a
28 | selection of pre-made pizzas.
29 |
30 |
31 |
32 |
33 |
34 | Our Mission
35 |
36 |
37 | Our mission is to provide you with the best pizza ordering
38 | experience. We want to make it easy for you to order your favorite
39 | pizza. We also want to make it easy for you to create your own
40 | custom pizza.
41 |
42 |
43 |
44 |
45 | Contact Us
46 |
47 | If you have any questions or concerns, please contact us at{' '}
48 |
52 | contact@pizzapalette.com
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default AboutScreen;
62 |
--------------------------------------------------------------------------------
/client/src/components/ui/Home/HowItWorksSection.jsx:
--------------------------------------------------------------------------------
1 | import { FaPizzaSlice, FaCartPlus, FaMoneyCheckAlt } from 'react-icons/fa';
2 | import { BsFillEmojiHeartEyesFill } from 'react-icons/bs';
3 |
4 | function HowItWorksSection() {
5 | const StepsToOrderPizza = [
6 | {
7 | step: 1,
8 | icon: ,
9 | title: 'Choose Your Pizza',
10 | description:
11 | 'Browse our delicious range of pizzas and select your favorite one.',
12 | },
13 | {
14 | step: 2,
15 | icon: ,
16 | title: 'Add to Cart',
17 | description:
18 | 'Click the "Add to Cart" button to add your chosen pizza to your cart.',
19 | },
20 | {
21 | step: 3,
22 | icon: ,
23 | title: 'Checkout Your Order',
24 | description:
25 | 'Review your order details and proceed to the secure checkout.',
26 | },
27 | {
28 | step: 4,
29 | icon: ,
30 | title: 'Enjoy Your Pizza',
31 | description:
32 | 'Sit back, relax, and enjoy your delicious pizza delivered to your doorstep.',
33 | },
34 | ];
35 |
36 | return (
37 |
41 |
42 | How It Works
43 |
44 |
45 |
46 | We have made it very easy for you to order your favorite pizza.
47 |
48 |
49 | {StepsToOrderPizza.map((step) => (
50 |
54 |
55 | {step.icon}
56 |
57 |
58 |
59 | {step.step}
60 | {'. '}
61 | {step.title}
62 |
63 |
{step.description}
64 |
65 |
66 | ))}
67 |
68 |
69 | );
70 | }
71 |
72 | export default HowItWorksSection;
73 |
--------------------------------------------------------------------------------
/client/src/screens/User/UserOrdersScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | // Import Thunks
6 | import { listOrdersByUserId } from '../../redux/asyncThunks/orderThunks';
7 | import { getUserDetails } from '../../redux/asyncThunks/userThunks';
8 |
9 | // Import Components
10 | import Loader from '../../components/ui/Loader';
11 | import UserOrdersTable from '../../components/ui/UserOrdersTable';
12 | import Message from '../../components/ui/Message';
13 | import VerficationModal from '../../components/ui/Auth/VerficationModal';
14 |
15 | function UserOrdersScreen() {
16 | const [modalVisible, setModalVisible] = useState(false);
17 |
18 | const dispatch = useDispatch();
19 | const navigate = useNavigate();
20 |
21 | const user = useSelector((state) => state.user);
22 | const { loading, userInfo, userDetails } = user;
23 |
24 | const admin = useSelector((state) => state.admin);
25 | const { adminUserInfo } = admin;
26 |
27 | const order = useSelector((state) => state.order);
28 | const {
29 | loading: orderLoading,
30 | orderListByUserId,
31 | orderListByUserIdError,
32 | } = order;
33 |
34 | useEffect(() => {
35 | if (!userInfo) {
36 | navigate('/login');
37 | } else if (adminUserInfo) {
38 | navigate('/admin/dashboard');
39 | } else {
40 | dispatch(getUserDetails({}));
41 | dispatch(listOrdersByUserId(userInfo._id));
42 | }
43 | }, [dispatch, navigate, userInfo, adminUserInfo]);
44 |
45 | useEffect(() => {
46 | if (userDetails && !userDetails.isVerified) {
47 | setModalVisible(true);
48 | }
49 | }, [userDetails]);
50 |
51 | return (
52 | <>
53 |
54 | {loading || orderLoading ? (
55 |
56 | ) : orderListByUserIdError ? (
57 | {orderListByUserIdError}
58 | ) : (
59 | <>
60 | {orderListByUserId.length > 0 ? (
61 | <>
62 |
63 | My Orders
64 |
65 |
66 | >
67 | ) : (
68 |
69 | No Orders Found!
70 |
71 | )}
72 | >
73 | )}
74 |
75 | {modalVisible && (
76 | setModalVisible(false)} />
77 | )}
78 | >
79 | );
80 | }
81 |
82 | export default UserOrdersScreen;
83 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | // Import required packages
2 | const colors = require('colors');
3 | const cors = require('cors');
4 | const dotenv = require('dotenv');
5 | const express = require('express');
6 | const morgan = require('morgan');
7 | const bodyParser = require('body-parser');
8 |
9 | // Import Configs and Middlewares
10 | const connectDb = require('./config/db');
11 | const { notFound, errorHandler } = require('./middlewares/errorMiddlewares');
12 |
13 | // Import Routes
14 | const adminUserRoutes = require('./routes/adminUserRoutes');
15 | const userRoutes = require('./routes/userRoutes');
16 | const pizzaRoutes = require('./routes/pizzaRoutes');
17 | const orderRoutes = require('./routes/orderRoutes');
18 | const inventoryRoutes = require('./routes/inventoryRoutes');
19 |
20 | // Configure DotEnv
21 | dotenv.config();
22 |
23 | // Create Express App
24 | const app = express();
25 |
26 | // Connect to Database
27 | connectDb();
28 |
29 | // Configure Middlewares
30 | if (process.env.NODE_ENV === 'development') {
31 | // Use morgan for logging during development
32 | app.use(morgan('dev'));
33 | }
34 |
35 | // Enable Cross-Origin Resource Sharing
36 | app.use(cors());
37 |
38 | // Parse incoming JSON data
39 | app.use(express.json());
40 |
41 | // Parse incoming form data
42 | app.use(bodyParser.urlencoded({ extended: true }));
43 | app.use(bodyParser.json());
44 |
45 | // Basic route for the root URL
46 | app.get('/', (req, res) => {
47 | res.send(
48 | `
49 | Hello, Code Wizard!
50 | Got a moment?
51 | Your API is not just running; it's sprinting like Usain Bolt!
52 |
53 |
Everything is Awesome!
54 |
💻✨🚀
55 |
56 |
57 | Need more magic? Explore the code and unleash your creativity!
58 | Happy coding, developer! 🎉
59 |
60 | `
61 | );
62 | });
63 |
64 | // Configure API routes
65 | app.use('/api/admin', adminUserRoutes);
66 | app.use('/api/users', userRoutes);
67 | app.use('/api/pizzas', pizzaRoutes);
68 | app.use('/api/orders', orderRoutes);
69 | app.use('/api/stocks', inventoryRoutes);
70 |
71 | // Error Middleware
72 | app.use(notFound);
73 | app.use(errorHandler);
74 |
75 | // Configure Port
76 | const PORT = process.env.PORT || 5000;
77 |
78 | // Start the server
79 | app.listen(
80 | PORT,
81 | console.log(
82 | `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold
83 | )
84 | );
85 |
--------------------------------------------------------------------------------
/client/src/components/ui/Message.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect, useState } from 'react';
3 | import {
4 | FaCheckCircle,
5 | FaExclamationCircle,
6 | FaTimesCircle,
7 | FaInfoCircle,
8 | } from 'react-icons/fa';
9 |
10 | const Message = ({ children }) => {
11 | const successStyles =
12 | 'bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative';
13 | const warningStyles =
14 | 'bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded relative';
15 | const errorStyles =
16 | 'bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative';
17 | const infoStyles =
18 | 'bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded relative';
19 |
20 | const styles = {
21 | success: successStyles,
22 | warning: warningStyles,
23 | error: errorStyles,
24 | info: infoStyles,
25 | };
26 |
27 | const iconComponents = {
28 | success: FaCheckCircle,
29 | warning: FaExclamationCircle,
30 | error: FaTimesCircle,
31 | info: FaInfoCircle,
32 | };
33 |
34 | const getStatusText = (status) => {
35 | if (status >= 200 && status <= 299) {
36 | return 'Success';
37 | } else if (status >= 400 && status <= 499) {
38 | return 'Client Error';
39 | } else if (status >= 500 && status <= 599) {
40 | return 'Server Error';
41 | } else {
42 | return 'Unknown Status';
43 | }
44 | };
45 |
46 | const [visible, setVisible] = useState(true);
47 |
48 | useEffect(() => {
49 | const timer = setTimeout(() => {
50 | setVisible(false);
51 | }, 8000);
52 |
53 | return () => {
54 | clearTimeout(timer);
55 | };
56 | }, []);
57 |
58 | if (!visible) {
59 | return null;
60 | }
61 |
62 | let alertStyle = '';
63 | let Icon = '';
64 |
65 | if (children.status >= 200 && children.status <= 299) {
66 | alertStyle = styles.success;
67 | Icon = iconComponents.success;
68 | } else if (children.status >= 400 && children.status <= 499) {
69 | alertStyle = styles.warning;
70 | Icon = iconComponents.warning;
71 | } else if (children.status >= 500 && children.status <= 599) {
72 | alertStyle = styles.error;
73 | Icon = iconComponents.error;
74 | } else {
75 | alertStyle = styles.info;
76 | Icon = iconComponents.info;
77 | }
78 |
79 | return (
80 |
81 | {Icon && }
82 |
83 | {getStatusText(children.status)}:
84 |
85 | {children.message}
86 |
87 | );
88 | };
89 |
90 | Message.propTypes = {
91 | children: PropTypes.object.isRequired,
92 | };
93 |
94 | export default Message;
95 |
--------------------------------------------------------------------------------
/client/src/components/ui/Auth/ForgetPassword/EmailForm.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | // Import Thunks
6 | import { forgotPassword } from '../../../../redux/asyncThunks/userThunks';
7 | import { setPasswordResetEmail } from '../../../../redux/slices/userSlice';
8 |
9 | // Import Components
10 | import Button from '../../Button';
11 | import Loader from '../../Loader';
12 | import Message from '../../Message';
13 |
14 | function EmailForm({ setCurrentStep }) {
15 | const [email, setEmail] = useState('');
16 | const dispatch = useDispatch();
17 |
18 | const user = useSelector((state) => state.user);
19 | const { loading, userForgotPasswordError, userForgotPasswordSuccess } = user;
20 |
21 | const submitHandler = (e) => {
22 | e.preventDefault();
23 | dispatch(setPasswordResetEmail(email));
24 | dispatch(forgotPassword({ email }));
25 | };
26 |
27 | useEffect(() => {
28 | if (userForgotPasswordSuccess) {
29 | setCurrentStep('OTPForm');
30 | }
31 | }, [userForgotPasswordSuccess, setCurrentStep]);
32 | return (
33 | <>
34 | {loading ? (
35 |
36 |
37 |
38 | ) : (
39 |
73 | )}
74 | >
75 | );
76 | }
77 |
78 | EmailForm.propTypes = {
79 | setCurrentStep: PropTypes.func.isRequired,
80 | };
81 |
82 | export default EmailForm;
83 |
--------------------------------------------------------------------------------
/client/src/screens/MenuScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Link } from 'react-router-dom';
4 |
5 | // Import Thunks
6 | import { listPizzas } from '../redux/asyncThunks/pizzaThunks';
7 | import { getUserDetails } from '../redux/asyncThunks/userThunks';
8 |
9 | // Import Components
10 | import VerficationModal from '../components/ui/Auth/VerficationModal';
11 | import Button from '../components/ui/Button';
12 | import Loader from '../components/ui/Loader';
13 | import Message from '../components/ui/Message';
14 | import PizzaList from '../components/ui/PizzaMenu/PizzaList';
15 |
16 | function MenuScreen() {
17 | const dispatch = useDispatch();
18 | const [modalVisible, setModalVisible] = useState(false);
19 |
20 | const user = useSelector((state) => state.user);
21 | const { userDetails } = user;
22 |
23 | const pizza = useSelector((state) => state.pizza);
24 | const { loading, pizzaList, pizzaListError } = pizza;
25 |
26 | const cart = useSelector((state) => state.cart);
27 | const { cartAddItemError } = cart;
28 |
29 | useEffect(() => {
30 | if (!userDetails) {
31 | dispatch(getUserDetails({}));
32 | }
33 | if (!pizzaList || pizzaList.length < 1) {
34 | dispatch(listPizzas({}));
35 | }
36 | }, [dispatch, userDetails, pizzaList]);
37 |
38 | useEffect(() => {
39 | if (userDetails && !userDetails.isVerified) {
40 | setModalVisible(true);
41 | }
42 | }, [userDetails]);
43 |
44 | return (
45 | <>
46 |
47 | {loading ? (
48 |
49 | ) : pizzaListError || cartAddItemError ? (
50 | {pizzaListError || cartAddItemError}
51 | ) : pizzaList.length > 0 ? (
52 | <>
53 |
54 |
Pizza Menu
55 | {userDetails && (
56 |
57 |
58 | Create Custom Pizza
59 |
60 |
61 | )}
62 |
63 | pizza.createdBy === 'admin'
66 | )}
67 | />
68 | >
69 | ) : (
70 |
71 | No Pizzas Found!
72 |
73 | )}
74 |
75 | {modalVisible && (
76 | setModalVisible(false)} />
77 | )}
78 | >
79 | );
80 | }
81 |
82 | export default MenuScreen;
83 |
--------------------------------------------------------------------------------
/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { Provider } from 'react-redux';
4 | import { RouterProvider, createBrowserRouter } from 'react-router-dom';
5 |
6 | // Import App
7 | import App from './App.jsx';
8 | import './index.css';
9 | import store from './redux/store.js';
10 |
11 | // Import Screens
12 | import AboutScreen from './screens/AboutScreen.jsx';
13 | import AdminDashboardScreen from './screens/Admin/AdminDashboardScreen.jsx';
14 | import AdminLoginScreen from './screens/Admin/AdminLoginScreen.jsx';
15 | import AdminRegisterScreen from './screens/Admin/AdminRegisterScreen.jsx';
16 | import HomeScreen from './screens/HomeScreen.jsx';
17 | import MenuScreen from './screens/MenuScreen.jsx';
18 | import CheckoutScreen from './screens/User/CheckoutScreen.jsx';
19 | import ForgetPasswordScreen from './screens/User/ForgetPasswordScreen.jsx';
20 | import ProfileScreen from './screens/User/ProfileScreen.jsx';
21 | import UserCreateCustomPizzaScreen from './screens/User/UserCreateCustomPizzaScreen.jsx';
22 | import UserLoginScreen from './screens/User/UserLoginScreen.jsx';
23 | import UserOrdersScreen from './screens/User/UserOrdersScreen.jsx';
24 | import UserRegisterScreen from './screens/User/UserRegisterScreen.jsx';
25 |
26 | // Create Router
27 | const router = createBrowserRouter([
28 | {
29 | path: '/',
30 | element: ,
31 | children: [
32 | {
33 | path: '/',
34 | element: ,
35 | },
36 | {
37 | path: '/login',
38 | element: ,
39 | },
40 | {
41 | path: '/register',
42 | element: ,
43 | },
44 | {
45 | path: '/forget-pwd',
46 | element: ,
47 | },
48 |
49 | {
50 | path: '/profile',
51 | element: ,
52 | },
53 | {
54 | path: '/custom-pizza',
55 | element: ,
56 | },
57 | {
58 | path: '/my-orders',
59 | element: ,
60 | },
61 | {
62 | path: '/checkout',
63 | element: ,
64 | },
65 | {
66 | path: '/admin/dashboard',
67 | element: ,
68 | },
69 | {
70 | path: '/admin/login',
71 | element: ,
72 | },
73 | {
74 | path: '/admin/register',
75 | element: ,
76 | },
77 | {
78 | path: '/menu',
79 | element: ,
80 | },
81 | {
82 | path: '/about',
83 | element: ,
84 | },
85 | ],
86 | },
87 | ]);
88 |
89 | // Render App
90 | ReactDOM.createRoot(document.getElementById('root')).render(
91 |
92 |
93 |
94 |
95 |
96 | );
97 |
--------------------------------------------------------------------------------
/client/src/components/ui/Cart/CartModal/CartItemList.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { FaMinus, FaPlus, FaTrash } from 'react-icons/fa';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | // Import Thunks
6 | import { addToCart, removeFromCart } from '../../../../redux/slices/cartSlice';
7 |
8 | // Import Components
9 | import Button from '../../Button';
10 |
11 | function CartItemList() {
12 | const dispatch = useDispatch();
13 |
14 | const cart = useSelector((state) => state.cart);
15 | const { cartItems } = cart;
16 |
17 | // State to manage item quantities
18 | const [itemQuantities, setItemQuantities] = useState({});
19 |
20 | // Function to update item quantity
21 | const updateQuantity = (itemId, quantity) => {
22 | setItemQuantities({
23 | ...itemQuantities,
24 | [itemId]: quantity,
25 | });
26 | };
27 |
28 | return (
29 | <>
30 | {cartItems.map((item) => (
31 |
35 |
36 |
37 |
{item.name}
38 |
39 | Price: ${item.price} | Size:{' '}
40 | {item.size.charAt(0).toUpperCase() + item.size.slice(1)}
41 |
42 |
43 |
{
46 | const newQuantity =
47 | (itemQuantities[item._id] || item.qty) - 1;
48 | if (newQuantity >= 1) {
49 | dispatch(addToCart({ id: item._id, qty: newQuantity }));
50 | updateQuantity(item._id, newQuantity);
51 | }
52 | }}
53 | >
54 |
55 |
56 |
Qty: {item.qty}
57 |
{
60 | const newQuantity =
61 | (itemQuantities[item._id] || item.qty) + 1;
62 | if (newQuantity <= 10) {
63 | dispatch(addToCart({ id: item._id, qty: newQuantity }));
64 | updateQuantity(item._id, newQuantity);
65 | }
66 | }}
67 | >
68 |
69 |
70 |
71 |
72 |
dispatch(removeFromCart(item._id))}
76 | >
77 |
78 |
79 |
80 |
Total: ${item.price * item.qty}
81 |
82 | ))}
83 | >
84 | );
85 | }
86 |
87 | export default CartItemList;
88 |
--------------------------------------------------------------------------------
/client/src/components/ui/Profile/ProfileBtnAndDropOnNav.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { BiSolidDownArrow, BiSolidUserDetail } from 'react-icons/bi';
3 | import { CgLogOut } from 'react-icons/cg';
4 | import { FaUserAlt } from 'react-icons/fa';
5 | import { useDispatch, useSelector } from 'react-redux';
6 | import { Link } from 'react-router-dom';
7 |
8 | import { getUserDetails } from '../../../redux/asyncThunks/userThunks';
9 |
10 | function ProfileBtnAndDropOnNav({
11 | dropIsOpen,
12 | setDropIsOpen,
13 | dropdownRef,
14 | logoutHandler,
15 | }) {
16 | const dispatch = useDispatch();
17 |
18 | const user = useSelector((state) => state.user);
19 | const { userInfo } = user;
20 |
21 | const admin = useSelector((state) => state.admin);
22 | const { adminUserInfo } = admin;
23 |
24 | return (
25 |
26 |
setDropIsOpen(!dropIsOpen)}
29 | className="text-black hover:text-orange-500 border-2 border-orange-500 rounded-full inline-flex items-center p-2 focus:outline-none"
30 | >
31 |
32 |
33 |
34 | {dropIsOpen && (
35 |
36 | {adminUserInfo && (
37 | {
40 | setDropIsOpen(!dropIsOpen);
41 | }}
42 | className="inline-flex items-center w-full px-4 py-2 text-sm text-left text-orange-500 hover:bg-orange-100"
43 | >
44 |
45 | Dashboard
46 |
47 | )}
48 | {userInfo && (
49 | {
52 | dispatch(getUserDetails({}));
53 | setDropIsOpen(!dropIsOpen);
54 | }}
55 | className="inline-flex items-center w-full px-4 py-2 text-sm text-left text-orange-500 hover:bg-orange-100"
56 | >
57 |
58 | Profile
59 |
60 | )}
61 |
66 |
67 | Logout
68 |
69 |
70 | )}
71 |
72 | );
73 | }
74 |
75 | ProfileBtnAndDropOnNav.propTypes = {
76 | dropIsOpen: PropTypes.bool.isRequired,
77 | setDropIsOpen: PropTypes.func.isRequired,
78 | dropdownRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
79 | logoutHandler: PropTypes.func.isRequired,
80 | };
81 |
82 | export default ProfileBtnAndDropOnNav;
83 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/Lists/StaffList.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | // Import Thunks
5 | import {
6 | deleteAdminUserById,
7 | listAdminUsers,
8 | } from '../../../../../redux/asyncThunks/adminThunks';
9 |
10 | // Import Components
11 | import Loader from '../../../Loader';
12 | import Message from '../../../Message';
13 | import Table from '../Table';
14 |
15 | function StaffList() {
16 | const adminUserColumns = [
17 | '_id',
18 | 'name',
19 | 'email',
20 | 'role',
21 | 'permissions',
22 | 'isApproved',
23 | ];
24 |
25 | const dispatch = useDispatch();
26 |
27 | const admin = useSelector((state) => state.admin);
28 | const {
29 | loading,
30 | adminUserList,
31 | adminUserListError,
32 | adminUserDeleteByIdError,
33 | adminUserUpdateProfileByIdError,
34 | adminUserUpdateProfileByIdSuccess,
35 | adminUserDeleteByIdSuccess,
36 | } = admin;
37 |
38 | const handleDelete = (id) => {
39 | dispatch(deleteAdminUserById(id)).then(() => dispatch(listAdminUsers({})));
40 | };
41 |
42 | const handleChange = (id) => {
43 | dispatch(
44 | listAdminUsers({
45 | id,
46 | isApproved: !adminUserList.find((user) => user._id === id).isApproved,
47 | })
48 | );
49 | };
50 |
51 | const successMessageUpdate = adminUserUpdateProfileByIdSuccess && {
52 | status: '200',
53 | message: 'User Updated Successfully!',
54 | };
55 |
56 | const successMessageDelete = adminUserDeleteByIdSuccess && {
57 | status: '200',
58 | message: 'User Deleted Successfully!',
59 | };
60 |
61 | useEffect(() => {
62 | if (!adminUserList) {
63 | dispatch(listAdminUsers({}));
64 | }
65 | }, [dispatch, adminUserList]);
66 |
67 | return (
68 |
69 |
All Staff
70 | {loading ? (
71 |
72 | ) : (
73 | <>
74 | {(adminUserListError ||
75 | adminUserDeleteByIdError ||
76 | adminUserUpdateProfileByIdError) && (
77 |
78 | {adminUserListError ||
79 | adminUserDeleteByIdError ||
80 | adminUserUpdateProfileByIdError}
81 |
82 | )}
83 | {successMessageDelete ||
84 | (successMessageUpdate && (
85 |
{successMessageDelete || successMessageUpdate}
86 | ))}
87 |
88 | {adminUserList.length > 0 ? (
89 |
95 | ) : (
96 |
97 | No Staff Found..
98 |
99 | )}
100 |
101 | >
102 | )}
103 |
104 | );
105 | }
106 |
107 | export default StaffList;
108 |
--------------------------------------------------------------------------------
/client/src/components/ui/CheckoutSteps/RazorPayPaymentButton.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect } from 'react';
3 | import { useSelector, useDispatch } from 'react-redux';
4 |
5 | // Import Actions
6 | import { setRazorPayPaymentDetails } from '../../../redux/slices/cartSlice';
7 |
8 | // Import Components
9 | import Button from '../Button';
10 | import Logo from '/android-chrome-512x512.png';
11 |
12 | function RazorPayPaymentButton({ amount, orderId }) {
13 | const dispatch = useDispatch();
14 |
15 | const user = useSelector((state) => state.user);
16 | const { userInfo } = user;
17 |
18 | useEffect(() => {
19 | const loadRazorpayScript = () => {
20 | const script = document.createElement('script');
21 | script.src = 'https://checkout.razorpay.com/v1/checkout.js';
22 | script.async = true;
23 | script.onload = () => {
24 | // Razorpay script is loaded, you can now create rzp1 object here
25 |
26 | const options = {
27 | key: import.meta.env.VITE_RAZORPAY_KEY_ID,
28 | amount: amount,
29 | currency: 'USD',
30 | name: userInfo.name,
31 | description: 'Order Payment',
32 | image: Logo,
33 | order_id: orderId,
34 | handler: function (response) {
35 | dispatch(
36 | setRazorPayPaymentDetails({
37 | razorPayPaymentId: response.razorpay_payment_id,
38 | razorPayOrderId: response.razorpay_order_id,
39 | razorPaySignature: response.razorpay_signature,
40 | })
41 | );
42 | },
43 | prefill: {
44 | name: userInfo.name,
45 | email: userInfo.email,
46 | contact: userInfo.phone,
47 | },
48 | notes: {
49 | address: 'Razorpay Corporate Office',
50 | },
51 | theme: {
52 | color: '#61dafb',
53 | },
54 | };
55 |
56 | const rzp1 = new window.Razorpay(options);
57 | rzp1.on('payment.failed', function (response) {
58 | alert(response.error.code);
59 | alert(response.error.description);
60 | alert(response.error.source);
61 | alert(response.error.step);
62 | alert(response.error.reason);
63 | alert(response.error.metadata.order_id);
64 | alert(response.error.metadata.payment_id);
65 | });
66 |
67 | window.rzp1 = rzp1;
68 | };
69 | document.body.appendChild(script);
70 | };
71 |
72 | loadRazorpayScript();
73 | }, [
74 | amount,
75 | dispatch,
76 | orderId,
77 | userInfo.email,
78 | userInfo.name,
79 | userInfo.phone,
80 | ]);
81 |
82 | return (
83 | {
89 | if (window.rzp1) {
90 | window.rzp1.open();
91 | }
92 | }}
93 | >
94 | Pay Now
95 |
96 | );
97 | }
98 |
99 | RazorPayPaymentButton.propTypes = {
100 | amount: PropTypes.number.isRequired,
101 | orderId: PropTypes.string.isRequired,
102 | };
103 |
104 | export default RazorPayPaymentButton;
105 |
--------------------------------------------------------------------------------
/client/src/components/ui/Auth/ForgetPassword/OTPForm.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState, useEffect } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | // Import Thunks
6 | import { setPasswordResetOTP } from '../../../../redux/slices/userSlice';
7 | import { forgotPassword } from '../../../../redux/asyncThunks/userThunks';
8 |
9 | // Import Components
10 | import Button from '../../Button';
11 | import Loader from '../../Loader';
12 | import Message from '../../Message';
13 |
14 | function OTPForm({ setCurrentStep }) {
15 | const [otp, setOtp] = useState('');
16 |
17 | const dispatch = useDispatch();
18 |
19 | const user = useSelector((state) => state.user);
20 | const { loading, userPasswordResetEmail, userForgotPasswordError } = user;
21 |
22 | const submitHandler = (e) => {
23 | e.preventDefault();
24 | dispatch(setPasswordResetOTP(otp));
25 | setInterval(() => {
26 | setCurrentStep('PasswordForm');
27 | }, 1000);
28 | };
29 |
30 | useEffect(() => {
31 | if (userForgotPasswordError) {
32 | setInterval(() => {
33 | setCurrentStep('EmailForm');
34 | }, 1000);
35 | }
36 | }, [userForgotPasswordError, setCurrentStep]);
37 |
38 | return (
39 | <>
40 | {loading ? (
41 |
42 |
43 |
44 | ) : (
45 |
97 | )}
98 | >
99 | );
100 | }
101 |
102 | OTPForm.propTypes = {
103 | setCurrentStep: PropTypes.func.isRequired,
104 | };
105 |
106 | export default OTPForm;
107 |
--------------------------------------------------------------------------------
/client/src/screens/Admin/AdminDashboardScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import {
3 | FaBoxes,
4 | FaClipboardList,
5 | FaHome,
6 | FaPizzaSlice,
7 | FaUser,
8 | FaUsers,
9 | } from 'react-icons/fa';
10 | import { useDispatch, useSelector } from 'react-redux';
11 | import { useNavigate } from 'react-router-dom';
12 |
13 | // Import Thunks
14 | import { listAdminUsers } from '../../redux/asyncThunks/adminThunks';
15 | import { listOrders } from '../../redux/asyncThunks/orderThunks';
16 | import { listPizzas } from '../../redux/asyncThunks/pizzaThunks';
17 | import { listUsers } from '../../redux/asyncThunks/userThunks';
18 | import { listInventory } from '../../redux/asyncThunks/inventoryThunks';
19 |
20 | // Import Components
21 | import MainContent from '../../components/ui/Admin/Dashboard/MainContent';
22 | import SideBar from '../../components/ui/Admin/Dashboard/SideBar/SideBar';
23 |
24 | function AdminDashboardScreen() {
25 | const menuItems = [
26 | {
27 | name: 'Home',
28 | icon: ,
29 | },
30 | {
31 | name: 'Staff',
32 | icon: ,
33 | },
34 | {
35 | name: 'Users',
36 | icon: ,
37 | },
38 | {
39 | name: 'Pizzas',
40 | icon: ,
41 | },
42 | {
43 | name: 'Orders',
44 | icon: ,
45 | },
46 | {
47 | name: 'Inventory',
48 | icon: ,
49 | },
50 | ];
51 |
52 | const [activeMenuItem, setActiveMenuItem] = useState('Home');
53 | const [collapsible, setCollapsible] = useState(false);
54 |
55 | const navigate = useNavigate();
56 | const dispatch = useDispatch();
57 |
58 | const user = useSelector((state) => state.user);
59 | const { userInfo } = user;
60 |
61 | const admin = useSelector((state) => state.admin);
62 | const { adminUserInfo } = admin;
63 |
64 | const toggleSidebar = () => {
65 | setCollapsible((prevState) => !prevState);
66 | };
67 |
68 | const handleMenuItemClick = (name) => {
69 | setActiveMenuItem(name);
70 | toggleSidebar();
71 | };
72 |
73 | useEffect(() => {
74 | if (!adminUserInfo) {
75 | navigate('/admin/login');
76 | }
77 | dispatch(listUsers({}));
78 | dispatch(listAdminUsers({}));
79 | dispatch(listPizzas({}));
80 | dispatch(listOrders({}));
81 | dispatch(listInventory({}));
82 | }, [dispatch, navigate, adminUserInfo]);
83 |
84 | useEffect(() => {
85 | // Check if any other user type is logged in (redirect to homepage)
86 | if (userInfo) {
87 | navigate('/');
88 | }
89 | }, [navigate, userInfo]);
90 |
91 | return (
92 |
93 | {adminUserInfo ? (
94 | <>
95 | {collapsible && (
96 |
102 | )}
103 |
104 |
110 | >
111 | ) : (
112 | navigate('/admin/login')
113 | )}
114 |
115 | );
116 | }
117 |
118 | export default AdminDashboardScreen;
119 |
--------------------------------------------------------------------------------
/client/src/components/ui/Auth/UserLoginForm.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Link } from 'react-router-dom';
4 |
5 | import Button from '../Button';
6 | import Loader from '../Loader';
7 | import Message from '../Message';
8 |
9 | import { loginUser } from '../../../redux/asyncThunks/userThunks';
10 |
11 | function UserLoginForm() {
12 | const [email, setEmail] = useState('');
13 | const [password, setPassword] = useState('');
14 |
15 | const dispatch = useDispatch();
16 |
17 | const user = useSelector((state) => state.user);
18 | const { loading, userLoginError } = user;
19 |
20 | const handleLogin = (e) => {
21 | e.preventDefault();
22 | dispatch(loginUser({ email, password }));
23 | };
24 |
25 | const forgetPwdHandler = () => {
26 | console.log('Forget Password');
27 | };
28 |
29 | return (
30 | <>
31 | {loading ? (
32 |
33 |
34 |
35 | ) : (
36 |
102 | )}
103 | >
104 | );
105 | }
106 |
107 | export default UserLoginForm;
108 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Auth/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Link } from 'react-router-dom';
4 |
5 | import Button from '../../Button';
6 | import Loader from '../../Loader';
7 | import Message from '../../Message';
8 |
9 | import { loginAdmin } from '../../../../redux/asyncThunks/adminThunks';
10 |
11 | function AdminLoginForm() {
12 | const [email, setEmail] = useState('');
13 | const [password, setPassword] = useState('');
14 |
15 | const dispatch = useDispatch();
16 |
17 | const admin = useSelector((state) => state.admin);
18 | const { loading, adminUserLoginError } = admin;
19 |
20 | const handleLogin = (e) => {
21 | e.preventDefault();
22 | dispatch(loginAdmin({ email, password }));
23 | };
24 |
25 | const forgetPwdHandler = () => {
26 | console.log('Forget Password');
27 | };
28 |
29 | return (
30 | <>
31 | {loading ? (
32 |
33 |
34 |
35 | ) : (
36 |
102 | )}
103 | >
104 | );
105 | }
106 |
107 | export default AdminLoginForm;
108 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/Lists/PizzasList.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | // Import Thunks
5 | import {
6 | deletePizzaById,
7 | listPizzas,
8 | } from '../../../../../redux/asyncThunks/pizzaThunks';
9 |
10 | // Import Components
11 | import Loader from '../../../Loader';
12 | import Message from '../../../Message';
13 | import Table from '../Table';
14 |
15 | function PizzasList() {
16 | const pizzaColumns = ['_id', 'name', 'price', 'size'];
17 |
18 | const dispatch = useDispatch();
19 |
20 | const pizza = useSelector((state) => state.pizza);
21 | const {
22 | loading,
23 | pizzaList,
24 | pizzaListError,
25 | pizzaDeleteByIdError,
26 | pizzaDeleteByIdSuccess,
27 | } = pizza;
28 |
29 | const handleDelete = (id) => {
30 | dispatch(deletePizzaById(id)).then(() => dispatch(listPizzas({})));
31 | };
32 |
33 | const successMessageDelete = pizzaDeleteByIdSuccess && {
34 | status: '200',
35 | message: 'pizza Deleted Successfully!',
36 | };
37 |
38 | const PizzaByAdmin = pizzaList.filter((pizza) => pizza.createdBy === 'admin');
39 | const customPizzas = pizzaList.filter((pizza) => pizza.createdBy === 'user');
40 |
41 | useEffect(() => {
42 | if (!pizzaList) {
43 | dispatch(listPizzas({}));
44 | }
45 | }, [dispatch, pizzaList]);
46 |
47 | return (
48 |
49 |
All pizzas
50 | {loading ? (
51 |
52 | ) : (
53 | <>
54 | {(pizzaListError || pizzaDeleteByIdError) && (
55 |
{pizzaListError || pizzaDeleteByIdError}
56 | )}
57 | {successMessageDelete &&
{successMessageDelete} }
58 |
59 | {pizzaList.length > 0 ? (
60 | <>
61 | {PizzaByAdmin.length > 0 ? (
62 |
63 |
64 | Pizzas By Admin
65 |
66 |
71 |
72 | ) : (
73 |
74 | No Pizzas Created By Admin Found..
75 |
76 | )}
77 | {customPizzas.length > 0 ? (
78 |
79 |
80 | Custom Pizzas
81 |
82 |
87 |
88 | ) : (
89 |
90 | No Custom Pizzas Found..
91 |
92 | )}
93 | >
94 | ) : (
95 |
96 | No Pizzas Found..
97 |
98 | )}
99 |
100 | >
101 | )}
102 |
103 | );
104 | }
105 |
106 | export default PizzasList;
107 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Dashboard/Lists/InventoryList.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | // Import Thunks
5 | import {
6 | listInventory,
7 | deleteStockById,
8 | } from '../../../../../redux/asyncThunks/inventoryThunks';
9 |
10 | // Import Components
11 | import Loader from '../../../Loader';
12 | import Message from '../../../Message';
13 | import Table from '../Table';
14 |
15 | function InventoryList() {
16 | const inventoryColumns = ['_id', 'item', 'price', 'threshold', 'quantity'];
17 |
18 | const dispatch = useDispatch();
19 |
20 | const inventory = useSelector((state) => state.inventory);
21 | const {
22 | loading,
23 | inventoryList,
24 | inventoryListError,
25 | inventoryDeleteByIdError,
26 | inventoryDeleteByIdSuccess,
27 | } = inventory;
28 |
29 | const handleDelete = (id) => {
30 | dispatch(deleteStockById(id)).then(() => dispatch(listInventory({})));
31 | };
32 |
33 | const handleChange = (id) => {
34 | console.log(id);
35 | };
36 |
37 | const successMessageDelete = inventoryDeleteByIdSuccess && {
38 | status: '200',
39 | message: 'Inventory Item Deleted Successfully!',
40 | };
41 |
42 | useEffect(() => {
43 | if (!inventoryList) {
44 | dispatch(listInventory({}));
45 | }
46 | }, [dispatch, inventoryList]);
47 |
48 | return (
49 |
50 |
All Stocks
51 | {loading ? (
52 |
53 | ) : (
54 | <>
55 | {(inventoryListError || inventoryDeleteByIdError) && (
56 |
{inventoryListError || inventoryDeleteByIdError}
57 | )}
58 | {successMessageDelete &&
{successMessageDelete} }
59 |
60 | {inventoryList ? (
61 | <>
62 |
63 |
64 | All Bases
65 |
66 |
72 |
73 |
74 |
75 | All Cheeses
76 |
77 |
83 |
84 |
85 |
86 | All Sauces
87 |
88 |
94 |
95 |
96 |
97 | All Veggies
98 |
99 |
105 |
106 | >
107 | ) : (
108 |
109 | No Stock Found..
110 |
111 | )}
112 |
113 | >
114 | )}
115 |
116 | );
117 | }
118 |
119 | export default InventoryList;
120 |
--------------------------------------------------------------------------------
/client/src/components/ui/CheckoutSteps/PaymentStep.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect, useState } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | // Import Actions
6 | import {
7 | createRazorPayOrder,
8 | savePaymentMethod,
9 | } from '../../../redux/slices/cartSlice';
10 |
11 | // Import Components
12 | import Button from '../Button';
13 | import Loader from '../Loader';
14 | import Message from '../Message';
15 |
16 | function PaymentStep({ setCurrentStep }) {
17 | const dispatch = useDispatch();
18 |
19 | const cart = useSelector((state) => state.cart);
20 | const {
21 | shippingAddress,
22 | cartItems,
23 | orderGetRazorPayOrderDetails,
24 | orderGetRazorPayOrderIdError,
25 | orderGetRazorPayOrderIdSuccess,
26 | } = cart;
27 |
28 | const order = useSelector((state) => state.order);
29 | const { loading } = order;
30 |
31 | const [paymentMethod, setPaymentMethod] = useState(cart.paymentMethod || '');
32 |
33 | useEffect(() => {
34 | if (!shippingAddress) {
35 | setCurrentStep('Shipping');
36 | }
37 | }, [shippingAddress, setCurrentStep]);
38 |
39 | const submitHandler = (e) => {
40 | e.preventDefault();
41 |
42 | const amount =
43 | cartItems &&
44 | Math.round(
45 | (
46 | cartItems.reduce((acc, item) => acc + item.price * item.qty, 0) +
47 | (cartItems.reduce((acc, item) => acc + item.price * item.qty, 0) > 100
48 | ? 0
49 | : 10) +
50 | Number(
51 | (
52 | 0.15 *
53 | cartItems.reduce((acc, item) => acc + item.price * item.qty, 0)
54 | ).toFixed(2)
55 | )
56 | ).toFixed(2)
57 | );
58 |
59 | dispatch(savePaymentMethod(paymentMethod));
60 | dispatch(createRazorPayOrder({ amount, currency: 'USD' }));
61 | };
62 |
63 | useEffect(() => {
64 | if (orderGetRazorPayOrderIdSuccess && orderGetRazorPayOrderDetails) {
65 | setCurrentStep('Place Order');
66 | }
67 | }, [
68 | orderGetRazorPayOrderIdSuccess,
69 | orderGetRazorPayOrderDetails,
70 | setCurrentStep,
71 | ]);
72 | return (
73 |
123 | );
124 | }
125 |
126 | PaymentStep.propTypes = {
127 | setCurrentStep: PropTypes.func.isRequired,
128 | };
129 |
130 | export default PaymentStep;
131 |
--------------------------------------------------------------------------------
/client/src/components/ui/Cart/CartModal/CartModal.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect, useState } from 'react';
3 | import { FaMoneyCheckAlt, FaShoppingCart, FaTimes } from 'react-icons/fa';
4 | import { useSelector } from 'react-redux';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | // Import Components
8 | import Button from '../../Button';
9 | import Loader from '../../Loader';
10 | import CartItemList from './CartItemList';
11 | import Message from '../../Message';
12 |
13 | function CartModal({ onClose }) {
14 | const [modalVisible, setModalVisible] = useState(false);
15 | const navigate = useNavigate();
16 |
17 | const cart = useSelector((state) => state.cart);
18 | const { loading, cartItems, cartAddItemError, cartRemoveItemError } = cart;
19 |
20 | const handleModalClose = () => {
21 | setModalVisible(false);
22 | setTimeout(() => {
23 | onClose();
24 | }, 300);
25 | };
26 |
27 | const handleCheckout = () => {
28 | setModalVisible(false);
29 | setTimeout(() => {
30 | onClose();
31 | }, 300);
32 | navigate('/checkout');
33 | };
34 |
35 | useEffect(() => {
36 | if (onClose) {
37 | setModalVisible(true);
38 | }
39 | }, [onClose]);
40 |
41 | return (
42 |
48 |
e.stopPropagation()}
51 | >
52 |
53 |
54 |
55 | Your Cart
56 |
57 |
61 |
62 |
63 |
64 | {loading ? (
65 |
66 | ) : (
67 | <>
68 | {(cartAddItemError || cartRemoveItemError) && (
69 |
{cartAddItemError || cartRemoveItemError}
70 | )}
71 | {cartItems && cartItems.length > 0 ? (
72 |
73 |
74 |
75 |
76 | Total Cost: $
77 | {cartItems
78 | .reduce((acc, item) => acc + item.qty * item.price, 0)
79 | .toFixed(2)}
80 |
81 |
82 | *Shipping and taxes calculated at checkout
83 |
84 |
85 |
86 | By proceeding to checkout, you agree to our Terms of Service
87 | and Privacy Policy.
88 |
89 |
90 | You also agree that your order will be handled by our third
91 | party payment processor.
92 |
93 |
99 |
100 | Checkout
101 |
102 |
103 |
104 | ) : (
105 |
Your Cart is Empty!
106 | )}
107 | >
108 | )}
109 |
110 |
111 | );
112 | }
113 |
114 | CartModal.propTypes = {
115 | onClose: PropTypes.func.isRequired,
116 | };
117 |
118 | export default CartModal;
119 |
--------------------------------------------------------------------------------
/client/src/redux/slices/pizzaSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | // Import Thunks
4 | import {
5 | deletePizzaById,
6 | getPizzaById,
7 | listPizzas,
8 | createPizza,
9 | updatePizzaById,
10 | } from '../asyncThunks/pizzaThunks';
11 |
12 | // Initial State
13 | const initialState = {
14 | pizzaInfo: null,
15 | pizzaList: [],
16 | pizzaListError: null,
17 | pizzaCreateError: null,
18 | pizzaGetByIdError: null,
19 | pizzaUpdateByIdError: null,
20 | pizzaDeleteByIdError: null,
21 | pizzaListSuccess: false,
22 | pizzaCreateSuccess: false,
23 | pizzaGetByIdSuccess: false,
24 | pizzaUpdateByIdSuccess: false,
25 | pizzaDeleteByIdSuccess: false,
26 | loading: false,
27 | };
28 |
29 | // Create Slice
30 | const pizzaSlice = createSlice({
31 | name: 'pizza',
32 | initialState,
33 | reducers: {
34 | clearPizzaData: (state) => {
35 | state.pizzaList = [];
36 | state.pizzaInfo = null;
37 | state.pizzaListError = null;
38 | state.pizzaCreateError = null;
39 | state.pizzaGetByIdError = null;
40 | state.pizzaUpdateByIdError = null;
41 | state.pizzaDeleteByIdError = null;
42 | state.pizzaListSuccess = false;
43 | state.pizzaCreateSuccess = false;
44 | state.pizzaGetByIdSuccess = false;
45 | state.pizzaUpdateByIdSuccess = false;
46 | state.pizzaDeleteByIdSuccess = false;
47 | state.loading = false;
48 | },
49 | },
50 | extraReducers: (builder) => {
51 | builder
52 | .addCase(createPizza.pending, (state) => {
53 | state.loading = true;
54 | state.pizzaCreateError = null;
55 | state.pizzaCreateSuccess = false;
56 | })
57 | .addCase(createPizza.fulfilled, (state, action) => {
58 | state.loading = false;
59 | state.pizzaInfo = action.payload;
60 | state.pizzaCreateSuccess = true;
61 | })
62 | .addCase(createPizza.rejected, (state, action) => {
63 | state.loading = false;
64 | state.pizzaCreateError = action.payload;
65 | })
66 | .addCase(listPizzas.pending, (state) => {
67 | state.loading = true;
68 | state.pizzaListError = null;
69 | state.pizzaListSuccess = false;
70 | })
71 | .addCase(listPizzas.fulfilled, (state, action) => {
72 | state.loading = false;
73 | state.pizzaList = action.payload;
74 | state.pizzaListSuccess = true;
75 | })
76 | .addCase(listPizzas.rejected, (state, action) => {
77 | state.loading = false;
78 | state.pizzaListError = action.payload;
79 | })
80 | .addCase(getPizzaById.pending, (state) => {
81 | state.loading = true;
82 | state.pizzaGetByIdError = null;
83 | state.pizzaGetByIdSuccess = false;
84 | })
85 | .addCase(getPizzaById.fulfilled, (state, action) => {
86 | state.loading = false;
87 | state.pizzaInfo = action.payload;
88 | state.pizzaGetByIdSuccess = true;
89 | })
90 | .addCase(getPizzaById.rejected, (state, action) => {
91 | state.loading = false;
92 | state.pizzaGetByIdError = action.payload;
93 | })
94 | .addCase(updatePizzaById.pending, (state) => {
95 | state.loading = true;
96 | state.pizzaUpdateByIdError = null;
97 | state.pizzaUpdateByIdSuccess = false;
98 | })
99 | .addCase(updatePizzaById.fulfilled, (state, action) => {
100 | state.loading = false;
101 | state.pizzaInfo = action.payload;
102 | state.pizzaUpdateByIdSuccess = true;
103 | })
104 | .addCase(updatePizzaById.rejected, (state, action) => {
105 | state.loading = false;
106 | state.pizzaUpdateByIdError = action.payload;
107 | })
108 | .addCase(deletePizzaById.pending, (state) => {
109 | state.loading = true;
110 | state.pizzaDeleteByIdError = null;
111 | state.pizzaDeleteByIdSuccess = false;
112 | })
113 | .addCase(deletePizzaById.fulfilled, (state) => {
114 | state.loading = false;
115 | state.pizzaDeleteByIdSuccess = true;
116 | })
117 | .addCase(deletePizzaById.rejected, (state, action) => {
118 | state.loading = false;
119 | state.pizzaDeleteByIdError = action.payload;
120 | });
121 | },
122 | });
123 |
124 | // Export Actions
125 | export const { clearPizzaData } = pizzaSlice.actions;
126 |
127 | // Export Reducer
128 | export default pizzaSlice.reducer;
129 |
--------------------------------------------------------------------------------
/client/src/screens/User/ProfileScreen.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | // Import Thunks
6 | import { listOrdersByUserId } from '../../redux/asyncThunks/orderThunks';
7 | import { getUserDetails } from '../../redux/asyncThunks/userThunks';
8 |
9 | // Import Components
10 | import VerficationModal from '../../components/ui/Auth/VerficationModal';
11 | import Button from '../../components/ui/Button';
12 | import Loader from '../../components/ui/Loader';
13 | import Message from '../../components/ui/Message';
14 | import EditProfileForm from '../../components/ui/Profile/EditProfileForm';
15 | import Profile from '../../components/ui/Profile/Profile';
16 | import UserOrdersTable from '../../components/ui/UserOrdersTable';
17 |
18 | function ProfileScreen() {
19 | const [isEditing, setIsEditing] = useState(false);
20 | const [modalVisible, setModalVisible] = useState(false);
21 |
22 | const dispatch = useDispatch();
23 | const navigate = useNavigate();
24 |
25 | const user = useSelector((state) => state.user);
26 | const {
27 | loading,
28 | userDetails,
29 | userDetailsError,
30 | userInfo,
31 | userUpdateProfileSuccess,
32 | } = user;
33 |
34 | const order = useSelector((state) => state.order);
35 | const {
36 | loading: orderLoading,
37 | orderListByUserId,
38 | orderListByUserIdError,
39 | } = order;
40 |
41 | const successMessage = userUpdateProfileSuccess && {
42 | status: '200',
43 | message: 'Updated Successfully!',
44 | };
45 |
46 | useEffect(() => {
47 | if (!userInfo) {
48 | navigate('/login');
49 | } else {
50 | dispatch(getUserDetails({}));
51 | dispatch(listOrdersByUserId(userInfo._id));
52 | }
53 | }, [dispatch, navigate, userInfo]);
54 |
55 | useEffect(() => {
56 | if (userDetails && !userDetails.isVerified) {
57 | setModalVisible(true);
58 | }
59 | }, [userDetails]);
60 |
61 | useEffect(() => {
62 | if (userUpdateProfileSuccess) {
63 | dispatch(getUserDetails({}));
64 | }
65 | }, [dispatch, userUpdateProfileSuccess]);
66 |
67 | return (
68 | <>
69 |
70 | {loading || orderLoading ? (
71 |
72 | ) : userDetailsError || orderListByUserIdError ? (
73 | {userDetailsError || orderListByUserIdError}
74 | ) : (
75 | userDetails && (
76 | <>
77 |
78 |
79 | {isEditing ? 'Edit Profile' : 'Your Profile'}
80 |
81 | {isEditing ? (
82 |
83 | ) : (
84 | <>
85 | {successMessage &&
{successMessage} }
86 |
87 | >
88 | )}
89 | {!isEditing && (
90 |
setIsEditing(!isEditing)}
94 | >
95 | Edit Profile
96 |
97 | )}
98 |
99 |
100 | {orderListByUserId.length > 0 ? (
101 | <>
102 |
103 | My Orders
104 |
105 |
106 | >
107 | ) : (
108 |
109 | No Orders Found!
110 |
111 | )}
112 |
113 | >
114 | )
115 | )}
116 |
117 | {modalVisible && (
118 | setModalVisible(false)} />
119 | )}
120 | >
121 | );
122 | }
123 |
124 | export default ProfileScreen;
125 |
--------------------------------------------------------------------------------
/client/src/components/ui/Auth/ForgetPassword/PasswordForm.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState, useEffect } from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 | import { useDispatch, useSelector } from 'react-redux';
5 |
6 | // Import Thunks
7 | import { resetPassword } from '../../../../redux/asyncThunks/userThunks';
8 | import {
9 | setPasswordResetOTP,
10 | setPasswordResetEmail,
11 | } from '../../../../redux/slices/userSlice';
12 |
13 | // Import Components
14 | import Button from '../../Button';
15 | import Loader from '../../Loader';
16 | import Message from '../../Message';
17 |
18 | function PasswordForm({ setCurrentStep }) {
19 | const [password, setPassword] = useState('');
20 | const [confirmPassword, setConfirmPassword] = useState('');
21 |
22 | const navigate = useNavigate();
23 | const dispatch = useDispatch();
24 |
25 | const user = useSelector((state) => state.user);
26 | const {
27 | loading,
28 | userPasswordResetOTP,
29 | userPasswordResetEmail,
30 | userResetPasswordError,
31 | userResetPasswordSuccess,
32 | } = user;
33 |
34 | const submitHandler = (e) => {
35 | e.preventDefault();
36 | dispatch(
37 | resetPassword({
38 | email: userPasswordResetEmail,
39 | resetToken: userPasswordResetOTP,
40 | newPassword: password,
41 | confirmNewPassword: confirmPassword,
42 | })
43 | ).then(() => {
44 | dispatch(setPasswordResetOTP(''));
45 | dispatch(setPasswordResetEmail(''));
46 | });
47 | };
48 |
49 | useEffect(() => {
50 | if (userResetPasswordError) {
51 | setInterval(() => {
52 | setCurrentStep('EmailForm');
53 | }, 1000);
54 | }
55 | }, [userResetPasswordError, setCurrentStep]);
56 |
57 | useEffect(() => {
58 | if (userResetPasswordSuccess) {
59 | navigate('/login');
60 | setInterval(() => {
61 | setCurrentStep('EmailForm');
62 | }, 1000);
63 | }
64 | }, [userResetPasswordSuccess, setCurrentStep, navigate]);
65 |
66 | return (
67 | <>
68 | {loading ? (
69 |
70 |
71 |
72 | ) : (
73 |
127 | )}
128 | >
129 | );
130 | }
131 |
132 | PasswordForm.propTypes = {
133 | setCurrentStep: PropTypes.func.isRequired,
134 | };
135 |
136 | export default PasswordForm;
137 |
--------------------------------------------------------------------------------
/client/src/components/ui/Admin/Auth/RegisterForm.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 |
4 | import Button from '../../Button';
5 | import Loader from '../../Loader';
6 | import Message from '../../Message';
7 |
8 | import { registerAdmin } from '../../../../redux/asyncThunks/adminThunks';
9 |
10 | function AdminRegisterForm() {
11 | const [name, setName] = useState('');
12 | const [email, setEmail] = useState('');
13 | const [password, setPassword] = useState('');
14 | const [confirmPassword, setConfirmPassword] = useState('');
15 |
16 | const dispatch = useDispatch();
17 |
18 | const admin = useSelector((state) => state.admin);
19 | const { loading, adminUserRegisterError } = admin;
20 |
21 | const handleRegister = (e) => {
22 | e.preventDefault();
23 | dispatch(
24 | registerAdmin({
25 | name,
26 | email,
27 | password,
28 | confirmPassword,
29 | })
30 | );
31 | };
32 |
33 | return (
34 | <>
35 | {loading ? (
36 |
37 |
38 |
39 | ) : (
40 |
134 | )}
135 | >
136 | );
137 | }
138 |
139 | export default AdminRegisterForm;
140 |
--------------------------------------------------------------------------------
/client/src/redux/asyncThunks/pizzaThunks.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk } from '@reduxjs/toolkit';
2 | import axios from 'axios';
3 |
4 | // Create Async Thunks
5 |
6 | // Create Pizza
7 | export const createPizza = createAsyncThunk(
8 | 'pizza/createPizza',
9 | async (pizzaData, { rejectWithValue, getState }) => {
10 | try {
11 | const {
12 | admin: { adminUserInfo },
13 | user: { userInfo },
14 | } = getState();
15 |
16 | const config = {
17 | headers: {
18 | Authorization: `Bearer ${
19 | adminUserInfo ? adminUserInfo.token : userInfo.token
20 | }`,
21 | },
22 | };
23 |
24 | const { data } = await axios.post(
25 | `${import.meta.env.VITE_SERVER_URL}/pizzas`,
26 | {
27 | name: pizzaData.name,
28 | description: pizzaData.description,
29 | bases: pizzaData.bases,
30 | sauces: pizzaData.sauces,
31 | cheeses: pizzaData.cheeses,
32 | veggies: pizzaData.veggies,
33 | price: pizzaData.price,
34 | size: pizzaData.size,
35 | imageUrl: pizzaData.imageUrl,
36 | },
37 | config
38 | );
39 |
40 | return data;
41 | } catch (error) {
42 | return rejectWithValue({
43 | status: error.response && error.response.status,
44 | message:
45 | error.response && error.response.data.message
46 | ? error.response.data.message
47 | : error.message,
48 | });
49 | }
50 | }
51 | );
52 |
53 | // Fetch All Pizzas
54 | export const listPizzas = createAsyncThunk(
55 | 'pizza/listPizzas',
56 | async (_, { rejectWithValue }) => {
57 | try {
58 | const { data } = await axios.get(
59 | `${import.meta.env.VITE_SERVER_URL}/pizzas`
60 | );
61 |
62 | return data;
63 | } catch (error) {
64 | return rejectWithValue({
65 | status: error.response && error.response.status,
66 | message:
67 | error.response && error.response.data.message
68 | ? error.response.data.message
69 | : error.message,
70 | });
71 | }
72 | }
73 | );
74 |
75 | // Fetch Single Pizza
76 | export const getPizzaById = createAsyncThunk(
77 | 'pizza/getPizzaById',
78 | async (id, { rejectWithValue }) => {
79 | try {
80 | const { data } = await axios.get(
81 | `${import.meta.env.VITE_SERVER_URL}/pizzas/${id}`
82 | );
83 |
84 | return data;
85 | } catch (error) {
86 | return rejectWithValue({
87 | status: error.response && error.response.status,
88 | message:
89 | error.response && error.response.data.message
90 | ? error.response.data.message
91 | : error.message,
92 | });
93 | }
94 | }
95 | );
96 |
97 | // Update Pizza By Id
98 | export const updatePizzaById = createAsyncThunk(
99 | 'pizza/updatePizzaById',
100 | async (
101 | {
102 | id,
103 | name,
104 | description,
105 | base,
106 | sauces,
107 | cheeses,
108 | veggies,
109 | price,
110 | size,
111 | imageUrl,
112 | },
113 | { rejectWithValue, getState }
114 | ) => {
115 | try {
116 | const {
117 | admin: { adminUserInfo },
118 | } = getState();
119 |
120 | const config = {
121 | headers: {
122 | Authorization: `Bearer ${adminUserInfo.token}`,
123 | },
124 | };
125 |
126 | const { data } = await axios.put(
127 | `${import.meta.env.VITE_SERVER_URL}/pizzas/${id}`,
128 | {
129 | name,
130 | description,
131 | base,
132 | sauces,
133 | cheeses,
134 | veggies,
135 | price,
136 | size,
137 | imageUrl,
138 | },
139 | config
140 | );
141 |
142 | return data;
143 | } catch (error) {
144 | return rejectWithValue({
145 | status: error.response && error.response.status,
146 | message:
147 | error.response && error.response.data.message
148 | ? error.response.data.message
149 | : error.message,
150 | });
151 | }
152 | }
153 | );
154 |
155 | // Delete Pizza By Id
156 | export const deletePizzaById = createAsyncThunk(
157 | 'pizza/deletePizzaById',
158 | async (id, { rejectWithValue, getState }) => {
159 | try {
160 | const {
161 | admin: { adminUserInfo },
162 | } = getState();
163 |
164 | const config = {
165 | headers: {
166 | Authorization: `Bearer ${adminUserInfo.token}`,
167 | },
168 | };
169 |
170 | const { data } = await axios.delete(
171 | `${import.meta.env.VITE_SERVER_URL}/pizzas/${id}`,
172 | config
173 | );
174 |
175 | return data;
176 | } catch (error) {
177 | return rejectWithValue({
178 | status: error.response && error.response.status,
179 | message:
180 | error.response && error.response.data.message
181 | ? error.response.data.message
182 | : error.message,
183 | });
184 | }
185 | }
186 | );
187 |
--------------------------------------------------------------------------------
/client/src/components/ui/Auth/VerficationModal.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useEffect, useState } from 'react';
3 | import { FaTimes } from 'react-icons/fa';
4 | import { useDispatch, useSelector } from 'react-redux';
5 |
6 | // Import THunks
7 | import {
8 | getUserDetails,
9 | verifyEmail,
10 | } from '../../../redux/asyncThunks/userThunks';
11 |
12 | // Import Components
13 | import Button from '../Button';
14 | import Loader from '../Loader';
15 | import Message from '../Message';
16 |
17 | function VerficationModal({ onClose }) {
18 | const [modalVisible, setModalVisible] = useState(false);
19 |
20 | const dispatch = useDispatch();
21 |
22 | const user = useSelector((state) => state.user);
23 | const { loading, userDetails, userVerifyEmailError, userVerifyEmailSuccess } =
24 | user;
25 |
26 | const [email, setEmail] = useState(userDetails?.email || '');
27 | const [verificationCode, setVerificationCode] = useState('');
28 |
29 | const handleModalClose = () => {
30 | setModalVisible(false);
31 | setTimeout(() => {
32 | onClose();
33 | }, 300);
34 | };
35 |
36 | const submitHandler = (e) => {
37 | e.preventDefault();
38 | dispatch(verifyEmail({ email, verificationCode }));
39 | console.log('VerficationModal', email, verificationCode);
40 | };
41 |
42 | useEffect(() => {
43 | if (!userDetails) {
44 | dispatch(getUserDetails({}));
45 | }
46 |
47 | if (userVerifyEmailSuccess) {
48 | dispatch(getUserDetails({}));
49 | setModalVisible(false);
50 | setTimeout(() => {
51 | onClose();
52 | }, 300);
53 | }
54 |
55 | if (onClose) {
56 | setModalVisible(true);
57 | }
58 | }, [dispatch, onClose, userVerifyEmailSuccess, userDetails]);
59 | return (
60 |
66 |
e.stopPropagation()}
69 | >
70 |
71 |
72 | Verfiy Your Email
73 |
74 |
78 |
79 |
80 |
81 | {loading ? (
82 |
83 | ) : (
84 |
138 | )}
139 |
140 |
141 | );
142 | }
143 |
144 | VerficationModal.propTypes = {
145 | onClose: PropTypes.func,
146 | };
147 |
148 | export default VerficationModal;
149 |
--------------------------------------------------------------------------------
/client/src/redux/slices/inventorySlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | // Import Thunks
4 | import {
5 | createStock,
6 | listInventory,
7 | updateStockById,
8 | deleteStockById,
9 | getStockById,
10 | } from '../asyncThunks/inventoryThunks';
11 |
12 | // Initial State
13 | const initialState = {
14 | inventoryList: [],
15 | stockDetails: {},
16 | inventoryListError: null,
17 | inventoryCreateStockError: null,
18 | inventoryGetStockByIdError: null,
19 | inventoryUpdateStockByIdError: null,
20 | inventoryDeleteStockByIdError: null,
21 | inventoryListSuccess: false,
22 | inventoryCreateStockSuccess: false,
23 | inventoryGetStockByIdSuccess: false,
24 | inventoryUpdateStockByIdSuccess: false,
25 | inventoryDeleteStockByIdSuccess: false,
26 | loading: false,
27 | };
28 |
29 | // Inventory Slice
30 | const inventorySlice = createSlice({
31 | name: 'inventory',
32 | initialState,
33 | reducers: {
34 | clearInventoryData(state) {
35 | state.inventoryList = [];
36 | state.stockDetails = {};
37 | state.inventoryListError = null;
38 | state.inventoryCreateStockError = null;
39 | state.inventoryGetStockByIdError = null;
40 | state.inventoryUpdateStockByIdError = null;
41 | state.inventoryDeleteStockByIdError = null;
42 | state.inventoryListSuccess = false;
43 | state.inventoryCreateStockSuccess = false;
44 | state.inventoryGetStockByIdSuccess = false;
45 | state.inventoryUpdateStockByIdSuccess = false;
46 | state.inventoryDeleteStockByIdSuccess = false;
47 | state.loading = false;
48 | },
49 | },
50 | extraReducers: (builder) => {
51 | builder
52 | .addCase(createStock.pending, (state) => {
53 | state.loading = true;
54 | state.inventoryCreateStockSuccess = false;
55 | state.inventoryCreateStockError = null;
56 | })
57 | .addCase(createStock.fulfilled, (state, action) => {
58 | state.loading = false;
59 | state.stockDetails = action.payload;
60 | state.inventoryCreateStockSuccess = true;
61 | state.inventoryCreateStockError = null;
62 | })
63 | .addCase(createStock.rejected, (state, action) => {
64 | state.loading = false;
65 | state.inventoryCreateStockSuccess = false;
66 | state.inventoryCreateStockError = action.payload;
67 | })
68 | .addCase(listInventory.pending, (state) => {
69 | state.loading = true;
70 | state.inventoryListSuccess = false;
71 | state.inventoryListError = null;
72 | })
73 | .addCase(listInventory.fulfilled, (state, action) => {
74 | state.loading = false;
75 | state.inventoryList = action.payload;
76 | state.inventoryListSuccess = true;
77 | state.inventoryListError = null;
78 | })
79 | .addCase(listInventory.rejected, (state, action) => {
80 | state.loading = false;
81 | state.inventoryListSuccess = false;
82 | state.inventoryListError = action.payload;
83 | })
84 | .addCase(getStockById.pending, (state) => {
85 | state.loading = true;
86 | state.inventoryGetStockByIdSuccess = false;
87 | state.inventoryGetStockByIdError = null;
88 | })
89 | .addCase(getStockById.fulfilled, (state, action) => {
90 | state.loading = false;
91 | state.stockDetails = action.payload;
92 | state.inventoryGetStockByIdSuccess = true;
93 | state.inventoryGetStockByIdError = null;
94 | })
95 | .addCase(getStockById.rejected, (state, action) => {
96 | state.loading = false;
97 | state.inventoryGetStockByIdSuccess = false;
98 | state.inventoryGetStockByIdError = action.payload;
99 | })
100 | .addCase(updateStockById.pending, (state) => {
101 | state.loading = true;
102 | state.inventoryUpdateStockByIdSuccess = false;
103 | state.inventoryUpdateStockByIdError = null;
104 | })
105 | .addCase(updateStockById.fulfilled, (state, action) => {
106 | state.loading = false;
107 | state.stockDetails = action.payload;
108 | state.inventoryUpdateStockByIdSuccess = true;
109 | state.inventoryUpdateStockByIdError = null;
110 | })
111 | .addCase(updateStockById.rejected, (state, action) => {
112 | state.loading = false;
113 | state.inventoryUpdateStockByIdSuccess = false;
114 | state.inventoryUpdateStockByIdError = action.payload;
115 | })
116 | .addCase(deleteStockById.pending, (state) => {
117 | state.loading = true;
118 | state.inventoryDeleteStockByIdSuccess = false;
119 | state.inventoryDeleteStockByIdError = null;
120 | })
121 | .addCase(deleteStockById.fulfilled, (state) => {
122 | state.loading = false;
123 | state.inventoryDeleteStockByIdSuccess = true;
124 | state.inventoryDeleteStockByIdError = null;
125 | })
126 | .addCase(deleteStockById.rejected, (state, action) => {
127 | state.loading = false;
128 | state.inventoryDeleteStockByIdSuccess = false;
129 | state.inventoryDeleteStockByIdError = action.payload;
130 | });
131 | },
132 | });
133 |
134 | // Export Actions
135 | export const { clearInventoryData } = inventorySlice.actions;
136 |
137 | // Export Reducer
138 | export default inventorySlice.reducer;
139 |
--------------------------------------------------------------------------------
/client/src/redux/slices/orderSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | // Import Thunks
4 | import {
5 | createOrder,
6 | deleteOrderById,
7 | getOrderById,
8 | listOrders,
9 | listOrdersByUserId,
10 | updateOrderById,
11 | } from '../asyncThunks/orderThunks';
12 |
13 | // Create Intitial State
14 | const initialState = {
15 | orderInfo: {},
16 | orderList: [],
17 | orderListByUserId: [],
18 | orderCreateError: null,
19 | orderDetailsByIdError: null,
20 | orderListError: null,
21 | orderListByUserIdError: null,
22 | orderUpdateByIdError: null,
23 | orderDeleteByIdError: null,
24 | orderDetailsByIdSuccess: false,
25 | orderListSuccess: false,
26 | orderListByUserIdSuccess: false,
27 | orderCreateSuccess: false,
28 | orderUpdateByIdSuccess: false,
29 | orderDeleteByIdSuccess: false,
30 | loading: false,
31 | };
32 |
33 | // Create Slice
34 | const orderSlice = createSlice({
35 | name: 'order',
36 | initialState,
37 | reducers: {
38 | clearOrderData: (state) => {
39 | state.orderInfo = {};
40 | state.orderListByUserId = [];
41 | state.orderList = [];
42 | state.orderCreateError = null;
43 | state.orderListByUserIdError = null;
44 | state.orderListError = null;
45 | state.orderDetailsByIdError = null;
46 | state.orderUpdateByIdError = null;
47 | state.orderDeleteByIdError = null;
48 | state.orderCreateSuccess = false;
49 | state.orderListByUserIdSuccess = false;
50 | state.orderListSuccess = false;
51 | state.orderDetailsByIdSuccess = false;
52 | state.orderUpdateByIdSuccess = false;
53 | state.orderDeleteByIdSuccess = false;
54 | state.loading = false;
55 | },
56 | },
57 | extraReducers: (builder) => {
58 | builder
59 | .addCase(createOrder.pending, (state) => {
60 | state.loading = true;
61 | state.orderCreateSuccess = false;
62 | state.orderCreateError = null;
63 | })
64 | .addCase(createOrder.fulfilled, (state, action) => {
65 | state.loading = false;
66 | state.orderCreateSuccess = true;
67 | state.orderInfo = action.payload;
68 | })
69 | .addCase(createOrder.rejected, (state, action) => {
70 | state.loading = false;
71 | state.orderCreateError = action.payload;
72 | })
73 | .addCase(listOrders.pending, (state) => {
74 | state.loading = true;
75 | state.orderListSuccess = false;
76 | state.orderListError = null;
77 | })
78 | .addCase(listOrders.fulfilled, (state, action) => {
79 | state.loading = false;
80 | state.orderListSuccess = true;
81 | state.orderList = action.payload;
82 | })
83 | .addCase(listOrders.rejected, (state, action) => {
84 | state.loading = false;
85 | state.orderListError = action.payload;
86 | })
87 | .addCase(listOrdersByUserId.pending, (state) => {
88 | state.loading = true;
89 | state.orderListByUserIdSuccess = false;
90 | state.orderListByUserIdError = null;
91 | })
92 | .addCase(listOrdersByUserId.fulfilled, (state, action) => {
93 | state.loading = false;
94 | state.orderListByUserIdSuccess = true;
95 | state.orderListByUserId = action.payload;
96 | })
97 | .addCase(listOrdersByUserId.rejected, (state, action) => {
98 | state.loading = false;
99 | state.orderListByUserIdError = action.payload;
100 | })
101 | .addCase(getOrderById.pending, (state) => {
102 | state.loading = true;
103 | state.orderDetailsByIdSuccess = false;
104 | state.orderDetailsByIdError = null;
105 | })
106 | .addCase(getOrderById.fulfilled, (state, action) => {
107 | state.loading = false;
108 | state.orderDetailsByIdSuccess = true;
109 | state.orderInfo = action.payload;
110 | })
111 | .addCase(getOrderById.rejected, (state, action) => {
112 | state.loading = false;
113 | state.orderDetailsByIdError = action.payload;
114 | })
115 | .addCase(updateOrderById.pending, (state) => {
116 | state.loading = true;
117 | state.orderUpdateByIdSuccess = false;
118 | state.orderUpdateByIdError = null;
119 | })
120 | .addCase(updateOrderById.fulfilled, (state, action) => {
121 | state.loading = false;
122 | state.orderUpdateByIdSuccess = true;
123 | state.orderInfo = action.payload;
124 | })
125 | .addCase(updateOrderById.rejected, (state, action) => {
126 | state.loading = false;
127 | state.orderUpdateByIdError = action.payload;
128 | })
129 | .addCase(deleteOrderById.pending, (state) => {
130 | state.loading = true;
131 | state.orderDeleteByIdSuccess = false;
132 | state.orderDeleteByIdError = null;
133 | })
134 | .addCase(deleteOrderById.fulfilled, (state) => {
135 | state.loading = false;
136 | state.orderDeleteByIdSuccess = true;
137 | })
138 | .addCase(deleteOrderById.rejected, (state, action) => {
139 | state.loading = false;
140 | state.orderDeleteByIdError = action.payload;
141 | });
142 | },
143 | });
144 |
145 | // Export Actions
146 | export const { clearOrderData } = orderSlice.actions;
147 |
148 | // Export Reducer
149 | export default orderSlice.reducer;
150 |
--------------------------------------------------------------------------------
/client/src/redux/asyncThunks/inventoryThunks.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk } from '@reduxjs/toolkit';
2 | import axios from 'axios';
3 |
4 | // Create Thunks
5 |
6 | // Create Inventory
7 | export const createStock = createAsyncThunk(
8 | 'inventory/createStock',
9 | async (stockData, { rejectWithValue, getState }) => {
10 | try {
11 | const {
12 | admin: { adminUserInfo },
13 | } = getState();
14 |
15 | const config = {
16 | headers: {
17 | Authorization: `Bearer ${adminUserInfo.token}`,
18 | },
19 | };
20 |
21 | const { data } = await axios.post(
22 | `${import.meta.env.VITE_SERVER_URL}/stocks`,
23 | {
24 | type: stockData.type,
25 | item: stockData.item,
26 | price: stockData.price,
27 | quantity: stockData.quantity,
28 | threshold: stockData.threshold,
29 | },
30 | config
31 | );
32 |
33 | return data;
34 | } catch (error) {
35 | return rejectWithValue({
36 | status: error.response && error.response.status,
37 | message:
38 | error.response && error.response.data.message
39 | ? error.response.data.message
40 | : error.message,
41 | });
42 | }
43 | }
44 | );
45 |
46 | // Get All Inventory
47 | export const listInventory = createAsyncThunk(
48 | 'inventory/listStocks',
49 | async (_, { rejectWithValue, getState }) => {
50 | try {
51 | const {
52 | user: { userInfo },
53 | admin: { adminUserInfo },
54 | } = getState();
55 |
56 | let config;
57 |
58 | if (userInfo) {
59 | config = {
60 | headers: {
61 | Authorization: `Bearer ${userInfo.token}`,
62 | },
63 | };
64 | }
65 |
66 | if (adminUserInfo) {
67 | config = {
68 | headers: {
69 | Authorization: `Bearer ${adminUserInfo.token}`,
70 | },
71 | };
72 | }
73 |
74 | const { data } = await axios.get(
75 | `${import.meta.env.VITE_SERVER_URL}/stocks`,
76 | config
77 | );
78 |
79 | return data;
80 | } catch (error) {
81 | return rejectWithValue({
82 | status: error.response && error.response.status,
83 | message:
84 | error.response && error.response.data.message
85 | ? error.response.data.message
86 | : error.message,
87 | });
88 | }
89 | }
90 | );
91 |
92 | // Get Stock By Id
93 | export const getStockById = createAsyncThunk(
94 | 'inventory/getStockById',
95 | async (id, { rejectWithValue, getState }) => {
96 | try {
97 | const {
98 | user: { userInfo },
99 | admin: { adminUserInfo },
100 | } = getState();
101 |
102 | let config;
103 |
104 | if (userInfo) {
105 | config = {
106 | headers: {
107 | Authorization: `Bearer ${userInfo.token}`,
108 | },
109 | };
110 | }
111 |
112 | if (adminUserInfo) {
113 | config = {
114 | headers: {
115 | Authorization: `Bearer ${adminUserInfo.token}`,
116 | },
117 | };
118 | }
119 |
120 | const { data } = await axios.get(
121 | `${import.meta.env.VITE_SERVER_URL}/stocks/${id}`,
122 | config
123 | );
124 |
125 | return data;
126 | } catch (error) {
127 | return rejectWithValue({
128 | status: error.response && error.response.status,
129 | message:
130 | error.response && error.response.data.message
131 | ? error.response.data.message
132 | : error.message,
133 | });
134 | }
135 | }
136 | );
137 |
138 | // Update Stock By Id
139 | export const updateStockById = createAsyncThunk(
140 | 'inventory/updateStockById',
141 | async (id, { rejectWithValue, getState }) => {
142 | try {
143 | const {
144 | admin: { adminUserInfo },
145 | } = getState();
146 |
147 | const config = {
148 | headers: {
149 | Authorization: `Bearer ${adminUserInfo.token}`,
150 | },
151 | };
152 |
153 | const { data } = await axios.put(
154 | `${import.meta.env.VITE_SERVER_URL}/stocks/${id}`,
155 | config
156 | );
157 |
158 | return data;
159 | } catch (error) {
160 | return rejectWithValue({
161 | status: error.response && error.response.status,
162 | message:
163 | error.response && error.response.data.message
164 | ? error.response.data.message
165 | : error.message,
166 | });
167 | }
168 | }
169 | );
170 |
171 | // Delete Stock By Id
172 | export const deleteStockById = createAsyncThunk(
173 | 'inventory/deleteStockById',
174 | async (id, { rejectWithValue, getState }) => {
175 | try {
176 | const {
177 | admin: { adminUserInfo },
178 | } = getState();
179 |
180 | const config = {
181 | headers: {
182 | Authorization: `Bearer ${adminUserInfo.token}`,
183 | },
184 | };
185 |
186 | const { data } = await axios.delete(
187 | `${import.meta.env.VITE_SERVER_URL}/stocks/${id}`,
188 | config
189 | );
190 |
191 | return data;
192 | } catch (error) {
193 | return rejectWithValue({
194 | status: error.response && error.response.status,
195 | message:
196 | error.response && error.response.data.message
197 | ? error.response.data.message
198 | : error.message,
199 | });
200 | }
201 | }
202 | );
203 |
--------------------------------------------------------------------------------
/client/src/components/ui/UserOrdersTable.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState } from 'react';
3 | import {
4 | FaBoxOpen,
5 | FaCheck,
6 | FaChevronDown,
7 | FaChevronRight,
8 | FaTruck,
9 | FaUtensils,
10 | } from 'react-icons/fa';
11 |
12 | import Button from './Button';
13 |
14 | function UserOrdersTable({ orders }) {
15 | const ordersPerPage = 10;
16 | const [currentPage, setCurrentPage] = useState(1);
17 |
18 | // Calculate starting and ending indices for the current page
19 | const startIndex = (currentPage - 1) * ordersPerPage;
20 | const endIndex = startIndex + ordersPerPage;
21 |
22 | const getStatusIcon = (status) => {
23 | switch (status) {
24 | case 'Received':
25 | return (
26 |
27 | );
28 | case 'In the Kitchen':
29 | return (
30 |
31 | );
32 | case 'Sent for Delivery':
33 | return (
34 |
35 | );
36 | case 'Delivered':
37 | return (
38 |
39 | );
40 | default:
41 | return (
42 |
43 | );
44 | }
45 | };
46 |
47 | return (
48 | <>
49 |
50 |
51 |
52 |
53 | Order ID
54 | User ID
55 | Total Price
56 | Status
57 | Date
58 |
59 |
60 |
61 | {orders.slice(startIndex, endIndex).map((order) => (
62 |
63 |
64 | {order._id}
65 |
66 |
67 | {order.user}
68 |
69 |
70 | ${order.totalPrice}
71 |
72 |
73 | {getStatusIcon(order.status)}
74 |
75 |
76 | {order.createdAt.substring(0, 10)}
77 |
78 |
79 | ))}
80 |
81 |
82 |
83 |
84 | {orders.length > ordersPerPage && (
85 |
86 | {currentPage > 1 && (
87 | setCurrentPage((prev) => prev - 1)}
91 | >
92 | Prev
93 |
94 | )}
95 |
96 | Page {currentPage} of {Math.ceil(orders.length / ordersPerPage)}
97 |
98 | {currentPage < Math.ceil(orders.length / ordersPerPage) && (
99 | setCurrentPage((prev) => prev + 1)}
103 | >
104 | Next
105 |
106 | )}
107 |
108 | )}
109 |
110 |
111 |
112 |
113 | Recieved
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | In the Kitchen
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | Sent for Delivery
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | Delivered
138 |
139 |
140 | >
141 | );
142 | }
143 |
144 | UserOrdersTable.propTypes = {
145 | orders: PropTypes.array.isRequired,
146 | };
147 |
148 | export default UserOrdersTable;
149 |
--------------------------------------------------------------------------------
/client/src/redux/asyncThunks/orderThunks.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk } from '@reduxjs/toolkit';
2 | import axios from 'axios';
3 |
4 | // Create THunks
5 |
6 | // Order Create
7 | export const createOrder = createAsyncThunk(
8 | 'order/createOrder',
9 | async (orderData, { getState, rejectWithValue }) => {
10 | try {
11 | const {
12 | user: { userInfo },
13 | } = getState();
14 |
15 | const config = {
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | Authorization: `Bearer ${userInfo.token}`,
19 | },
20 | };
21 |
22 | const { data } = await axios.post(
23 | `${import.meta.env.VITE_SERVER_URL}/orders`,
24 | {
25 | orderItems: orderData.orderItems,
26 | deliveryAddress: orderData.deliveryAddress,
27 | salesTax: orderData.salesTax,
28 | deliveryCharges: orderData.deliveryCharges,
29 | totalPrice: orderData.totalPrice,
30 | payment: orderData.payment,
31 | },
32 | config
33 | );
34 |
35 | return data;
36 | } catch (error) {
37 | return rejectWithValue({
38 | status: error.response && error.response.status,
39 | message:
40 | error.response && error.response.data.message
41 | ? error.response.data.message
42 | : error.message,
43 | });
44 | }
45 | }
46 | );
47 |
48 | // Order List By User Id
49 | export const listOrdersByUserId = createAsyncThunk(
50 | 'order/listOrdersByUserId',
51 | async (_, { getState, rejectWithValue }) => {
52 | try {
53 | const {
54 | user: { userInfo },
55 | } = getState();
56 |
57 | const config = {
58 | headers: {
59 | Authorization: `Bearer ${userInfo.token}`,
60 | },
61 | };
62 |
63 | const { data } = await axios.get(
64 | `${import.meta.env.VITE_SERVER_URL}/orders/user`,
65 | config
66 | );
67 |
68 | return data;
69 | } catch (error) {
70 | return rejectWithValue({
71 | status: error.response && error.response.status,
72 | message:
73 | error.response && error.response.data.message
74 | ? error.response.data.message
75 | : error.message,
76 | });
77 | }
78 | }
79 | );
80 |
81 | // Order List (Admin)
82 | export const listOrders = createAsyncThunk(
83 | 'order/listOrders',
84 | async (_, { getState, rejectWithValue }) => {
85 | try {
86 | const {
87 | admin: { adminUserInfo },
88 | } = getState();
89 |
90 | const config = {
91 | headers: {
92 | Authorization: `Bearer ${adminUserInfo.token}`,
93 | },
94 | };
95 |
96 | const { data } = await axios.get(
97 | `${import.meta.env.VITE_SERVER_URL}/orders`,
98 | config
99 | );
100 |
101 | return data;
102 | } catch (error) {
103 | return rejectWithValue({
104 | status: error.response && error.response.status,
105 | message:
106 | error.response && error.response.data.message
107 | ? error.response.data.message
108 | : error.message,
109 | });
110 | }
111 | }
112 | );
113 |
114 | // Order Details By Id (Admin)
115 | export const getOrderById = createAsyncThunk(
116 | 'order/getOrderById',
117 | async (id, { getState, rejectWithValue }) => {
118 | try {
119 | const {
120 | admin: { adminUserInfo },
121 | } = getState();
122 |
123 | const config = {
124 | headers: {
125 | Authorization: `Bearer ${adminUserInfo.token}`,
126 | },
127 | };
128 |
129 | const { data } = await axios.get(
130 | `${import.meta.env.VITE_SERVER_URL}/orders/${id}`,
131 | config
132 | );
133 |
134 | return data;
135 | } catch (error) {
136 | return rejectWithValue({
137 | status: error.response && error.response.status,
138 | message:
139 | error.response && error.response.data.message
140 | ? error.response.data.message
141 | : error.message,
142 | });
143 | }
144 | }
145 | );
146 |
147 | // Order Update by Id (Admin)
148 | export const updateOrderById = createAsyncThunk(
149 | 'order/updateOrderById',
150 | async ({ id, status }, { getState, rejectWithValue }) => {
151 | try {
152 | const {
153 | admin: { adminUserInfo },
154 | } = getState();
155 |
156 | const config = {
157 | headers: {
158 | Authorization: `Bearer ${adminUserInfo.token}`,
159 | },
160 | };
161 |
162 | const { data } = await axios.put(
163 | `${import.meta.env.VITE_SERVER_URL}/orders/${id}`,
164 | { status },
165 | config
166 | );
167 |
168 | return data;
169 | } catch (error) {
170 | return rejectWithValue({
171 | status: error.response && error.response.status,
172 | message:
173 | error.response && error.response.data.message
174 | ? error.response.data.message
175 | : error.message,
176 | });
177 | }
178 | }
179 | );
180 |
181 | // Order Delete by Id (Admin)
182 | export const deleteOrderById = createAsyncThunk(
183 | 'order/deleteOrderById',
184 | async (id, { getState, rejectWithValue }) => {
185 | try {
186 | const {
187 | admin: { adminUserInfo },
188 | } = getState();
189 |
190 | const config = {
191 | headers: {
192 | Authorization: `Bearer ${adminUserInfo.token}`,
193 | },
194 | };
195 |
196 | const { data } = await axios.delete(
197 | `${import.meta.env.VITE_SERVER_URL}/orders/${id}`,
198 | config
199 | );
200 |
201 | return data;
202 | } catch (error) {
203 | return rejectWithValue({
204 | status: error.response && error.response.status,
205 | message:
206 | error.response && error.response.data.message
207 | ? error.response.data.message
208 | : error.message,
209 | });
210 | }
211 | }
212 | );
213 |
--------------------------------------------------------------------------------
/client/src/components/ui/Profile/EditProfileForm.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | import Button from '../Button';
6 | import Loader from '../Loader';
7 | import Message from '../Message';
8 |
9 | import {
10 | getUserDetails,
11 | updateUserProfile,
12 | } from '../../../redux/asyncThunks/userThunks';
13 |
14 | function EditProfileForm({ setIsEditing }) {
15 | const dispatch = useDispatch();
16 |
17 | const user = useSelector((state) => state.user);
18 | const { loading, userUpdateProfileError, userDetails } = user;
19 |
20 | const initialFormData = {
21 | name: userDetails.name,
22 | email: userDetails.email,
23 | address: userDetails.address,
24 | phoneNumber: userDetails.phoneNumber,
25 | password: '',
26 | confirmPassword: '',
27 | };
28 |
29 | const [formData, setFormData] = useState(initialFormData);
30 |
31 | const handleFieldChange = (e) => {
32 | setFormData({
33 | ...formData,
34 | [e.target.name]: e.target.value,
35 | });
36 | };
37 |
38 | const handleSubmit = (e) => {
39 | e.preventDefault();
40 | dispatch(updateUserProfile(formData)).then(() => {
41 | setIsEditing(false);
42 | dispatch(getUserDetails({}));
43 | });
44 | };
45 |
46 | const handleCancel = () => {
47 | setFormData(initialFormData);
48 | setIsEditing(false);
49 | };
50 |
51 | return (
52 | <>
53 | {loading ? (
54 |
55 | ) : (
56 | <>
57 | {userUpdateProfileError && (
58 | {userUpdateProfileError}
59 | )}
60 |
162 | >
163 | )}
164 | >
165 | );
166 | }
167 |
168 | EditProfileForm.propTypes = {
169 | setIsEditing: PropTypes.func.isRequired,
170 | user: PropTypes.object.isRequired,
171 | };
172 | export default EditProfileForm;
173 |
--------------------------------------------------------------------------------
/client/src/components/ui/CheckoutSteps/ShippingStep.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState } from 'react';
3 | import { useDispatch, useSelector } from 'react-redux';
4 |
5 | // Import Actions
6 | import { saveShippingAddress } from '../../../redux/slices/cartSlice';
7 |
8 | // Import Components
9 | import Button from '../Button';
10 |
11 | function ShippingStep({ setCurrentStep }) {
12 | const dispatch = useDispatch();
13 |
14 | const cart = useSelector((state) => state.cart);
15 | const { shippingAddress, cartItems } = cart;
16 |
17 | const [phoneNumber, setPhoneNumber] = useState(shippingAddress.phoneNumber);
18 | const [address, setAddress] = useState(shippingAddress.address);
19 | const [city, setCity] = useState(shippingAddress.city);
20 | const [postalCode, setPostalCode] = useState(shippingAddress.postalCode);
21 | const [country, setCountry] = useState(shippingAddress.country);
22 |
23 | const submitHandler = (e) => {
24 | e.preventDefault();
25 | dispatch(
26 | saveShippingAddress({ phoneNumber, address, city, postalCode, country })
27 | );
28 | setCurrentStep('Payment');
29 | };
30 |
31 | return (
32 |
161 | );
162 | }
163 |
164 | ShippingStep.propTypes = {
165 | setCurrentStep: PropTypes.func.isRequired,
166 | };
167 |
168 | export default ShippingStep;
169 |
--------------------------------------------------------------------------------