diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8dbe145..0345064 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,33 +1,32 @@ Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change. - Fixes # (issue) - ## Type of change -Please give a X on it which is applicable +Please mark with an X the type that applies: - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) -- [ ] Refactor Code -- [ ] A documentation update -- [ ] Others(mentioned in the issue number) +- [ ] Refactor code +- [ ] Documentation update +- [ ] Other (mentioned in the issue number) # How Has This Been Tested? -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce them. Please also list any relevant details for your test configuration. + +**_Test A: Describe here_** -**_Test A Describe here_** +**_Test B: Describe here (if required)_** -**_Test B Describe here (if Requred)_** +# Screenshots and Videos -# Screenshorts and Vedios: +Please provide screenshots and videos of the changes you made. -give screenshorts and vedio of the changes you made +# Checklist -# Checklist: -give a X on it which is applicable +Please mark with an X the items that apply: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d78ebfe --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Vansh Waldeo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 361f5b6..73d5ed3 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,16 @@ Welcome to Foodies, your go-to college dining companion! Designed for seamless c ``` PORT=3000 DATABASE_URL="http://localhost:21713/foods" + EMAIL="The email from which forgot the password email will be sent" + MAILPASS="password for the email ( app password )" ``` + ### **STEP TO GENERATE APP PASSWORD** + 1) Enable 2-step verification if not + **In https://myaccount.google.com/security, do you see 2-step verification set to ON** + + 2) Generate APP PASSWORD by clicking on below link + **https://myaccount.google.com/apppasswords?rapt=AEjHL4PAhcbtFEpLwfNtVix3bfiGe71GdrW_Naiuvp_NVnMZyTd0UR07M2mVnEyWzkw9kB99YVhhfEVtjxTi3QWSZ39biK-zGwnghm0u778vwmlh6TFbmh4** + 6. **Start the Backend Server (in the terminal within the /server directory):**
In the terminal where you navigated to the /server directory, run the following command to start the backend server: diff --git a/package.json b/package.json index e33361c..33d7efb 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emailjs/browser": "^4.3.3", + "@react-icons/all-files": "^4.1.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -12,7 +14,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", - "react-icons": "^4.11.0", + "react-icons": "^4.12.0", "react-icons-kit": "^2.0.0", "react-modal": "^3.16.1", "react-router-dom": "^6.16.0", diff --git a/public/c4.png b/public/c4.png new file mode 100644 index 0000000..1b66139 Binary files /dev/null and b/public/c4.png differ diff --git a/public/i9.png b/public/i9.png new file mode 100644 index 0000000..597b3c6 Binary files /dev/null and b/public/i9.png differ diff --git a/server/config/cloudinaryConfig.js b/server/config/cloudinaryConfig.js index cf949e4..e8139fb 100644 --- a/server/config/cloudinaryConfig.js +++ b/server/config/cloudinaryConfig.js @@ -1,14 +1,26 @@ -const config = require("cloudinary").config; -const uploader = require("cloudinary").uploader; -const dotenv = require("dotenv"); +const cloudinary = require('cloudinary').v2; +const dotenv = require('dotenv'); dotenv.config(); const cloudinaryConfig = (req, res, next) => { - config({ + cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }); next(); }; -module.exports = { cloudinaryConfig, uploader }; + +const uploadImage = async (filePath) => { + try { + const result = await cloudinary.uploader.upload(filePath, { + folder: 'canteen_images', // Specify the folder where images should be uploaded + }); + return result; + } catch (error) { + throw new Error('Error uploading image to Cloudinary'); + } +}; + +module.exports = { cloudinaryConfig, uploadImage }; + diff --git a/server/config/database.js b/server/config/database.js index 820c338..44cfef0 100644 --- a/server/config/database.js +++ b/server/config/database.js @@ -5,7 +5,6 @@ require("dotenv").config(); const dbConnect = () =>{ mongoose.connect(process.env.DATABASE_URL,{ - useNewUrlParser : true, useUnifiedTopology : true, }) diff --git a/server/controllers/Auth.js b/server/controllers/Auth.js index 085ed28..57982fa 100644 --- a/server/controllers/Auth.js +++ b/server/controllers/Auth.js @@ -3,15 +3,33 @@ const User = require("../models/studentLoginInfo"); const jwt = require("jsonwebtoken"); const Canteen = require("../models/canteenLoginInfo"); const Session = require("../models/session"); - +const Contact = require('../models/Contact'); +const { + forgotPasswordToken, + verifyToken, + findUserByEmail, + findUserById, +} = require("../utils/PasswordTokenAndUser"); +const nodemailer = require("nodemailer"); require("dotenv").config(); exports.studentSignup = async (req, res) => { console.log("This is jwt", process.env.JWT_SECRET); try { console.log(req.body); - const { name, email, collegeName, accountType, password } = await req.body; - const existingUser = await User.findOne({ email }); + const { name, email, collegeName, accountType, password, confirmPassword } = + await req.body; + + if (password !== confirmPassword) { + return res.status(400).json({ + success: false, + message: "Password and Confirm password didn't match, try again", + }); + } + + const existingUser = await User.findOne({ + email, + }); if (existingUser) { return res.status(400).json({ @@ -87,7 +105,10 @@ exports.studentLogin = async (req, res) => { }); // creating a session - const session = new Session({ userId: user._id, token }); + const session = new Session({ + userId: user._id, + token, + }); await session.save(); user = user.toObject(); @@ -107,14 +128,20 @@ exports.studentLogin = async (req, res) => { // message: "User logged in succesfully", // }); - // Setting cookie + // Setting cookie res.cookie("token", token, { httpOnly: true, secure: true, maxAge: 3600000, }); - res.json({ success: true, message: "Logged in successfully", token, user }); + res.json({ + success: true, + message: "Logged in successfully", + token, + user, + }); } else { + return res.status(403).json({ success: false, message: "Pasword Incorrect", @@ -161,7 +188,10 @@ exports.studentLogout = async (req, res) => { await Session.findOneAndDelete({ token }); res.clearCookie("token"); } - res.status(200).json({ success: true, message: "Logged out successfully" }); + res.status(200).json({ + success: true, + message: "Logged out successfully", + }); } catch (error) { console.log(error); return res.status(500).json({ @@ -233,9 +263,13 @@ exports.canteenSignup = async (req, res) => { }); // Create a token - const token = jwt.sign({ id: canteen._id, email: canteen.email }, process.env.JWT_SECRET, { - expiresIn: '1h', // Set token expiration time as needed - }); + const token = jwt.sign( + { id: canteen._id, email: canteen.email }, + process.env.JWT_SECRET, + { + expiresIn: "1h", // Set token expiration time as needed + } + ); console.log("User created successfully with ID:", canteen._id); return res.status(200).json({ @@ -263,7 +297,9 @@ exports.canteenLogin = async (req, res) => { }); } - let canteen = await Canteen.findOne({ email }); + let canteen = await Canteen.findOne({ + email, + }); if (!canteen) { return res.status(401).json({ success: false, @@ -301,7 +337,10 @@ exports.canteenLogin = async (req, res) => { // }); // Create session - const session = new Session({ userId: canteen._id, token }); + const session = new Session({ + userId: canteen._id, + token, + }); await session.save(); // Set cookie @@ -310,8 +349,13 @@ exports.canteenLogin = async (req, res) => { secure: true, maxAge: 3600000, }); - res.json({ success: true, message: "Logged in successfully", token, canteen, cantId: canteen._id }); - + res.json({ + success: true, + message: "Logged in successfully", + token, + canteen, + cantId: canteen._id, + }); } else { return res.status(403).json({ success: false, @@ -359,7 +403,10 @@ exports.canteenLogout = async (req, res) => { await Session.findOneAndDelete({ token }); res.clearCookie("token"); } - res.status(200).json({ success: true, message: "Logged out successfully" }); + res.status(200).json({ + success: true, + message: "Logged out successfully", + }); } catch (error) { console.log(error); return res.status(500).json({ @@ -393,3 +440,158 @@ exports.changeCanteenPassword = async (req, res) => { message: "Password updated successfully.", }); }; + +//contactUs + +exports.saveContactMessage = async (req, res) => { + try { + const { name, email, message } = req.body; + if (!name || !email || !message) { + return res.status(400).send('All fields are required'); + } + const newContact = new Contact({ name, email, message }); + await newContact.save(); + res.status(201).send('Message received'); + } catch (error) { + console.error('Error saving message:', error.message, error); + res.status(500).send('Error saving message'); + } +}; +// verify user for reset password +exports.forgotPassword = async (req, res) => { + try { + const { email } = req.body; + const existingUser = await findUserByEmail(email); + + if (!existingUser) { + return res.status(400).json({ + success: false, + message: "User does not exist", + }); + } else { + const tokenReturn = forgotPasswordToken(existingUser); + // const link = `http://localhost:3000/api/v1/newPassword/${existingUser._id}/${tokenReturn}`; + + const link = `https://foodies-web-app.vercel.app/api/v1/newPassword/${existingUser._id}/${tokenReturn}`; + + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.MAILPASS, + }, + }); + + const mailOptions = { + from: process.env.EMAIL, + to: email, + subject: "Password Reset Link", + html: ` +
+

Password Reset Request

+

Hello,

+

You have requested to reset your password. Please click the button below to reset your password:

+
+ Reset Password +
+

If you did not request this, please ignore this email.

+

Thank you,

+

FoodiesWeb

+
+

© 2024 Your Company Name. All rights reserved.

+
+ `, + }; + + await transporter.sendMail(mailOptions, function (error, info) { + if (error) { + console.log(error); + } + }); + + res.status(201).json({ + msg: "You should receive an email", + }); + } + } catch (error) { + console.error(error); + return res.status(500).json({ + success: false, + message: "User verification failed", + }); + } +}; + +//for verification of link +exports.verifyLink = async (req, res) => { + const { id, token } = req.params; + console.log(req.params); + + const oldUser = await findUserById(id); + if (!oldUser) { + return res.status(404).json({ + success: false, + message: "User not found!", + }); + } + + try { + console.log("Found user: ", oldUser); + const verify = verifyToken(oldUser, token); + console.log("VerifyToken result: ", verify); + + if (verify.id === id) { + res.status(201).json({ + email: verify.email, + status: "Verified", + }); + } else { + res.status(201).json({ + status: "Cannot Verify", + }); + } + } catch (error) { + res.status(201).json({ + status: "Not Verified", + }); + } +}; + +exports.resetPassword = async (req, res) => { + const { id, token } = req.params; + const { password } = req.body; + + console.log(password, " ", id, " ", token); + + try { + const oldUser = await findUserById(id); + + if (!oldUser) { + return res.status(404).json("User not found"); + } + + const verify = verifyToken(oldUser, token); + if (verify.id !== id) { + return res.status(201).json({ change: false }); + } + + const salt = await bcrypt.genSalt(10); + const newPassword = await bcrypt.hash(password, salt); + + if (oldUser instanceof User) { + await User.findByIdAndUpdate(id, { + password: newPassword, + }); + } else if (oldUser instanceof Canteen) { + await Canteen.findByIdAndUpdate(id, { + password: newPassword, + }); + } + + res.status(201).json({ change: true }); + } catch (error) { + console.log("Error while changing password: ", error); + res.status(500).json("Some error occurred!"); + } +}; + diff --git a/server/controllers/canteenController.js b/server/controllers/canteenController.js index 41eba08..e36b07c 100644 --- a/server/controllers/canteenController.js +++ b/server/controllers/canteenController.js @@ -3,6 +3,7 @@ const Breakfast = require('../models/breakfast'); const Lunch = require('../models/lunch'); const Dinner = require('../models/dinner'); const Canteen = require("../models/canteenLoginInfo"); +const { uploader } = require('../config/cloudinaryConfig'); @@ -27,7 +28,7 @@ const getBreakfast = async(req , res , next) =>{ try{ const id = req.params.id; - console.log(id); + const breakfastData = await Breakfast.find({ canteen: id }).select("dish").select("dishId").exec(); @@ -117,9 +118,9 @@ const addBreakfastDish = asyncHandler(async (req, res, next) => { // Controller function to remove a breakfast dish const removeBreakfastDish = asyncHandler(async (req, res, next) => { const canteenId = req.params.id; - const { dish } = req.body; + const dish = req.body._id; - await Breakfast.deleteOne({ canteen: canteenId, dish }).exec(); + await Breakfast.deleteOne({ _id:dish }).exec(); res.json({ message: 'Dish removed successfully' }); }); @@ -146,9 +147,9 @@ const addLunchDish = asyncHandler(async (req, res, next) => { // Controller function to remove a lunch dish const removeLunchDish = asyncHandler(async (req, res, next) => { const canteenId = req.params.id; - const { dish } = req.body; + const dish = req.body._id; - await Lunch.deleteOne({ canteen: canteenId, dish }).exec(); + await Lunch.deleteOne({ _id:dish }).exec(); res.json({ message: 'Dish removed successfully' }); }); @@ -174,12 +175,111 @@ const addDinnerDish = asyncHandler(async (req, res, next) => { // Controller function to remove a dinner dish const removeDinnerDish = asyncHandler(async (req, res, next) => { const canteenId = req.params.id; - const { dish } = req.body; + const dish = req.body._id; - await Dinner.deleteOne({ canteen: canteenId, dish }).exec(); + await Dinner.deleteOne({ _id:dish }).exec(); res.json({ message: 'Dish removed successfully' }); }); +// Controller function to update canteen details + + +const updateCanteen = async (req, res, next) => { + try { + const canteenId = req.params.id; + const { name, email, collegeName, canteenImage } = req.body; + + // Process the uploaded file if exists + if (req.file) { + const filePath = `public/uploads/${req.file.originalname}`; + const uploadedImage = await uploader.upload(filePath); + req.body.canteenImage = uploadedImage.url; // Update the canteenImage with the uploaded file URL + } + + // Find the canteen by ID and update + const canteen = await Canteen.findByIdAndUpdate(canteenId, req.body, { new: true }); + + // If canteen not found, return error + if (!canteen) { + return res.status(404).json({ success: false, message: "Canteen not found" }); + } + + // Return success response + res.status(200).json({ success: true, message: "Canteen updated successfully", data: canteen }); + } catch (error) { + // Handle errors + console.error("Error updating canteen:", error); + res.status(500).json({ success: false, error: "Internal Server Error" }); + } +}; +//controller to update Canteen FoddItem Details + + +// Controller function to update a breakfast dish +const updateBreakfastDish = asyncHandler(async (req, res, next) => { + const canteenId = req.params.id; + const { dishId, dish } = req.body; + + try { + const updatedDish = await Breakfast.findOneAndUpdate( + { _id: dishId, canteen: canteenId }, + { $set: { dish } }, + { new: true } + ).exec(); + + if (!updatedDish) { + return res.status(404).json({ message: 'Dish not found' }); + } + + res.json({ message: 'Dish updated successfully', data: updatedDish }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); +//Controller to update Lunch +const updateLunchDish = asyncHandler(async (req, res, next) => { + const canteenId = req.params.id; + const { dishId, dish } = req.body; + + try { + const updatedDish = await Lunch.findOneAndUpdate( + { _id: dishId, canteen: canteenId }, + { $set: { dish } }, + { new: true } + ).exec(); + + if (!updatedDish) { + return res.status(404).json({ message: 'Dish not found' }); + } + + res.json({ message: 'Dish updated successfully', data: updatedDish }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); +//Controller to update dinner + +const updateDinnerDish = asyncHandler(async (req, res, next) => { + const canteenId = req.params.id; + const { dishId, dish } = req.body; + + try { + const updatedDish = await Dinner.findOneAndUpdate( + { _id: dishId, canteen: canteenId }, + { $set: { dish } }, + { new: true } + ).exec(); + + if (!updatedDish) { + return res.status(404).json({ message: 'Dish not found' }); + } + + res.json({ message: 'Dish updated successfully', data: updatedDish }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + module.exports = { getCanteenDashboard, @@ -193,4 +293,8 @@ module.exports = { getBreakfast, getLunch, getDinner, + updateCanteen, + updateBreakfastDish, + updateLunchDish, + updateDinnerDish, }; diff --git a/server/middleware/multer.middleware.js b/server/middleware/multer.middleware.js index a385092..a294166 100644 --- a/server/middleware/multer.middleware.js +++ b/server/middleware/multer.middleware.js @@ -1,5 +1,8 @@ -const multer = require("multer"); +const multer = require('multer'); const storage = multer.memoryStorage(); -const multerUploads = multer({ storage }).single("image"); +const multerUploads = multer({ + storage, + limits: { fileSize: 50 * 1024 * 1024 } // Set limit to 50MB +}).single("image"); module.exports = multerUploads; diff --git a/server/models/Contact.js b/server/models/Contact.js new file mode 100644 index 0000000..b82aeb7 --- /dev/null +++ b/server/models/Contact.js @@ -0,0 +1,11 @@ +const mongoose = require('mongoose'); + +const contactSchema = new mongoose.Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + message: { type: String, required: true }, +}); + +const Contact = mongoose.model('Contact', contactSchema); + +module.exports = Contact; diff --git a/server/models/canteenLoginInfo.js b/server/models/canteenLoginInfo.js index af22b92..d75ac33 100644 --- a/server/models/canteenLoginInfo.js +++ b/server/models/canteenLoginInfo.js @@ -14,15 +14,18 @@ const canteenSchema = new Schema({ type: String, required: true, }, - accountType : { - type : String, - enum : ["User" , "Canteen"], - required : true, + accountType: { + type: String, + enum: ["User", "Canteen"], + required: true, }, password: { type: String, required: true, }, + canteenImage: { + type: String, // Assuming you're storing the URL or base64 string of the image + }, }); const Canteen = mongoose.model('Canteen', canteenSchema); diff --git a/server/package.json b/server/package.json index 38bb8fe..9875b1c 100644 --- a/server/package.json +++ b/server/package.json @@ -17,13 +17,14 @@ "cors": "^2.8.5", "datauri": "^4.1.0", "dotenv": "^16.4.5", - "express": "^4.18.2", + "express": "^4.19.2", "express-async-handler": "^1.2.0", "faker": "^5.5.3", "jsonwebtoken": "^9.0.2", - "mongoose": "^7.6.2", + "mongoose": "^7.6.13", "multer": "^1.4.5-lts.1", - "nodemon": "^3.0.1", + "nodemailer": "^6.9.13", + "nodemon": "^3.1.3", "path": "^0.12.7" } } diff --git a/server/routes/canteen.js b/server/routes/canteen.js index e4b574e..83e6242 100644 --- a/server/routes/canteen.js +++ b/server/routes/canteen.js @@ -4,6 +4,7 @@ const router = express.Router(); // Import canteen controller functions const canteenController = require('../controllers/canteenController'); const { auth, isCanteen } = require('../middlewares/auth'); +const multerUploads = require('../middleware/multer.middleware'); router.get('/getcanteen' , canteenController.getAllCanteen); @@ -37,4 +38,12 @@ router.post('/:id/dinner/add',auth,isCanteen, canteenController.addDinnerDish); // Route to remove a dinner dish for a specific canteen router.delete('/:id/dinner/remove',auth,isCanteen, canteenController.removeDinnerDish); +//router to update profile +router.put('/:id/update', auth, isCanteen, multerUploads, canteenController.updateCanteen); + +// New update routes +router.put('/:id/breakfast/updateitem',auth,isCanteen, canteenController.updateBreakfastDish); +router.put('/:id/lunch/updateitem',auth,isCanteen, canteenController.updateLunchDish); +router.put('/:id/dinner/updateitem',auth,isCanteen, canteenController.updateDinnerDish); + module.exports = router; diff --git a/server/routes/contactRoutes.js b/server/routes/contactRoutes.js new file mode 100644 index 0000000..c7fcc7e --- /dev/null +++ b/server/routes/contactRoutes.js @@ -0,0 +1,7 @@ +const express = require('express'); +const { saveContactMessage } = require('../controllers/Auth'); +const router = express.Router(); + +router.post('/', saveContactMessage); + +module.exports = router; diff --git a/server/routes/student.js b/server/routes/student.js index 3168ac9..e6f2054 100644 --- a/server/routes/student.js +++ b/server/routes/student.js @@ -7,6 +7,9 @@ router.post("/studentSignup", authController.studentSignup); router.post("/studentLogin", authController.studentLogin); router.post("/canteenSignup", authController.canteenSignup); router.post("/canteenLogin", authController.canteenLogin); +router.post("/VerifyUser",authController.forgotPassword); +router.get("/resetPassword/:id/:token",authController.verifyLink); +router.post("/newPassword/:id/:token",authController.resetPassword); router.get("/studentLogout", studentAuth, authController.studentLogout); router.get("/canteenLogout", auth, authController.canteenLogout); router.post( @@ -20,4 +23,5 @@ router.post( isCanteen, authController.changeCanteenPassword ); + module.exports = router; diff --git a/server/server.js b/server/server.js index 0339668..6836f6c 100644 --- a/server/server.js +++ b/server/server.js @@ -1,11 +1,11 @@ const express = require("express"); const app = express(); -const dotenv = require('dotenv') -dotenv.config({path : ".env"}); const cors = require("cors"); var cookieParser = require("cookie-parser"); const PORT = process.env.PORT || 4000; const cloudinaryConfig = require("./config/cloudinaryConfig"); +const contactRoutes = require('./routes/contactRoutes'); +const bodyParser = require('body-parser'); app.use( @@ -25,6 +25,7 @@ const uploadFileRouter = require("./routes/uploadFile"); app.use("/api/v1", canteenRoutes); app.use("/api/v1", studentRoutes); app.use("/api/v1", uploadFileRouter); +app.use('/api/contact', contactRoutes); app.listen(PORT, () => { console.log(`Server started succesfully at ${PORT}`); diff --git a/server/utils/PasswordTokenAndUser.js b/server/utils/PasswordTokenAndUser.js new file mode 100644 index 0000000..a11723b --- /dev/null +++ b/server/utils/PasswordTokenAndUser.js @@ -0,0 +1,51 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/studentLoginInfo"); +const Canteen = require("../models/canteenLoginInfo"); + +const forgotPasswordToken = (oldUser) => { + const secret = + process.env.JWT_SECRET + oldUser.password; + const token = jwt.sign( + { + email: oldUser.email, + id: oldUser._id, + }, + secret, + { + expiresIn: "2m", + } + ); + return token; +}; + +const verifyToken = (oldUser, token) => { + const secret = + process.env.JWT_SECRET + oldUser.password; + const verify = jwt.verify(token, secret); + + return verify; +}; + +const findUserByEmail = async (email) => { + let user = await User.findOne({ email }); + if (!user) { + user = await Canteen.findOne({ email }); + } + return user; +}; + +const findUserById = async (id) => { + let user = await User.findById(id); + if (!user) { + user = await Canteen.findById(id); + } + return user; +}; + + +module.exports = { + forgotPasswordToken, + verifyToken, + findUserById, + findUserByEmail, +}; diff --git a/src/App.css b/src/App.css index 74b5e05..3464378 100644 --- a/src/App.css +++ b/src/App.css @@ -12,6 +12,9 @@ animation: App-logo-spin infinite 20s linear; } } +li:hover span { + opacity: 1; +} .App-header { background-color: #282c34; @@ -36,3 +39,7 @@ transform: rotate(360deg); } } + +.nav-item{ + font-size: 52px !important; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index b82484b..87fc149 100644 --- a/src/App.js +++ b/src/App.js @@ -11,8 +11,16 @@ import SectionPage from './pages/SectionPage'; import News from './pages/News'; import NotFound from './pages/NotFound'; import Loader from './components/Loader/Loader'; +import ForgotPassword from './pages/ForgotPassword'; +import ResetPassword from './pages/ResetPassword'; import { ThemeProvider } from './themeContext'; +import ContactUs from './pages/ContactUs'; + +import { AuthProvider } from './authContext' +import EditProfile from './pages/EditProfile'; + + const Layout = ({ children }) => { return (
@@ -23,6 +31,7 @@ const Layout = ({ children }) => { function App() { return ( +
@@ -30,16 +39,23 @@ function App() { } /> } /> } /> + } /> + + } /> + } /> + } /> } /> } /> } /> } /> } /> + } /> } />
+
); } diff --git a/src/authContext.js b/src/authContext.js new file mode 100644 index 0000000..4b1fb0b --- /dev/null +++ b/src/authContext.js @@ -0,0 +1,23 @@ +import { createContext, useContext, useState } from "react"; + +const authContext = createContext({ + isAuthenticated: false +}); + +export const useAuth = () => useContext(authContext); + +const AuthProvider = ({ children }) => { + const [isAuthenticated, setAuthenticated] = useState(false); + + const checkAuthentication = (token) => { + setAuthenticated(!!token); + }; + + return ( + + {children} + + ); +}; + +export { AuthProvider }; diff --git a/src/components/CanteenCard.jsx b/src/components/CanteenCard.jsx index 302d38a..b78a9b7 100644 --- a/src/components/CanteenCard.jsx +++ b/src/components/CanteenCard.jsx @@ -1,9 +1,27 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import myHotel from "../assets/myHotel.jpg" +import fetchRandomRestaurantImage from "../utils/unsplash"; +import { ClipLoader } from "react-spinners"; // Import ClipLoader from react-spinners -const CanteenCard = ({ canteen }) => { +const CanteenCard = ({ canteen }) => { const [selectedRecipes, setSelectedRecipes] = useState({}); + const [imageSrc, setImageSrc] = useState(''); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const getImage = async () => { + try { + const imageUrl = await fetchRandomRestaurantImage(); + setImageSrc(imageUrl); + } catch (error) { + console.error('Error fetching image:', error); + setImageSrc('default-image-url'); + } finally { + setLoading(false); + } + }; + getImage(); + }, []); const handleAddToMenu = (recipeId) => { setSelectedRecipes((prevSelectedRecipes) => ({ @@ -14,45 +32,51 @@ const CanteenCard = ({ canteen }) => { return (
-
- - {canteen.name} - -
-
- -
- {canteen.name} -
-
+ {loading ? ( +
+ +
+ ) : ( +
+ + {canteen.name} + +
+ )} +
+ +
+ {canteen.name} +
+
- - View Menu - - + View Menu + + +
-
); }; diff --git a/src/components/FloatBtn/FloatBtn.css b/src/components/FloatBtn/FloatBtn.css new file mode 100644 index 0000000..d3367f8 --- /dev/null +++ b/src/components/FloatBtn/FloatBtn.css @@ -0,0 +1,23 @@ + +.scrollButton { + position: fixed; + bottom: 40px; + right: 20px; + background-color: #66BB6A; + color: white; + border: none; + border-radius: 50%; + width: 45px; + height: 45px; + font-size: 24px; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + transition: background-color 0.3s ease; + } + + .scrollButton:hover { + background-color: #43A047; + } \ No newline at end of file diff --git a/src/components/FloatBtn/FloatBtn.jsx b/src/components/FloatBtn/FloatBtn.jsx new file mode 100644 index 0000000..e5decca --- /dev/null +++ b/src/components/FloatBtn/FloatBtn.jsx @@ -0,0 +1,17 @@ +import "./FloatBtn.css"; + +const FloatBtn = () => { + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: "smooth" + }); + }; + return( + + ); +}; + +export default FloatBtn; \ No newline at end of file diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index 9256716..45d5c69 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -1,45 +1,23 @@ +import { Link } from "react-router-dom" -import React from 'react'; -import { useNavigate } from 'react-router-dom'; - -const Footer = () => { - const navigate = useNavigate(); - - const handleNavigation = (path) => { - navigate(path); - window.scrollTo(0, 0); - }; - return ( -
-
-
- -
-
-
-

Company

-
    -
  • handleNavigation('/about')}>About Us
  • -
  • News
  • -
  • Contact Us
  • -
-
-
-

Legal Pages

- -
-
-
-
- © 2024-2025 Foodies™. All Rights Reserved. -
-
- ) -} - -export default Footer - +export default function Footer(){ + return ( + + ) +} \ No newline at end of file diff --git a/src/components/Modal-update.js b/src/components/Modal-update.js new file mode 100644 index 0000000..37e99df --- /dev/null +++ b/src/components/Modal-update.js @@ -0,0 +1,40 @@ + +import React, { useState } from "react"; + +const Modalupdate = ({ dish, onUpdate, onCancel }) => { + const [updatedDish, setUpdatedDish] = useState(dish); + + const handleUpdate = () => { + onUpdate(updatedDish); + }; + + return ( +
+
+

Edit Dish

+ setUpdatedDish(e.target.value)} + className="border border-gray-300 p-2 w-full mb-4" + /> +
+ + +
+
+
+ ); +}; + +export default Modalupdate; diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 12dd6ef..e6e0351 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -16,19 +16,21 @@ const Navbar = () => { }; return ( - + ); }; @@ -111,11 +117,11 @@ const NavItem = ({ icon, to, children }) => { }; const MobileNavItem = ({ to, children }) => { - let classname = "z-[2] text-gray-300 text-center hover:text-white block px-3 py-2 rounded-md text-xl font-medium "; + const classname = "z-[2] text-gray-300 text-center hover:text-white block px-3 py-2 rounded-md text-xl font-medium "; return ( {children} @@ -129,5 +135,3 @@ const IconNews = () => 📰; const IconRateUs = () => ; export default Navbar; - - diff --git a/src/index.css b/src/index.css index 4ea23b9..587bb9d 100644 --- a/src/index.css +++ b/src/index.css @@ -23,6 +23,11 @@ code { display: none; } +.nav-item{ + font-size: 12px !important; + font-weight: bolder !important; +} + * { scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE, Edge */ diff --git a/src/index.js b/src/index.js index 062e0e2..bda112c 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import './index.css'; import App from './App'; import { BrowserRouter } from 'react-router-dom'; import { Toaster } from 'react-hot-toast'; + const root = ReactDOM.createRoot(document.getElementById('root')); root.render( diff --git a/src/pages/About.jsx b/src/pages/About.jsx index c8833bd..95352f4 100644 --- a/src/pages/About.jsx +++ b/src/pages/About.jsx @@ -4,9 +4,14 @@ import Navbar from "../components/Navbar"; import AOS from "aos"; import Loader from "../components/Loader/Loader"; import "aos/dist/aos.css"; -import Footer from "../components/Footer" +import Footer from "../components/Footer"; +import FloatBtn from "../components/FloatBtn/FloatBtn"; +import { useAuth } from "../authContext"; +import { useNavigate } from "react-router-dom"; const About = () => { + const navigate = useNavigate() + const { isAuthenticated } = useAuth(); const [loading,setLoading] = useState(false); useEffect(() => { @@ -15,6 +20,12 @@ const About = () => { setLoading(false); }, []); + useEffect(() => { + if(!isAuthenticated){ + navigate('/') + } + }, []) + return ( <> { @@ -32,10 +43,10 @@ const About = () => { data-aos="fade-right" className="md:w-1/2 order-2 md:order-1 md:px-20 px-10 text-center md:text-start" > -

+

Welcome to Foodies

-

+

Hey there, lovely Foodies! We're the passionate minds behind the scenes, and we're thrilled to tell you a little bit about who we are and why we created Foodies. @@ -56,7 +67,7 @@ const About = () => {

{ data-aos="fade-left" className="md:w-1/2 order-2 md:order-2 md:px-8 text-center md:text-start px-4" > -

+

Our Journey

@@ -85,16 +96,16 @@ const About = () => {

-
+

Our Mission

{
+
) } diff --git a/src/pages/AddFoodItem.jsx b/src/pages/AddFoodItem.jsx index 476c6ab..7e25280 100644 --- a/src/pages/AddFoodItem.jsx +++ b/src/pages/AddFoodItem.jsx @@ -83,10 +83,10 @@ function AddFoodItem() { }; return ( -
+

Add Food Item

diff --git a/src/pages/ContactUs.jsx b/src/pages/ContactUs.jsx new file mode 100644 index 0000000..ad47bd8 --- /dev/null +++ b/src/pages/ContactUs.jsx @@ -0,0 +1,167 @@ +import React, { useRef, useState } from "react"; +import { motion } from "framer-motion"; +import emailjs from "@emailjs/browser"; +import axios from "axios"; + + +const Contact = () => { + const formRef = useRef(); + const [form, setForm] = useState({ + name: "", + email: "", + message: "", + }); + + const [loading, setLoading] = useState(false); + + const handleChange = (e) => { + const { target } = e; + const { name, value } = target; + + setForm({ + ...form, + [name]: value, + }); + }; + + const handleEmailSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + + try { + await emailjs.send( + 'process.env.SERVICE_ID',//write service id here + 'process.env.TEMPLETS_ID',//write templet id here + { + from_name: form.name, + to_name: "FoodiesWeb", + from_email: form.email, + to_email: "info@foodiweb.com", + message: form.message, + }, + 'process.env.PUBLIC_KEY' //write public_key here + ); + + + + + setLoading(false); + alert("Thank you. I will get back to you as soon as possible."); + + setForm({ + name: "", + email: "", + message: "", + }); + } catch (error) { + setLoading(false); + console.error(error); + + alert("Sorry, something went wrong while sending your message. Please try again later."); + } + }; + const handleSaveToDB = async (e) => { + e.preventDefault(); + setLoading(true); + + try { + // Save to MongoDB + await axios.post('http://localhost:3000/api/contact', form); + + setLoading(false); + alert("Your message has been saved to the database."); + + setForm({ + name: "", + email: "", + message: "", + }); + } catch (error) { + setLoading(false); + console.error(error); + alert("Sorry, something went wrong while saving your message to the database. Please try again later."); + } + }; + + return ( +
+

Get in touch

+

Contact Us.

+
+ +
+
+
+
+ + +
+ + + + +