├── .gitignore
├── frontend
├── src
│ ├── components
│ │ ├── WeekMenu
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ └── MenuBar
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ ├── utility
│ │ ├── fade.module.css
│ │ ├── removeLoader.js
│ │ ├── fade.js
│ │ └── routeAnimation.js
│ ├── index.css
│ ├── routes
│ │ ├── Schedule
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ ├── QRCode
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ ├── TotalMeals
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ ├── PurchaseHistory
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ ├── Buy
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ ├── ScanQR
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ │ └── AdminPanel
│ │ │ ├── index.module.css
│ │ │ └── index.js
│ ├── index.js
│ └── App.js
├── build
│ ├── favicon.ico
│ ├── assets
│ │ └── icon.png
│ ├── asset-manifest.json
│ ├── index.html
│ └── static
│ │ └── js
│ │ └── main.d9d36117.js.LICENSE.txt
├── public
│ ├── favicon.ico
│ ├── assets
│ │ └── icon.png
│ └── index.html
├── .gitignore
└── package.json
├── assets
├── payment.jpg
├── play_yt.jpg
├── qr_code.jpg
├── scan_qr.jpg
├── admin_panel.jpg
├── time_menu.jpg
├── total_meals.jpg
├── google_signin.jpg
├── purchase_page.jpg
└── purchase_history.jpg
├── models
├── User.js
├── Menu.js
├── Order.js
├── Time.js
└── Buyer.js
├── package.json
├── routes
├── data.js
├── auth.js
├── admin.js
└── user.js
├── SETUP.md
├── config
└── passport.js
├── index.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | config.env
--------------------------------------------------------------------------------
/frontend/src/components/WeekMenu/index.module.css:
--------------------------------------------------------------------------------
1 | .table {
2 | margin: 0.5rem;
3 | }
--------------------------------------------------------------------------------
/assets/payment.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/payment.jpg
--------------------------------------------------------------------------------
/assets/play_yt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/play_yt.jpg
--------------------------------------------------------------------------------
/assets/qr_code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/qr_code.jpg
--------------------------------------------------------------------------------
/assets/scan_qr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/scan_qr.jpg
--------------------------------------------------------------------------------
/assets/admin_panel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/admin_panel.jpg
--------------------------------------------------------------------------------
/assets/time_menu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/time_menu.jpg
--------------------------------------------------------------------------------
/assets/total_meals.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/total_meals.jpg
--------------------------------------------------------------------------------
/assets/google_signin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/google_signin.jpg
--------------------------------------------------------------------------------
/assets/purchase_page.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/purchase_page.jpg
--------------------------------------------------------------------------------
/assets/purchase_history.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/assets/purchase_history.jpg
--------------------------------------------------------------------------------
/frontend/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/frontend/build/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/build/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/frontend/build/assets/icon.png
--------------------------------------------------------------------------------
/frontend/public/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dashroshan/mess-management-system/HEAD/frontend/public/assets/icon.png
--------------------------------------------------------------------------------
/frontend/src/utility/fade.module.css:
--------------------------------------------------------------------------------
1 | .outerWrapper {
2 | display: grid;
3 | grid-template-columns: 1fr;
4 | -webkit-tap-highlight-color: transparent;
5 | }
6 |
7 | .innerWrapper {
8 | grid-row-start: 1;
9 | grid-column-start: 1;
10 | }
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/frontend/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.0ec4db39.css",
4 | "main.js": "/static/js/main.d9d36117.js",
5 | "index.html": "/index.html",
6 | "main.0ec4db39.css.map": "/static/css/main.0ec4db39.css.map",
7 | "main.d9d36117.js.map": "/static/js/main.d9d36117.js.map"
8 | },
9 | "entrypoints": [
10 | "static/css/main.0ec4db39.css",
11 | "static/js/main.d9d36117.js"
12 | ]
13 | }
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
--------------------------------------------------------------------------------
/frontend/src/routes/Schedule/index.module.css:
--------------------------------------------------------------------------------
1 | .menuBody {
2 | margin: 0 auto;
3 | }
4 |
5 | .menuBody>h1 {
6 | text-align: center;
7 | }
8 |
9 | .menuBody>h1:nth-of-type(1) {
10 | margin-top: 1rem;
11 | margin-bottom: 1.4rem;
12 | }
13 |
14 | .menuBody>h1:nth-of-type(2) {
15 | margin: 1.4rem 0;
16 | }
17 |
18 | .table {
19 | margin: 0.5rem;
20 | }
21 |
22 | @media (min-width:750px) {
23 | .menuBody {
24 | max-width: 80rem;
25 | }
26 | }
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const UserSchema = new mongoose.Schema({
4 | googleId: {
5 | type: String,
6 | required: true,
7 | },
8 | displayName: {
9 | type: String,
10 | required: true,
11 | },
12 | email: {
13 | type: String,
14 | required: true,
15 | }
16 | })
17 |
18 | // To be used by Passport to manage the google signins
19 | module.exports = mongoose.model('User', UserSchema)
20 |
--------------------------------------------------------------------------------
/frontend/src/routes/QRCode/index.module.css:
--------------------------------------------------------------------------------
1 | .qrBody {
2 | height: calc(100vh - 17vh);
3 | display: flex;
4 | flex-direction: column;
5 | gap: 1rem;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .loading {
11 | position: absolute;
12 | display: flex;
13 | background: white;
14 | width: 256px;
15 | height: 256px;
16 | justify-content: center;
17 | align-items: center;
18 | font-size: 7rem;
19 | color: #1f94ff;
20 | transition: .5s;
21 | }
--------------------------------------------------------------------------------
/frontend/src/routes/TotalMeals/index.module.css:
--------------------------------------------------------------------------------
1 | .menuBody {
2 | margin: 0 auto;
3 | margin-top: 1.5rem;
4 | }
5 |
6 | .menuBody>h1 {
7 | text-align: center;
8 | margin-top: 1rem;
9 | margin-bottom: 1.4rem;
10 | }
11 |
12 | .table {
13 | margin: 0.5rem;
14 | }
15 |
16 | .buttons {
17 | display: table;
18 | margin: 0 auto;
19 | }
20 |
21 | .textweek {
22 | display: inline;
23 | }
24 |
25 | @media (min-width:750px) {
26 | .menuBody {
27 | max-width: 80rem;
28 | }
29 | }
--------------------------------------------------------------------------------
/frontend/src/routes/PurchaseHistory/index.module.css:
--------------------------------------------------------------------------------
1 | .menuBody {
2 | margin: 0 auto;
3 | margin-top: 1.5rem;
4 | }
5 |
6 | .menuBody>h1 {
7 | text-align: center;
8 | margin-top: 1rem;
9 | margin-bottom: 1.4rem;
10 | }
11 |
12 | .table {
13 | margin: 0.5rem;
14 | }
15 |
16 | .buttons {
17 | display: table;
18 | margin: 0 auto;
19 | }
20 |
21 | .textweek {
22 | display: inline;
23 | }
24 |
25 | @media (min-width:750px) {
26 | .menuBody {
27 | max-width: 80rem;
28 | }
29 | }
--------------------------------------------------------------------------------
/models/Menu.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const MenuSchema = mongoose.model("menuitem", new mongoose.Schema({
4 | day: String,
5 | breakfast: String,
6 | lunch: String,
7 | dinner: String
8 | }));
9 |
10 | // Get the weekly menu
11 | module.exports.getMenu = async function () {
12 | const menuItems = await MenuSchema.find({})
13 | .select({ _id: 0 });
14 | return menuItems;
15 | }
16 |
17 | // Set the weekly menu
18 | module.exports.setMenus = async function (menus) {
19 | await MenuSchema.deleteMany({});
20 | await MenuSchema.insertMany(menus);
21 | }
--------------------------------------------------------------------------------
/frontend/src/routes/Buy/index.module.css:
--------------------------------------------------------------------------------
1 | .table {
2 | margin: 0.5rem;
3 | }
4 |
5 | .buyBody {
6 | max-width: 80rem;
7 | margin: 0 auto;
8 | text-align: center;
9 | }
10 |
11 | h1:first-of-type {
12 | margin-top: 1.2rem;
13 | }
14 |
15 | h1 {
16 | text-transform: capitalize;
17 | margin-top: 1.7rem;
18 | margin-bottom: 0.8rem;
19 | }
20 |
21 | .buy {
22 | margin-top: -0.4rem;
23 | margin-bottom: 1.5rem;
24 | }
25 |
26 | .bought {
27 | width: 100%;
28 | height: calc(100vh - 17vh);
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | }
--------------------------------------------------------------------------------
/models/Order.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const OrderSchema = mongoose.model("order", new mongoose.Schema({
4 | orderid: String,
5 | selected: Object
6 | }));
7 |
8 | // Save an order in progress throught RazorPay
9 | module.exports.saveOrder = async function (orderid, selected) {
10 | await OrderSchema.create({ orderid: orderid, selected: selected });
11 | }
12 |
13 | // Get a saved order to update the user after successful payment
14 | module.exports.getOrder = async function (orderid) {
15 | const orderObj = await OrderSchema.findOne({ orderid: orderid });
16 | return orderObj;
17 | }
--------------------------------------------------------------------------------
/models/Time.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const TimeSchema = mongoose.model("time", new mongoose.Schema({
4 | meal: String,
5 | time: String,
6 | cost: Number
7 | }));
8 |
9 | // Get the cost and time of breakfast, lunch, dinner
10 | module.exports.getTimes = async function () {
11 | const Times = await TimeSchema.find({})
12 | .select({ _id: 0 });
13 | return Times;
14 | }
15 |
16 | // Set the cost and time of breakfast, lunch, dinner
17 | module.exports.setTimes = async function (times) {
18 | await TimeSchema.deleteMany({});
19 | await TimeSchema.insertMany(times);
20 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mess-portal",
3 | "main": "index.js",
4 | "scripts": {
5 | "postinstall": "cd frontend && npm install --force",
6 | "start": "cd frontend && npm run build && cd .. && node index.js"
7 | },
8 | "license": "ISC",
9 | "dependencies": {
10 | "compression": "^1.7.4",
11 | "connect-mongo": "^3.2.0",
12 | "cors": "^2.8.5",
13 | "dotenv": "^8.2.0",
14 | "express": "^4.17.1",
15 | "express-session": "^1.17.1",
16 | "mongoose": "^5.11.14",
17 | "passport": "^0.4.1",
18 | "passport-google-oauth20": "^2.0.0",
19 | "razorpay": "^2.8.3"
20 | }
21 | }
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import 'antd/dist/antd.min.css';
5 | import App from './App';
6 | import { BrowserRouter } from 'react-router-dom';
7 |
8 | // Global variable. Switched to localhost for testing locally, and to / when
9 | // building and serving from node server
10 |
11 | window.APIROOT = '/';
12 | // window.APIROOT = 'http://localhost:4000/';
13 |
14 | const root = ReactDOM.createRoot(document.getElementById('root'));
15 | root.render(
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/frontend/src/utility/removeLoader.js:
--------------------------------------------------------------------------------
1 | // Removes the loader after the site has been fully loaded
2 | export default function RemoveLoader() {
3 | const onPageLoad = () => {
4 | setTimeout(() => {
5 | document.getElementById("loader_block").style.opacity = 0;
6 | setTimeout(() => {
7 | // Doing this without a timeout wouldn't play the fading opacity transition
8 | document.getElementById("loader_block").style.display = "none";
9 | }, 310);
10 | }, 200);
11 | };
12 |
13 | if (document.readyState === 'complete') onPageLoad();
14 | else {
15 | window.addEventListener('load', onPageLoad, false);
16 | return () => window.removeEventListener('load', onPageLoad);
17 | }
18 | }
--------------------------------------------------------------------------------
/frontend/src/routes/ScanQR/index.module.css:
--------------------------------------------------------------------------------
1 | .qrreader {
2 | display: none;
3 | }
4 |
5 | .card {
6 | margin: 0.5rem;
7 | max-width: 80rem;
8 | width: 100%;
9 | margin-top: 0.2rem;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .card h1 {
16 | text-align: center;
17 | margin-top: 1rem;
18 | }
19 |
20 | .body {
21 | display: flex;
22 | justify-content: center;
23 | }
24 |
25 | .body:nth-last-of-type(n+2) {
26 | margin-bottom: 1rem;
27 | }
28 |
29 | .icon {
30 | font-size: 10rem;
31 | margin: 0 auto;
32 | width: 100%;
33 | margin-bottom: 0.7rem;
34 | }
35 |
36 | .radio {
37 | display: table;
38 | margin: 0 auto;
39 | }
40 |
41 | .buttons {
42 | display: table;
43 | margin: 0 auto;
44 | }
--------------------------------------------------------------------------------
/routes/data.js:
--------------------------------------------------------------------------------
1 | // Import required modules
2 | const express = require("express");
3 | const router = express.Router();
4 |
5 | // Import database models
6 | const Menu = require('../models/Menu');
7 | const Time = require('../models/Time');
8 |
9 | // Get the weekly menu
10 | router.get(
11 | "/menu",
12 | async (req, res) => {
13 | res.send(await Menu.getMenu());
14 | }
15 | );
16 |
17 | // Get the time and cost of breakfast, lunch, dinner
18 | router.get(
19 | "/time",
20 | async (req, res) => {
21 | res.send(await Time.getTimes());
22 | }
23 | );
24 |
25 | // Get the logged in and admin status
26 | router.get(
27 | "/status",
28 | async (req, res) => {
29 | res.send({ loggedIn: req.isAuthenticated(), admin: (req.isAuthenticated() && req.user?.email === process.env.ADMIN) });
30 | }
31 | );
32 |
33 | module.exports = router;
--------------------------------------------------------------------------------
/routes/auth.js:
--------------------------------------------------------------------------------
1 | // Import required modules
2 | const express = require("express");
3 | const passport = require("passport");
4 | const router = express.Router();
5 |
6 | // Signin button link
7 | router.get(
8 | "/signin",
9 | passport.authenticate(
10 | "google",
11 | {
12 | prompt: "select_account",
13 | scope: ["profile", "email"]
14 | }
15 | )
16 | );
17 |
18 | // Signout button link
19 | router.get(
20 | "/signout",
21 | (req, res) => {
22 | req.logout();
23 | res.redirect(process.env.FRONTEND);
24 | }
25 | );
26 |
27 | // For google redirection handling
28 | router.get(
29 | "/google/callback",
30 | passport.authenticate("google", { failureRedirect: process.env.FRONTEND }),
31 | (req, res) => {
32 | res.redirect(process.env.FRONTEND);
33 | }
34 | );
35 |
36 | module.exports = router;
--------------------------------------------------------------------------------
/SETUP.md:
--------------------------------------------------------------------------------
1 | # Setup Guide
2 |
3 | Create a `config.env` file with the below content and place it inside the `config` directory containing `passport.js`
4 |
5 | ```
6 | # Port where the server should run
7 | PORT = 4000
8 |
9 | # For redirection after authentication
10 | FRONTEND = http://localhost:4000
11 |
12 | # MongoDB url
13 | MONGO_URI = mongodb+srv://{user}:{pass}@xxxx.xxxx.mongodb.net/mess
14 |
15 | # From google API console
16 | GOOGLE_CLIENT_ID = xxxx-xxxx.apps.googleusercontent.com
17 | GOOGLE_CLIENT_SECRET = xxxx-xxxx_xxxx_xxxx
18 |
19 | # Call back url set in API console
20 | CALLBACK_URL = /api/auth/google/callback
21 |
22 | # Razorpay Details
23 | PAY_ID = rzp_test_xxxx
24 | PAY_SECRET = xxxx
25 |
26 | # Admin GMail ID
27 | ADMIN = user@gmail.com
28 | ```
29 |
30 | Open the project folder in command prompt and run:
31 |
32 | 1. `npm install` once
33 | 2. `npm run start` to run the portal
34 |
--------------------------------------------------------------------------------
/frontend/src/utility/fade.js:
--------------------------------------------------------------------------------
1 | import { motion, AnimatePresence } from "framer-motion";
2 | import classes from "./fade.module.css";
3 |
4 | // Fades from prop one to prop two, when prop one2Two is true
5 | export default function Fade(props) {
6 | return (
7 |
8 |
9 |
17 | {props.one2Two ?
18 | props.one :
19 | props.two
20 | }
21 |
22 |
23 |
24 | );
25 | }
--------------------------------------------------------------------------------
/frontend/src/components/MenuBar/index.module.css:
--------------------------------------------------------------------------------
1 | .nav {
2 | position: fixed;
3 | min-height: 100vh;
4 | display: block;
5 | background: white;
6 | overflow-x: hidden;
7 | width: 15rem;
8 | padding-top: 0.8rem;
9 | }
10 |
11 | .navBtn {
12 | margin-left: 15rem;
13 | background: black;
14 | color: white;
15 | padding: 1.3rem 1.7rem;
16 | font-size: 1.3rem;
17 | width: fit-content;
18 | cursor: pointer;
19 | }
20 |
21 | .navWrap {
22 | position: fixed;
23 | }
24 |
25 | .logo {
26 | width: 100%;
27 | display: fixed;
28 | background: white;
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | height: 4.63rem;
33 | color: black;
34 | font-size: 1.3rem;
35 | font-weight: bold;
36 | position: fixed;
37 | box-shadow: 0 2px 1rem rgba(0, 0, 0, 0.05);
38 | }
39 |
40 | .gap {
41 | display: block;
42 | margin-top: 5rem;
43 | }
44 |
45 | .menu {
46 | z-index: 999;
47 | }
--------------------------------------------------------------------------------
/frontend/src/utility/routeAnimation.js:
--------------------------------------------------------------------------------
1 | // This allows a smooth fading transition between different pages of the site
2 |
3 | import { motion } from "framer-motion";
4 | import { useLocation, Outlet } from 'react-router-dom';
5 |
6 | const PageLayout = ({ children }) => children;
7 |
8 | const pageVariants = {
9 | initial: {
10 | opacity: 0
11 | },
12 | in: {
13 | opacity: 1
14 | },
15 | out: {
16 | opacity: 0
17 | }
18 | };
19 |
20 | const pageTransition = {
21 | type: "tween",
22 | ease: "linear",
23 | duration: 0.5
24 | };
25 |
26 | export default function AnimationLayout() {
27 | const { pathname } = useLocation();
28 | return (
29 |
30 |
37 |
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/frontend/src/routes/AdminPanel/index.module.css:
--------------------------------------------------------------------------------
1 | .card {
2 | margin: 0.5rem;
3 | max-width: 80rem;
4 | width: 100%;
5 | margin-top: 0.2rem;
6 | }
7 |
8 | .card h1 {
9 | text-align: center;
10 | margin-top: 0.2rem;
11 | margin-bottom: 0.8rem;
12 | }
13 |
14 | .adminBody {
15 | display: flex;
16 | justify-content: center;
17 | }
18 |
19 | .adminBody:nth-last-of-type(n+2) {
20 | margin-bottom: 1rem;
21 | }
22 |
23 | .table {
24 | margin: 0.8rem 0;
25 | }
26 |
27 | .table:first-of-type {
28 | margin-top: 0;
29 | }
30 |
31 | .table:last-of-type {
32 | margin-bottom: 0;
33 | }
34 |
35 | .buttons {
36 | display: table;
37 | margin: 0 auto;
38 | }
39 |
40 | .userList {
41 | height: 9.8rem;
42 | overflow-y: scroll;
43 | }
44 |
45 | .userList::-webkit-scrollbar {
46 | display: none;
47 | }
48 |
49 | .userListIndex {
50 | font-weight: 500;
51 | width: 2rem;
52 | display: inline-block;
53 | text-align: center;
54 | padding-right: 0.9rem;
55 | margin-right: 0.8rem;
56 | border-right: 1px solid #f0f0f0;
57 | }
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mess-management-system",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^4.7.0",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.4.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "antd": "^4.24.1",
11 | "axios": "^1.1.3",
12 | "framer-motion": "^7.6.5",
13 | "qrcode.react": "^3.1.0",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-qr-reader": "^3.0.0-beta-1",
17 | "react-razorpay": "^1.2.0",
18 | "react-responsive": "^9.0.0",
19 | "react-router-dom": "^6.4.3",
20 | "react-scripts": "5.0.1",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/config/passport.js:
--------------------------------------------------------------------------------
1 | const GoogleStrategy = require('passport-google-oauth20').Strategy
2 | const User = require('../models/User')
3 |
4 | module.exports = function (passport) {
5 | passport.use(
6 | new GoogleStrategy(
7 | {
8 | clientID: process.env.GOOGLE_CLIENT_ID,
9 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
10 | callbackURL: process.env.CALLBACK_URL,
11 | },
12 | async (accessToken, refreshToken, profile, done) => {
13 |
14 | //get the user data from google
15 | const newUser = {
16 | googleId: profile.id,
17 | displayName: profile.displayName,
18 | email: profile.emails[0].value
19 | }
20 |
21 | try {
22 | //find the user in our database or create user if not found
23 | let user = await User.findOne({ googleId: profile.id });
24 | if (!user) user = await User.create(newUser);
25 | done(null, user);
26 | } catch (err) {
27 | console.error(err);
28 | }
29 | }
30 | )
31 | )
32 |
33 | // used to serialize the user for the session
34 | passport.serializeUser((user, done) => {
35 | done(null, user.id)
36 | })
37 |
38 | // used to deserialize the user
39 | passport.deserializeUser((id, done) => {
40 | User.findById(id, (err, user) => done(err, user))
41 | })
42 | }
--------------------------------------------------------------------------------
/frontend/src/routes/TotalMeals/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Button, Space, message } from 'antd';
3 | import { useState, useEffect } from 'react';
4 | import axios from 'axios';
5 | import WeekMenu from '../../components/WeekMenu';
6 |
7 | export default function TotalMealsPage() {
8 | const [thisweek, setthisweek] = useState(true);
9 | const [menu, setMenu] = useState([]);
10 | const [loading, setLoading] = useState(true);
11 |
12 | useEffect(() => {
13 | const fetchData = async () => {
14 | setLoading(true);
15 | try {
16 | const response = await axios.post(window.APIROOT + 'api/admin/meals', { week: thisweek ? "this" : "next" });
17 | setMenu(response.data);
18 | console.log(response.data);
19 | setLoading(false);
20 | } catch (error) {
21 | message.error('Failed to fetch menu from server');
22 | }
23 | }
24 | fetchData();
25 | }, [thisweek]);
26 |
27 | return (
28 |
29 |
30 |
31 | setthisweek(true)}>This Week
32 | setthisweek(false)}>Next Week
33 |
34 |
35 |
{thisweek ? "Total Meals This Week" : "Total Meals Next Week"}
36 |
37 |
38 | );
39 | }
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | // Modules
2 | import { useEffect } from 'react';
3 | import { Route, Routes } from 'react-router-dom';
4 | import { Layout } from 'antd';
5 |
6 | // Utilities
7 | import AnimationLayout from './utility/routeAnimation';
8 | import RemoveLoader from './utility/removeLoader';
9 |
10 | // Components
11 | import MenuBar from './components/MenuBar';
12 |
13 | // Route Pages
14 | import QRCodePage from './routes/QRCode';
15 | import SchedulePage from './routes/Schedule';
16 | import PurchaseHistoryPage from './routes/PurchaseHistory';
17 | import BuyPage from './routes/Buy';
18 | import AdminPanel from './routes/AdminPanel';
19 | import TotalMealsPage from './routes/TotalMeals';
20 | import ScanQRPage from './routes/ScanQR';
21 |
22 | export default function App() {
23 | // Removes the loader after the site has been fully loaded
24 | useEffect(RemoveLoader, []);
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | }>
32 | } />
33 | } />
34 | } />
35 | } />
36 | } />
37 | } />
38 | } />
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // Import required modules
2 | const cors = require('cors');
3 | const express = require('express');
4 | const mongoose = require('mongoose');
5 | const compression = require('compression');
6 | const dotenv = require('dotenv');
7 | const passport = require('passport');
8 | const session = require('express-session');
9 | const MongoStore = require('connect-mongo')(session);
10 |
11 | // Initialize express app
12 | var app = express();
13 |
14 | // Load env and set PORT
15 | dotenv.config({ path: './config/config.env' });
16 | const PORT = process.env.PORT;
17 |
18 | // MongoDB connection
19 | mongoose.connect(process.env.MONGO_URI, {
20 | useNewUrlParser: true,
21 | useUnifiedTopology: true
22 | });
23 |
24 | // Passport config
25 | require('./config/passport')(passport);
26 |
27 | // Middleware
28 | app.use(cors({ credentials: true }));
29 | app.use(express.json());
30 | app.use(compression());
31 | app.use(express.urlencoded({ extended: true }));
32 | app.use(
33 | session({
34 | secret: 'Mess Portal',
35 | resave: true,
36 | saveUninitialized: true,
37 | store: new MongoStore({ mongooseConnection: mongoose.connection }),
38 | })
39 | );
40 | app.use(passport.initialize());
41 | app.use(passport.session());
42 |
43 | // Check authentication for admin endpoints
44 | app.use('/api/admin/:all', (req, res, next) => {
45 | if (req.isAuthenticated() && req.user?.email === process.env.ADMIN) next();
46 | else res.sendStatus(401);
47 | });
48 |
49 | // Check authentication for user endpoints
50 | app.use('/api/user/:all', (req, res, next) => {
51 | if (req.isAuthenticated()) next();
52 | else res.sendStatus(401);
53 | })
54 |
55 | // Routes
56 | app.use('/api/auth', require('./routes/auth'));
57 | app.use('/api/data', require('./routes/data'));
58 | app.use('/api/admin', require('./routes/admin'));
59 | app.use('/api/user', require('./routes/user'));
60 |
61 | // Frontend site
62 | app.use(express.static(__dirname + '/frontend/build'));
63 | app.get('*', (req, res) => res.sendFile(__dirname + '/frontend/build/index.html'));
64 |
65 | // Start server
66 | app.listen(PORT, console.log(`Server started on port ${PORT}`));
67 |
--------------------------------------------------------------------------------
/frontend/src/routes/Schedule/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Table, message } from 'antd';
3 | import { useMediaQuery } from 'react-responsive';
4 | import WeekMenu from '../../components/WeekMenu';
5 | import axios from "axios";
6 | import { useState, useEffect } from "react";
7 | import { motion } from "framer-motion";
8 |
9 | const timingCol = [
10 | {
11 | title: 'Meal',
12 | dataIndex: 'meal',
13 | key: 'meal',
14 | render: (text) => ({
15 | props: {
16 | style: { background: "#FAFAFA" },
17 | },
18 | children: {text}
19 | })
20 | },
21 | {
22 | title: 'Time',
23 | dataIndex: 'time',
24 | key: 'time'
25 | }
26 | ];
27 |
28 | export default function SchedulePage() {
29 | const mobile = useMediaQuery({ query: '(max-width: 750px)' });
30 | const [timingRow, setTimingRow] = useState([]);
31 | const [menu, setMenu] = useState([]);
32 |
33 | useEffect(() => {
34 | const fetchData = async () => {
35 | try {
36 | let response = await axios.get(window.APIROOT + 'api/data/time');
37 | setTimingRow(response.data);
38 | } catch (error) {
39 | message.error('Failed to fetch timing from server');
40 | }
41 | }
42 | fetchData();
43 | }, []);
44 |
45 | useEffect(() => {
46 | const fetchData = async () => {
47 | try {
48 | const response = await axios.get(window.APIROOT + 'api/data/menu');
49 | setMenu(response.data);
50 | } catch (error) {
51 | message.error('Failed to fetch menu from server');
52 | }
53 | }
54 | fetchData();
55 | }, []);
56 |
57 | return (
58 |
59 |
TIMING
60 |
61 |
MENU
62 |
63 |
64 |
65 |
66 | );
67 | }
--------------------------------------------------------------------------------
/frontend/src/routes/PurchaseHistory/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Button, Space, message } from 'antd';
3 | import { useMediaQuery } from 'react-responsive';
4 | import WeekMenu from '../../components/WeekMenu';
5 | import axios from "axios";
6 | import { useState, useEffect } from "react";
7 |
8 | export default function PurchaseHistoryPage() {
9 | const mobile = useMediaQuery({ query: '(max-width: 750px)' });
10 | const [thisweek, setthisweek] = useState(true);
11 | const [menu, setMenu] = useState([]);
12 | const [loading, setLoading] = useState(false);
13 |
14 | useEffect(() => {
15 | const fetchData = async () => {
16 | setLoading(true);
17 | try {
18 | const response = await axios.get(window.APIROOT + 'api/data/menu');
19 | const buyer = await axios.get(window.APIROOT + 'api/user/data');
20 | let data = [];
21 | for (let r of response.data) {
22 | data.push({
23 | day: r.day,
24 | breakfast: { text: r.breakfast, selected: buyer.data[thisweek ? "this" : "next"][r.day].breakfast },
25 | lunch: { text: r.lunch, selected: buyer.data[thisweek ? "this" : "next"][r.day].lunch },
26 | dinner: { text: r.dinner, selected: buyer.data[thisweek ? "this" : "next"][r.day].dinner },
27 | })
28 | }
29 | setMenu(data);
30 | setLoading(false);
31 | } catch (error) {
32 | message.error('Failed to fetch purchase history from server');
33 | }
34 | }
35 | fetchData();
36 | }, [thisweek]);
37 |
38 | return (
39 |
40 |
41 |
42 | setthisweek(true)}>This Week
43 | setthisweek(false)}>Next Week
44 |
45 |
46 |
{thisweek ? "Your Coupons This Week" : "Your Coupons Next Week"}
47 |
48 |
49 | );
50 | }
--------------------------------------------------------------------------------
/frontend/src/routes/QRCode/index.js:
--------------------------------------------------------------------------------
1 | import { QRCodeSVG } from 'qrcode.react';
2 | import classes from './index.module.css';
3 | import { ReloadOutlined, QuestionOutlined, LoadingOutlined } from '@ant-design/icons';
4 | import { Button, notification, Space, message } from 'antd';
5 | import axios from "axios";
6 | import { useState, useEffect } from "react";
7 |
8 | export default function QRCodePage() {
9 | const [loading, setLoading] = useState(false);
10 | const [rawCode, setRawCode] = useState(null);
11 |
12 | useEffect(() => {
13 | const fetchData = async () => {
14 | setLoading(true);
15 | try {
16 | const response = await axios.get(window.APIROOT + 'api/user/data');
17 | const code = response.data.secret + response.data.email;
18 | setRawCode(code);
19 | setLoading(false);
20 | } catch (error) {
21 | message.error('Failed to fetch QR code');
22 | }
23 | }
24 | fetchData();
25 | }, []);
26 |
27 | return (
28 |
29 |
33 |
34 | }
35 | onClick={async () => {
36 | setLoading(true);
37 | try {
38 | const response = await axios.get(window.APIROOT + 'api/user/resetSecret');
39 | const code = response.data.secret + response.data.email;
40 | setRawCode(code);
41 | setLoading(false);
42 | message.success('New QR code created successfully');
43 | } catch (error) {
44 | message.error('Failed to create new QR code');
45 | }
46 | }}
47 | >Create New
48 | } onClick={() =>
49 | notification.open({ message: Information , description: 'Keep your QR code private. If you think it has been compromised, reissue a new one using this Create New button.', placement: 'top', closeIcon: '[ CLOSE ]', duration: 10 })
50 | } />
51 |
52 |
53 | );
54 | }
--------------------------------------------------------------------------------
/routes/admin.js:
--------------------------------------------------------------------------------
1 | // Import required modules
2 | const express = require("express");
3 | const router = express.Router();
4 |
5 | // Import database models
6 | const Menu = require('../models/Menu');
7 | const Time = require('../models/Time');
8 | const Buyer = require('../models/Buyer');
9 |
10 | // Set the time and cost of breakfast, lunch, dinner
11 | router.post(
12 | "/setTime",
13 | async (req, res) => {
14 | await Time.setTimes(req.body.times);
15 | res.send();
16 | }
17 | );
18 |
19 | // Set the weekly menu
20 | router.post(
21 | "/setMenu",
22 | async (req, res) => {
23 | await Menu.setMenus(req.body.menus);
24 | res.send();
25 | }
26 | );
27 |
28 | // Get the total meals that need to be cooked
29 | router.post(
30 | "/meals",
31 | async (req, res) => {
32 | const buyers = await Buyer.allBuyers();
33 | let data = {
34 | monday: { breakfast: 0, lunch: 0, dinner: 0 },
35 | tuesday: { breakfast: 0, lunch: 0, dinner: 0 },
36 | wednesday: { breakfast: 0, lunch: 0, dinner: 0 },
37 | thursday: { breakfast: 0, lunch: 0, dinner: 0 },
38 | friday: { breakfast: 0, lunch: 0, dinner: 0 },
39 | saturday: { breakfast: 0, lunch: 0, dinner: 0 },
40 | sunday: { breakfast: 0, lunch: 0, dinner: 0 }
41 | }
42 | for (let buyer of buyers) {
43 | let meals = buyer[req.body.week];
44 | for (const [day, val] of Object.entries(meals)) {
45 | data[day]["breakfast"] += val.breakfast;
46 | data[day]["lunch"] += val.lunch;
47 | data[day]["dinner"] += val.dinner;
48 | }
49 | }
50 | const processed = [
51 | { day: "monday", breakfast: data.monday.breakfast, lunch: data.monday.lunch, dinner: data.monday.dinner },
52 | { day: "tuesday", breakfast: data.tuesday.breakfast, lunch: data.tuesday.lunch, dinner: data.tuesday.dinner },
53 | { day: "wednesday", breakfast: data.wednesday.breakfast, lunch: data.wednesday.lunch, dinner: data.wednesday.dinner },
54 | { day: "thursday", breakfast: data.thursday.breakfast, lunch: data.thursday.lunch, dinner: data.thursday.dinner },
55 | { day: "friday", breakfast: data.friday.breakfast, lunch: data.friday.lunch, dinner: data.friday.dinner },
56 | { day: "saturday", breakfast: data.saturday.breakfast, lunch: data.saturday.lunch, dinner: data.saturday.dinner },
57 | { day: "sunday", breakfast: data.sunday.breakfast, lunch: data.sunday.lunch, dinner: data.sunday.dinner }
58 | ]
59 | res.send(processed);
60 | }
61 | );
62 |
63 | module.exports = router;
--------------------------------------------------------------------------------
/routes/user.js:
--------------------------------------------------------------------------------
1 | // Import required modules
2 | const express = require("express");
3 | const router = express.Router();
4 | const Razorpay = require('razorpay');
5 |
6 | // Import database models
7 | const Buyer = require('../models/Buyer');
8 | const Time = require('../models/Time');
9 | const Order = require('../models/Order');
10 |
11 | // Import RazorPay payment validator
12 | var { validatePaymentVerification } = require('razorpay/dist/utils/razorpay-utils');
13 |
14 | // Get the user secret and meals purchased
15 | router.get(
16 | "/data",
17 | async (req, res) => {
18 | res.send(await Buyer.getBuyer(req.user?.email));
19 | }
20 | );
21 |
22 | // Reset the user secret
23 | router.get(
24 | "/resetSecret",
25 | async (req, res) => {
26 | res.send(await Buyer.resetSecret(req.user?.email));
27 | }
28 | );
29 |
30 | // Check if the user's coupon is valid for the current day and meal
31 | router.post(
32 | "/checkCoupon",
33 | async (req, res) => {
34 | res.send(await Buyer.checkCoupon(req.body));
35 | }
36 | );
37 |
38 | // Check if the user has already bought coupons for the next week
39 | router.get(
40 | "/boughtNextWeek",
41 | async (req, res) => {
42 | res.send(await Buyer.boughtNextWeek(req.user?.email));
43 | }
44 | );
45 |
46 | // Check if the payment throught RazorPay is successful
47 | router.post(
48 | "/checkOrder",
49 | async (req, res) => {
50 | const isValid = validatePaymentVerification({ "order_id": req.body.razorpay_order_id, "payment_id": req.body.razorpay_payment_id }, req.body.razorpay_signature, process.env.PAY_SECRET);
51 | if (isValid) {
52 | const orderObj = await Order.getOrder(req.body.razorpay_order_id);
53 | await Buyer.saveOrder(req.user?.email, orderObj.selected);
54 | }
55 | res.send(isValid);
56 | }
57 | );
58 |
59 | // Create a RazorPay order and send the order id to frontend
60 | router.post(
61 | "/createOrder",
62 | async (req, res) => {
63 | let costs = await Time.getTimes();
64 | let data = {};
65 | for (let c of costs) data[c.meal] = c.cost;
66 | let total = 0;
67 | for (const [day, val] of Object.entries(req.body.selected)) {
68 | if (val.breakfast === true) total += data.breakfast;
69 | if (val.lunch === true) total += data.lunch;
70 | if (val.dinner === true) total += data.dinner;
71 | }
72 |
73 | let instance = new Razorpay({ key_id: process.env.PAY_ID, key_secret: process.env.PAY_SECRET });
74 | let resp = await instance.orders.create({
75 | amount: total * 100,
76 | currency: "INR"
77 | });
78 | await Order.saveOrder(resp.id, req.body.selected);
79 | res.send(resp);
80 | }
81 | );
82 |
83 | module.exports = router;
--------------------------------------------------------------------------------
/frontend/src/routes/ScanQR/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { useState, useEffect } from 'react';
3 | import { QrReader } from 'react-qr-reader';
4 | import { Radio, Card, Button, message } from 'antd';
5 | import Fade from '../../utility/fade';
6 | import { CloseSquareOutlined, CheckSquareOutlined, ReloadOutlined, LoadingOutlined } from '@ant-design/icons';
7 | import { motion } from 'framer-motion';
8 | import axios from 'axios';
9 |
10 | export default function ScanQRPage() {
11 | const [data, setData] = useState();
12 | const [type, setType] = useState(null);
13 | const [valid, setValid] = useState(null);
14 |
15 | const checkCoupon = async (postData) => {
16 | try {
17 | const response = await axios.post(window.APIROOT + 'api/user/checkCoupon', postData);
18 | setValid(response.data);
19 | } catch (error) {
20 | message.error("Failed to reach server to verify coupon");
21 | setData(null);
22 | }
23 | }
24 |
25 | useEffect(() => {
26 | if (!data) return;
27 | const secret = data?.substring(0, 4);
28 | const email = data?.substring(4, data.length);
29 | const day = (new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(new Date())).toLowerCase();
30 | checkCoupon({ secret: secret, email: email, day: day, type: type });
31 | }, [data, type])
32 |
33 | return (
34 | <>
35 | {
36 | if (!!result) setData(result?.text)
37 | }} />
38 |
39 |
40 |
41 | setType(e.target.value)}>
42 | Breakfast
43 | Lunch
44 | Dinner
45 |
46 |
47 | {
48 | type ?
49 | Scan QR Code for {type} :
50 | Select the meal
51 | }
52 |
53 | {type ? } two={ } two={ } />} /> : null}
54 |
55 |
56 | } onClick={() => {
57 | setData(null);
58 | setValid(null);
59 | }}>Scan New
60 |
61 |
62 |
63 | >
64 | );
65 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [MESS PORTAL](https://mess.dashroshan.com)
2 |
3 | [**A D3 Fest Hackathon submission**](https://d3fest.tech)
4 |
5 | ### Team members:
6 |
7 | - Roshan Dash (Developer)
8 | - Subhajit Chatterjee (UI/UX, Documentation)
9 |
10 | ### ▶️ [View it on YouTube](https://www.youtube.com/watch?v=QqCdYTh8L7o)
11 |
12 | ## 🧱 PROBLEM STATEMENT
13 |
14 | **D3H05**
15 |
16 | Develop a solution for mess management. The solution should provide facilities to the mess admin and the students. For mess admins, they can manage coupons and other necessary details like menu, and pricing in the mess dashboard. Students can buy coupons from the mess dashboard only, deciding their desired meals for the week. The aim of the project will be to remove the hassle of buying coupons and provide a centralized platform for mess management.
17 |
18 | - **Task 1 -** QR codes for each meal a day in place of paper coupons
19 | - **Task 2 -** Razorpay Integration
20 |
21 | ## ✨ SALIENT FEATURES OF OUR SOLUTION
22 |
23 | - For **MESS OWNERS** :
24 |
25 | Has specialised mess dashboard where the admin can :
26 |
27 | - _Manage the menu_
28 | - _Edit timings_
29 | - _Regulate prices_
30 | - _Know total meals to be cooked_
31 | - _Scan and verify QR codes to provide meal_
32 | - _Razorpay integration to accept online payments_
33 |
34 | - For **STUDENTS** :
35 |
36 | They gain access to :
37 |
38 | - _View the weekly menu, timining, and costs_
39 | - _Decide and purchase their desired meals online_
40 | - _Review the meals purchased (for both present and next week)_
41 | - _Using a single QR code instead of paper coupons_
42 |
43 | ## 🎯 DETAILED DESCRIPTION
44 |
45 | - ### STUDENT SERVICES
46 |
47 | - **Mess time and menu on the home page**
48 |
49 | 
50 |
51 | - **Signing in to the account**
52 |
53 | The students can sign in using their respective Google accounts using the sign in option. Login can be restricted to certain domains like iiit-bh.ac.in
54 |
55 | 
56 |
57 | - **Buying coupons for next week**
58 |
59 | The student can apply for desired meals by selecting among the checkboxes. The final amount is displayed at the bottom for payment.
60 |
61 | 
62 |
63 | Upon clicking "Continue with Payment", the student is directed to the Payment Gateway of Razorpay to complete their purchase.
64 |
65 | 
66 |
67 | - **Purchase history**
68 |
69 | The student can check which meal and day coupons they have bought for the current and upcoming week.
70 |
71 | 
72 |
73 | - **Using QR code**
74 |
75 | The student will be provided a unique (static) QR code which can be used directly using smartphones or can also be printed and used just like an ID.
76 | In the case that the students feel that their QR code has been compromised, they can create a new QR code.
77 |
78 | 
79 |
80 | - ### ADMINISTRATOR SERVICES :
81 |
82 | - **Admin panel**
83 |
84 | This provides interface to edit the cost, time, and items of the weekly menu.
85 |
86 | 
87 |
88 | - **Total meals**
89 |
90 | This page shows the total meals to be cooked for the present week as well as the upcoming week based on the total coupons purchased.
91 |
92 | 
93 |
94 | - **Scan QR code**
95 |
96 | This allows to scan and verify the mess QR codes. After selecting the meal type, upon hovering the camera over a mess QR code it shows a tick mark if the person has purchased coupon for the given meal on the given day. Or a cross mark if the person has not purchased the meal, or have already claimed it.
97 |
98 | 
99 |
100 | The admin can press "Scan New" to check a new QR code.
101 |
102 | ## ❤️ Team Zenith
103 |
--------------------------------------------------------------------------------
/frontend/src/components/MenuBar/index.js:
--------------------------------------------------------------------------------
1 | import classes from "./index.module.css";
2 | import { useState, useEffect } from "react";
3 | import { motion } from "framer-motion";
4 | import { Link } from "react-router-dom";
5 | import Fade from "../../utility/fade";
6 | import axios from "axios";
7 | import {
8 | MenuOutlined,
9 | CloseOutlined,
10 | UserOutlined,
11 | ShoppingCartOutlined,
12 | QrcodeOutlined,
13 | CalendarOutlined,
14 | TableOutlined,
15 | SettingOutlined,
16 | SolutionOutlined,
17 | ScanOutlined
18 | } from '@ant-design/icons';
19 | import { Menu, message } from 'antd';
20 |
21 | // Create and send a menu item with relative link
22 | const getItem = (label, link, icon, key) => {
23 | return (
24 |
25 | {label}
26 |
27 | );
28 | }
29 |
30 | // Create and send a menu item with absolute link needed for signin and signout
31 | const getLoginItem = (label, link, icon, key) => {
32 | return (
33 |
34 | {label}
35 |
36 | );
37 | }
38 |
39 | export default function MenuBar() {
40 | const [open, setopen] = useState(false);
41 | const [status, setStatus] = useState(false);
42 |
43 | // On load, get the loggedin and admin status and render the menu accordingly
44 | useEffect(() => {
45 | const fetchData = async () => {
46 | try {
47 | const response = await axios.get(window.APIROOT + 'api/data/status');
48 | setStatus(response.data);
49 | } catch (error) {
50 | message.error('Failed to fetch data from server');
51 | }
52 | }
53 | fetchData();
54 | }, []);
55 |
56 | return (
57 | <>
58 |
59 |
Mess Portal
60 |
61 | setopen(!open)}>{
62 | <>
63 | {getLoginItem(status.loggedIn ? 'Sign out' : 'Sign in', status.loggedIn ? 'api/auth/signout' : '/api/auth/signin', , '2')}
64 | {status.loggedIn ? <>
65 |
66 | {getItem('Schedule', '/', , '3')}
67 | {getItem('Buy coupons', '/buy-coupons', , '4')}
68 | {getItem('Purchase history', '/purchase-history', , '5')}
69 | {getItem('QR code', '/qr-code', , '6')}
70 | {status.admin ? <>
71 |
72 | {getItem('Admin panel', '/admin', , '7')}
73 | {getItem('Total meals', '/total-meals', , '8')}
74 | {getItem('Scan QR code', '/scan-qr', , '9')}
75 | > : null}
76 | > : null}
77 | >
78 | }
79 | setopen(!open)}>
80 | {
81 | } two={ } />
82 | }
83 |
84 |
85 |
86 |
87 |
88 | >
89 | );
90 | }
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Mess Portal
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
184 |
185 |
186 |
187 | You need to enable JavaScript to run this app.
188 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/frontend/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Mess Portal
8 |
9 |
10 |
11 |
12 |
13 |
14 |
182 |
183 |
184 |
185 |
186 | You need to enable JavaScript to run this app.
187 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/frontend/src/components/WeekMenu/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Table } from 'antd';
3 |
4 | // menu: the menu object
5 | // mobile: display menu as 7 individual tables for better mobile usability when true
6 | // highlight: when true, it looks for cells to be highlighted in the menu object for purchase history page
7 | // loading: plays a loading spinner when true
8 | export default function WeekMenu(props) {
9 | const menu = props.menu;
10 | const mobile = props.mobile;
11 | const highlight = props.highlight;
12 | const loading = props.loading;
13 |
14 | if (mobile) {
15 | return menu.map(e =>
16 | ({
26 | props: {
27 | style: { background: "#FAFAFA" },
28 | },
29 | children: {text} ,
30 | })
31 | },
32 | {
33 | title: '',
34 | dataIndex: 'Meal',
35 | key: 'Meal',
36 | colSpan: 0,
37 | width: 300,
38 | render: (text, record) => ({
39 | props: highlight ? {
40 | style: { background: record.selected ? "#ceface" : "unset", color: record.selected ? "unset" : "lightgrey" },
41 | } : {},
42 | children: {text} ,
43 | })
44 | },
45 | ]
46 | }
47 | dataSource={
48 | [
49 | {
50 | key: '1',
51 | Type: 'Breakfast',
52 | Meal: highlight ? e.breakfast.text : e.breakfast,
53 | selected: e.breakfast.selected
54 | },
55 | {
56 | key: '2',
57 | Type: 'Lunch',
58 | Meal: highlight ? e.lunch.text : e.lunch,
59 | selected: e.lunch.selected
60 | },
61 | {
62 | key: '3',
63 | Type: 'Dinner',
64 | Meal: highlight ? e.dinner.text : e.dinner,
65 | selected: e.dinner.selected
66 | },
67 | ]
68 | }
69 | pagination={false} bordered />
70 | );
71 | }
72 | else {
73 | return ( ({
81 | props: {
82 | style: { background: "#FAFAFA" },
83 | },
84 | children: {text} ,
85 | })
86 | },
87 | {
88 | title: 'Breakfast',
89 | dataIndex: 'breakfast',
90 | key: 'breakfast',
91 | render: (text, record) => ({
92 | props: highlight ? {
93 | style: { background: record.selected.breakfast ? "#ceface" : "unset", color: record.selected.breakfast ? "unset" : "lightgrey" },
94 | } : {},
95 | children: {text} ,
96 | })
97 | },
98 | {
99 | title: 'Lunch',
100 | dataIndex: 'lunch',
101 | key: 'lunch',
102 | render: (text, record) => ({
103 | props: highlight ? {
104 | style: { background: record.selected.lunch ? "#ceface" : "unset", color: record.selected.lunch ? "unset" : "lightgrey" },
105 | } : {},
106 | children: {text} ,
107 | })
108 | },
109 | {
110 | title: 'Dinner',
111 | dataIndex: 'dinner',
112 | key: 'dinner',
113 | render: (text, record) => ({
114 | props: highlight ? {
115 | style: { background: record.selected.dinner ? "#ceface" : "unset", color: record.selected.dinner ? "unset" : "lightgrey" },
116 | } : {},
117 | children: {text} ,
118 | })
119 | },
120 | ]
121 | }
122 | dataSource={
123 | menu.map((e, i) => {
124 | return {
125 | key: i,
126 | day: e.day,
127 | breakfast: highlight ? e.breakfast.text : e.breakfast,
128 | lunch: highlight ? e.lunch.text : e.lunch,
129 | dinner: highlight ? e.dinner.text : e.dinner,
130 | selected: {
131 | breakfast: e.breakfast.selected,
132 | lunch: e.lunch.selected,
133 | dinner: e.dinner.selected
134 | },
135 | };
136 | })
137 | }
138 | pagination={false} bordered />);
139 | }
140 | }
--------------------------------------------------------------------------------
/frontend/build/static/js/main.d9d36117.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /*!
8 | Copyright (c) 2018 Jed Watson.
9 | Licensed under the MIT License (MIT), see
10 | http://jedwatson.github.io/classnames
11 | */
12 |
13 | /*! ./Component */
14 |
15 | /*! ./Context */
16 |
17 | /*! ./checkPropTypes */
18 |
19 | /*! ./cjs/react-is.development.js */
20 |
21 | /*! ./factoryWithTypeCheckers */
22 |
23 | /*! ./lib/ReactPropTypesSecret */
24 |
25 | /*! ./lib/has */
26 |
27 | /*! ./mediaQuery */
28 |
29 | /*! ./toQuery */
30 |
31 | /*! ./useMediaQuery */
32 |
33 | /*! css-mediaquery */
34 |
35 | /*! hyphenate-style-name */
36 |
37 | /*! matchmediaquery */
38 |
39 | /*! object-assign */
40 |
41 | /*! prop-types */
42 |
43 | /*! react */
44 |
45 | /*! react-is */
46 |
47 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
48 |
49 | /*! shallow-equal */
50 |
51 | /*!**********************!*\
52 | !*** ./src/index.ts ***!
53 | \**********************/
54 |
55 | /*!************************!*\
56 | !*** ./src/Context.ts ***!
57 | \************************/
58 |
59 | /*!************************!*\
60 | !*** ./src/toQuery.ts ***!
61 | \************************/
62 |
63 | /*!**************************!*\
64 | !*** ./src/Component.ts ***!
65 | \**************************/
66 |
67 | /*!***************************!*\
68 | !*** ./src/mediaQuery.ts ***!
69 | \***************************/
70 |
71 | /*!******************************!*\
72 | !*** ./src/useMediaQuery.ts ***!
73 | \******************************/
74 |
75 | /*!****************************************!*\
76 | !*** ./node_modules/react-is/index.js ***!
77 | \****************************************/
78 |
79 | /*!******************************************!*\
80 | !*** ./node_modules/prop-types/index.js ***!
81 | \******************************************/
82 |
83 | /*!********************************************!*\
84 | !*** ./node_modules/prop-types/lib/has.js ***!
85 | \********************************************/
86 |
87 | /*!*********************************************!*\
88 | !*** ./node_modules/object-assign/index.js ***!
89 | \*********************************************/
90 |
91 | /*!**********************************************!*\
92 | !*** ./node_modules/css-mediaquery/index.js ***!
93 | \**********************************************/
94 |
95 | /*!***********************************************!*\
96 | !*** ./node_modules/matchmediaquery/index.js ***!
97 | \***********************************************/
98 |
99 | /*!***************************************************!*\
100 | !*** ./node_modules/prop-types/checkPropTypes.js ***!
101 | \***************************************************/
102 |
103 | /*!****************************************************!*\
104 | !*** ./node_modules/hyphenate-style-name/index.js ***!
105 | \****************************************************/
106 |
107 | /*!******************************************************!*\
108 | !*** ./node_modules/shallow-equal/dist/index.esm.js ***!
109 | \******************************************************/
110 |
111 | /*!***********************************************************!*\
112 | !*** ./node_modules/react-is/cjs/react-is.development.js ***!
113 | \***********************************************************/
114 |
115 | /*!************************************************************!*\
116 | !*** ./node_modules/prop-types/factoryWithTypeCheckers.js ***!
117 | \************************************************************/
118 |
119 | /*!*************************************************************!*\
120 | !*** ./node_modules/prop-types/lib/ReactPropTypesSecret.js ***!
121 | \*************************************************************/
122 |
123 | /*!**************************************************************************************!*\
124 | !*** external {"commonjs":"react","commonjs2":"react","amd":"react","root":"React"} ***!
125 | \**************************************************************************************/
126 |
127 | /**
128 | * @license React
129 | * react-dom.production.min.js
130 | *
131 | * Copyright (c) Facebook, Inc. and its affiliates.
132 | *
133 | * This source code is licensed under the MIT license found in the
134 | * LICENSE file in the root directory of this source tree.
135 | */
136 |
137 | /**
138 | * @license React
139 | * react-jsx-runtime.production.min.js
140 | *
141 | * Copyright (c) Facebook, Inc. and its affiliates.
142 | *
143 | * This source code is licensed under the MIT license found in the
144 | * LICENSE file in the root directory of this source tree.
145 | */
146 |
147 | /**
148 | * @license React
149 | * react.production.min.js
150 | *
151 | * Copyright (c) Facebook, Inc. and its affiliates.
152 | *
153 | * This source code is licensed under the MIT license found in the
154 | * LICENSE file in the root directory of this source tree.
155 | */
156 |
157 | /**
158 | * @license React
159 | * scheduler.production.min.js
160 | *
161 | * Copyright (c) Facebook, Inc. and its affiliates.
162 | *
163 | * This source code is licensed under the MIT license found in the
164 | * LICENSE file in the root directory of this source tree.
165 | */
166 |
167 | /**
168 | * @license qrcode.react
169 | * Copyright (c) Paul O'Shannessy
170 | * SPDX-License-Identifier: ISC
171 | */
172 |
173 | /**
174 | * @remix-run/router v1.0.3
175 | *
176 | * Copyright (c) Remix Software Inc.
177 | *
178 | * This source code is licensed under the MIT license found in the
179 | * LICENSE.md file in the root directory of this source tree.
180 | *
181 | * @license MIT
182 | */
183 |
184 | /**
185 | * React Router DOM v6.4.3
186 | *
187 | * Copyright (c) Remix Software Inc.
188 | *
189 | * This source code is licensed under the MIT license found in the
190 | * LICENSE.md file in the root directory of this source tree.
191 | *
192 | * @license MIT
193 | */
194 |
195 | /**
196 | * React Router v6.4.3
197 | *
198 | * Copyright (c) Remix Software Inc.
199 | *
200 | * This source code is licensed under the MIT license found in the
201 | * LICENSE.md file in the root directory of this source tree.
202 | *
203 | * @license MIT
204 | */
205 |
206 | /** @license React v16.13.1
207 | * react-is.development.js
208 | *
209 | * Copyright (c) Facebook, Inc. and its affiliates.
210 | *
211 | * This source code is licensed under the MIT license found in the
212 | * LICENSE file in the root directory of this source tree.
213 | */
214 |
215 | /** @license React v16.13.1
216 | * react-is.production.min.js
217 | *
218 | * Copyright (c) Facebook, Inc. and its affiliates.
219 | *
220 | * This source code is licensed under the MIT license found in the
221 | * LICENSE file in the root directory of this source tree.
222 | */
223 |
--------------------------------------------------------------------------------
/models/Buyer.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const BuyerSchema = mongoose.model("buyer", new mongoose.Schema({
4 | email: String,
5 | secret: String,
6 | bought: Boolean,
7 | this: {
8 | monday: {
9 | breakfast: { type: Boolean, default: false },
10 | lunch: { type: Boolean, default: false },
11 | dinner: { type: Boolean, default: false }
12 | },
13 | tuesday: {
14 | breakfast: { type: Boolean, default: false },
15 | lunch: { type: Boolean, default: false },
16 | dinner: { type: Boolean, default: false }
17 | },
18 | wednesday: {
19 | breakfast: { type: Boolean, default: false },
20 | lunch: { type: Boolean, default: false },
21 | dinner: { type: Boolean, default: false }
22 | },
23 | thursday: {
24 | breakfast: { type: Boolean, default: false },
25 | lunch: { type: Boolean, default: false },
26 | dinner: { type: Boolean, default: false }
27 | },
28 | friday: {
29 | breakfast: { type: Boolean, default: false },
30 | lunch: { type: Boolean, default: false },
31 | dinner: { type: Boolean, default: false }
32 | },
33 | saturday: {
34 | breakfast: { type: Boolean, default: false },
35 | lunch: { type: Boolean, default: false },
36 | dinner: { type: Boolean, default: false }
37 | },
38 | sunday: {
39 | breakfast: { type: Boolean, default: false },
40 | lunch: { type: Boolean, default: false },
41 | dinner: { type: Boolean, default: false }
42 | }
43 | },
44 | next: {
45 | monday: {
46 | breakfast: { type: Boolean, default: false },
47 | lunch: { type: Boolean, default: false },
48 | dinner: { type: Boolean, default: false }
49 | },
50 | tuesday: {
51 | breakfast: { type: Boolean, default: false },
52 | lunch: { type: Boolean, default: false },
53 | dinner: { type: Boolean, default: false }
54 | },
55 | wednesday: {
56 | breakfast: { type: Boolean, default: false },
57 | lunch: { type: Boolean, default: false },
58 | dinner: { type: Boolean, default: false }
59 | },
60 | thursday: {
61 | breakfast: { type: Boolean, default: false },
62 | lunch: { type: Boolean, default: false },
63 | dinner: { type: Boolean, default: false }
64 | },
65 | friday: {
66 | breakfast: { type: Boolean, default: false },
67 | lunch: { type: Boolean, default: false },
68 | dinner: { type: Boolean, default: false }
69 | },
70 | saturday: {
71 | breakfast: { type: Boolean, default: false },
72 | lunch: { type: Boolean, default: false },
73 | dinner: { type: Boolean, default: false }
74 | },
75 | sunday: {
76 | breakfast: { type: Boolean, default: false },
77 | lunch: { type: Boolean, default: false },
78 | dinner: { type: Boolean, default: false }
79 | }
80 | }
81 | }));
82 |
83 | // Get the user details, or if it doesn't exists, create a new user object
84 | module.exports.getBuyer = async function (email) {
85 | let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
86 | let randomStr = "";
87 | for (let i = 0; i < 4; i++)
88 | randomStr += charset[Math.floor(Math.random() * charset.length)];
89 |
90 | const Buyer = await BuyerSchema.findOneAndUpdate(
91 | { email: email },
92 | {
93 | $setOnInsert: {
94 | bought: false,
95 | secret: randomStr,
96 | this: {
97 | monday: {
98 | breakfast: false,
99 | lunch: false,
100 | dinner: false
101 | },
102 | tuesday: {
103 | breakfast: false,
104 | lunch: false,
105 | dinner: false
106 | },
107 | wednesday: {
108 | breakfast: false,
109 | lunch: false,
110 | dinner: false
111 | },
112 | thursday: {
113 | breakfast: false,
114 | lunch: false,
115 | dinner: false
116 | },
117 | friday: {
118 | breakfast: false,
119 | lunch: false,
120 | dinner: false
121 | },
122 | saturday: {
123 | breakfast: false,
124 | lunch: false,
125 | dinner: false
126 | },
127 | sunday: {
128 | breakfast: false,
129 | lunch: false,
130 | dinner: false
131 | }
132 | },
133 | next: {
134 | monday: {
135 | breakfast: false,
136 | lunch: false,
137 | dinner: false
138 | },
139 | tuesday: {
140 | breakfast: false,
141 | lunch: false,
142 | dinner: false
143 | },
144 | wednesday: {
145 | breakfast: false,
146 | lunch: false,
147 | dinner: false
148 | },
149 | thursday: {
150 | breakfast: false,
151 | lunch: false,
152 | dinner: false
153 | },
154 | friday: {
155 | breakfast: false,
156 | lunch: false,
157 | dinner: false
158 | },
159 | saturday: {
160 | breakfast: false,
161 | lunch: false,
162 | dinner: false
163 | },
164 | sunday: {
165 | breakfast: false,
166 | lunch: false,
167 | dinner: false
168 | }
169 | }
170 | }
171 | },
172 | { new: true, upsert: true }
173 | ).select({ _id: 0 });
174 | return Buyer;
175 | }
176 |
177 | // Resets the user secret and returns the updated user object
178 | module.exports.resetSecret = async function (email) {
179 | let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
180 | let randomStr = "";
181 | for (let i = 0; i < 4; i++)
182 | randomStr += charset[Math.floor(Math.random() * charset.length)];
183 |
184 | const Buyer = await BuyerSchema.findOneAndUpdate(
185 | { email: email },
186 | { secret: randomStr }).select({ _id: 0 });
187 | return Buyer;
188 | }
189 |
190 | // Check if the user's coupon is valid for the current day and meal
191 | module.exports.checkCoupon = async function (data) {
192 | const Buyer = await BuyerSchema.findOne({ email: data.email, secret: data.secret });
193 | if (Buyer == null) return false;
194 | if (Buyer.this[data.day][data.type]) {
195 | await BuyerSchema.updateOne({ email: data.email }, { ["this." + data.day + "." + data.type]: false });
196 | return true;
197 | }
198 | return false;
199 | }
200 |
201 | // Save the purchased coupons after a successful payment
202 | module.exports.saveOrder = async function (email, data) {
203 | await BuyerSchema.updateOne({ email: email }, { next: data, bought: true });
204 | }
205 |
206 | // Check if the user has already bought the coupons for the coming week
207 | module.exports.boughtNextWeek = async function (email) {
208 | await module.exports.getBuyer(email);
209 | const Buyer = await BuyerSchema.findOne({ email: email });
210 | return Buyer.bought;
211 | }
212 |
213 | // Returns details of all the users
214 | module.exports.allBuyers = async function () {
215 | const Buyers = await BuyerSchema.find({});
216 | return Buyers;
217 | }
--------------------------------------------------------------------------------
/frontend/src/routes/AdminPanel/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Button, Input, Card, Table, message } from 'antd';
3 | import { SaveOutlined } from '@ant-design/icons'
4 | import axios from "axios";
5 | import { useState, useEffect } from "react";
6 | import { motion } from "framer-motion";
7 |
8 | function WeekMenu(menu, setMenu) {
9 | return menu.map(e =>
10 | ({
20 | props: {
21 | style: { background: "#FAFAFA" },
22 | },
23 | children: {text} ,
24 | })
25 | },
26 | {
27 | title: '',
28 | dataIndex: 'Meal',
29 | key: 'Meal',
30 | colSpan: 0,
31 | width: 300,
32 | render: (text, record) => ({
33 | props: {
34 | style: { padding: 0 },
35 | },
36 | children: {
38 | for (let i = 0; i < menu.length; i++) {
39 | let temp = menu;
40 | if (menu[i].day === e.day) {
41 | temp[i][record.Type.toLowerCase()] = ele.target.value;
42 | setMenu(temp);
43 | console.log(temp);
44 | break;
45 | }
46 | }
47 | }}
48 | />,
49 | })
50 | },
51 | ]
52 | }
53 | dataSource={
54 | [
55 | {
56 | key: '1',
57 | Type: 'Breakfast',
58 | Meal: e.breakfast,
59 | },
60 | {
61 | key: '2',
62 | Type: 'Lunch',
63 | Meal: e.lunch,
64 | },
65 | {
66 | key: '3',
67 | Type: 'Dinner',
68 | Meal: e.dinner,
69 | },
70 | ]
71 | }
72 | pagination={false} bordered />
73 | );
74 | }
75 |
76 | export default function AdminPanel() {
77 | // TIME AND COST START
78 | const [timingRow, setTimingRow] = useState([]);
79 | const [savingTime, setSavingTime] = useState(false);
80 |
81 | const timingCol = [
82 | {
83 | title: 'Meal',
84 | dataIndex: 'meal',
85 | key: 'meal',
86 | render: (text) => ({
87 | props: {
88 | style: { background: "#FAFAFA" },
89 | },
90 | children: {text}
91 | })
92 | },
93 | {
94 | title: 'Rs',
95 | dataIndex: 'cost',
96 | key: 'cost',
97 | render: (text, record) => ({
98 | props: {
99 | style: { padding: 0 },
100 | },
101 | children: {
103 | for (let i = 0; i < timingRow.length; i++) {
104 | let temp = timingRow;
105 | if (timingRow[i].meal === record.meal) {
106 | temp[i].cost = parseInt(e.target.value, 10);
107 | setTimingRow(temp);
108 | break;
109 | }
110 | }
111 | }}
112 | />,
113 | }),
114 | width: 100
115 | },
116 | {
117 | title: 'Time',
118 | dataIndex: 'time',
119 | key: 'time',
120 | render: (text, record) => ({
121 | props: {
122 | style: { padding: 0 },
123 | },
124 | children: {
126 | for (let i = 0; i < timingRow.length; i++) {
127 | let temp = timingRow;
128 | if (timingRow[i].meal === record.meal) {
129 | temp[i].time = e.target.value;
130 | setTimingRow(temp);
131 | break;
132 | }
133 | }
134 | }}
135 | />,
136 | })
137 | }
138 | ];
139 |
140 | const fetchTime = async () => {
141 | try {
142 | let response = await axios.get(window.APIROOT + 'api/data/time');
143 | setTimingRow(response.data);
144 | console.log(response.data);
145 | } catch (error) {
146 | message.error('Failed to fetch timing from server');
147 | }
148 | }
149 |
150 | const setTime = async () => {
151 | setSavingTime(true);
152 | try {
153 | await axios.post(window.APIROOT + 'api/admin/setTime', { times: timingRow });
154 | message.success('Changes saved successfully')
155 | } catch (error) {
156 | message.error('Failed to save changes');
157 | }
158 | setSavingTime(false);
159 | }
160 |
161 | useEffect(() => {
162 | fetchTime();
163 | }, []);
164 | // TIME AND COST END
165 |
166 |
167 | // MENU START
168 | const [menu, setMenu] = useState([]);
169 | const [savingMenu, setSavingMenu] = useState(false);
170 |
171 | const fetchMenu = async () => {
172 | try {
173 | const response = await axios.get(window.APIROOT + 'api/data/menu');
174 | setMenu(response.data);
175 | } catch (error) {
176 | message.error('Failed to fetch menu from server');
177 | }
178 | }
179 |
180 | const saveMenu = async () => {
181 | setSavingMenu(true);
182 | try {
183 | await axios.post(window.APIROOT + 'api/admin/setMenu', { menus: menu });
184 | message.success('Changes saved successfully')
185 | } catch (error) {
186 | message.error('Failed to save changes');
187 | }
188 | setSavingMenu(false);
189 | }
190 |
191 | useEffect(() => {
192 | fetchMenu();
193 | }, []);
194 | // MENU END
195 |
196 | return (
197 | <>
198 |
199 |
200 | TIME & COST
201 |
202 |
203 | } onClick={() => setTime()}>Save Changes
204 |
205 |
206 |
207 |
208 |
209 | {menu.length ?
210 | MENU
211 | {WeekMenu(menu, setMenu)}
212 |
213 | } onClick={() => saveMenu()}>Save Changes
214 |
215 | : null}
216 |
217 | >
218 | );
219 | }
--------------------------------------------------------------------------------
/frontend/src/routes/Buy/index.js:
--------------------------------------------------------------------------------
1 | import classes from './index.module.css';
2 | import { Table, Button, message, Card } from 'antd';
3 | import { ShoppingCartOutlined } from '@ant-design/icons';
4 | import { useState, useEffect, useCallback } from 'react';
5 | import axios from "axios";
6 | import useRazorpay from "react-razorpay";
7 |
8 | const dayToNum = { "monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3, "friday": 4, "saturday": 5, "sunday": 6 };
9 |
10 | const columns = [
11 | {
12 | title: 'Meal',
13 | dataIndex: 'meal',
14 | width: 90,
15 | },
16 | {
17 | title: 'Rs',
18 | dataIndex: 'cost',
19 | width: 50,
20 | },
21 | {
22 | title: 'Menu',
23 | dataIndex: 'menu',
24 | },
25 | ];
26 |
27 | async function createOrder(selected) {
28 | let response = await axios.post(window.APIROOT + 'api/user/createOrder', { selected: selected });
29 | return response.data;
30 | }
31 |
32 | async function CheckOrder(resp, setBought) {
33 | let response = await axios.post(window.APIROOT + 'api/user/checkOrder', resp);
34 | if (response.data) {
35 | message.success("Coupons bought!");
36 | setBought(true);
37 | }
38 | else {
39 | message.error("Failed to buy coupons!");
40 | }
41 | }
42 |
43 | function PayButton(props) {
44 | const Razorpay = useRazorpay();
45 | const handlePayment = useCallback(async () => {
46 | const order = await createOrder(props.selected);
47 | const options = {
48 | key: "rzp_test_LfhDxl3w4SvQr8",
49 | amount: order.amount.toString(),
50 | currency: "INR",
51 | name: "Mess Portal",
52 | order_id: order.id,
53 | handler: async (res) => {
54 | await CheckOrder(res, props.setBought);
55 | }
56 | };
57 |
58 | const rzpay = new Razorpay(options);
59 | rzpay.open();
60 | }, [Razorpay, props]);
61 |
62 | return (
63 | }>Continue with Payment
64 | );
65 | }
66 |
67 | export default function BuyPage() {
68 | const [cost, setCost] = useState(0);
69 | const [loading, setLoading] = useState(true);
70 | const [bought, setBought] = useState(false);
71 | const [menu, setMenu] = useState([
72 | {
73 | day: "monday",
74 | breakfast: { text: "", cost: "" },
75 | lunch: { text: "", cost: "" },
76 | dinner: { text: "", cost: "" },
77 | },
78 | {
79 | day: 'tuesday',
80 | breakfast: { text: "", cost: "" },
81 | lunch: { text: "", cost: "" },
82 | dinner: { text: "", cost: "" },
83 | },
84 | {
85 | day: 'wednesday',
86 | breakfast: { text: "", cost: "" },
87 | lunch: { text: "", cost: "" },
88 | dinner: { text: "", cost: "" },
89 | },
90 | {
91 | day: 'thursday',
92 | breakfast: { text: "", cost: "" },
93 | lunch: { text: "", cost: "" },
94 | dinner: { text: "", cost: "" },
95 | },
96 | {
97 | day: 'friday',
98 | breakfast: { text: "", cost: "" },
99 | lunch: { text: "", cost: "" },
100 | dinner: { text: "", cost: "" },
101 | },
102 | {
103 | day: 'saturday',
104 | breakfast: { text: "", cost: "" },
105 | lunch: { text: "", cost: "" },
106 | dinner: { text: "", cost: "" },
107 | },
108 | {
109 | day: 'sunday',
110 | breakfast: { text: "", cost: "" },
111 | lunch: { text: "", cost: "" },
112 | dinner: { text: "", cost: "" },
113 | }
114 | ]);
115 | const [selected, setSelected] = useState(
116 | {
117 | monday: { breakfast: false, lunch: false, dinner: false },
118 | tuesday: { breakfast: false, lunch: false, dinner: false },
119 | wednesday: { breakfast: false, lunch: false, dinner: false },
120 | thursday: { breakfast: false, lunch: false, dinner: false },
121 | friday: { breakfast: false, lunch: false, dinner: false },
122 | saturday: { breakfast: false, lunch: false, dinner: false },
123 | sunday: { breakfast: false, lunch: false, dinner: false },
124 | }
125 | );
126 |
127 | useEffect(() => {
128 | let cost = 0;
129 | for (const [day, val] of Object.entries(selected)) {
130 | for (const [meal, mealV] of Object.entries(val)) {
131 | if (mealV)
132 | cost += menu[dayToNum[day]][meal].cost;
133 | }
134 | }
135 | setCost(cost);
136 | }, [menu, selected]);
137 |
138 | useEffect(() => {
139 | const fetchData = async () => {
140 | try {
141 | let response = await axios.get(window.APIROOT + 'api/data/menu');
142 | let cost = await axios.get(window.APIROOT + 'api/data/time');
143 | let data = {};
144 | for (let c of cost.data) data[c.meal] = c.cost;
145 | for (let i = 0; i < response.data.length; i++) {
146 | response.data[i].breakfast = { text: response.data[i].breakfast, cost: data.breakfast }
147 | response.data[i].lunch = { text: response.data[i].lunch, cost: data.lunch }
148 | response.data[i].dinner = { text: response.data[i].dinner, cost: data.dinner }
149 | }
150 | setMenu(response.data);
151 | setLoading(false);
152 | } catch (error) {
153 | console.log(error);
154 | message.error('Failed to fetch menu from server');
155 | }
156 | }
157 | fetchData();
158 | }, []);
159 |
160 | useEffect(() => {
161 | const fetchData = async () => {
162 | try {
163 | let response = await axios.get(window.APIROOT + 'api/user/boughtNextWeek');
164 | setBought(response.data);
165 | } catch (error) {
166 | message.error('Failed to fetch data from server');
167 | }
168 | }
169 | fetchData();
170 | }, []);
171 |
172 | return (<>{bought ?
173 |
174 |
181 | You can buy coupons for a week, the week before. You have already bought coupons for the next week.
182 |
183 |
184 | :
185 |
186 | {
187 | menu.map(e =>
188 | <>
189 |
{e.day}
190 |
{
193 | setSelected({
194 | ...selected,
195 | [e.day]: {
196 | breakfast: skeys.includes('breakfast'),
197 | lunch: skeys.includes('lunch'),
198 | dinner: skeys.includes('dinner'),
199 | }
200 | });
201 | }
202 | }} columns={columns}
203 | dataSource={
204 | [
205 | {
206 | key: 'breakfast',
207 | meal: 'Breakfast',
208 | menu: e.breakfast.text,
209 | cost: e.breakfast.cost
210 | },
211 | {
212 | key: 'lunch',
213 | meal: 'Lunch',
214 | menu: e.lunch.text,
215 | cost: e.lunch.cost
216 | },
217 | {
218 | key: 'dinner',
219 | meal: 'Dinner',
220 | menu: e.dinner.text,
221 | cost: e.dinner.cost
222 | }
223 | ]
224 | }
225 | />
226 | >
227 | )
228 | }
229 | Total Cost: ₹{cost}
230 |
231 |
232 | }>);
233 | }
--------------------------------------------------------------------------------