diff --git a/README.md b/README.md index b65cc4a..28a1537 100644 --- a/README.md +++ b/README.md @@ -259,20 +259,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Arindam - - - haseebzaki-07 -
- Haseeb Zaki -
- - - - jainaryan04 -
- Aryan Ramesh Jain -
- alo7lika @@ -280,8 +266,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made alolika bhowmik - - Ashwinib26 @@ -289,13 +273,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Ashwini_ab - - - tejasbenibagde -
- Tejas Benibagde -
- itznayan @@ -303,29 +280,22 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Mahera Nayan + + - - Shirisha-16 -
- Tyarla Shirisha -
- - - - meghanakn22 + + tejasbenibagde
- meghanakn22 + Tejas Benibagde
- - VinayLodhi1712 + + Shirisha-16
- Vinay Anand Lodhi + Tyarla Shirisha
- - Amnyadav @@ -341,17 +311,10 @@ We extend our heartfelt gratitude to all the amazing contributors who have made - - Suhas-Koheda -
- Suhas Koheda -
- - - - Sumanbhadra + + haseebzaki-07
- Suman Bhadra + Haseeb Zaki
@@ -361,15 +324,15 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Sawan kushwah + + - - CoderFleet + + Suhas-Koheda
- Rudransh Pratap Singh + Suhas Koheda
- - Jay-1409 @@ -384,13 +347,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Vishnu Prasad Korada - - - Sourabh782 -
- Sourabh Singh Rawat -
- sajalbatra @@ -422,10 +378,10 @@ We extend our heartfelt gratitude to all the amazing contributors who have made - - AE-Hertz + + VinayLodhi1712
- Abhinandan + Vinay Anand Lodhi
@@ -435,13 +391,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Vishal Lade - - - AnushkaChouhan25 -
- Anushka Chouhan -
- REHAN-18 @@ -456,8 +405,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made t rahul prabhu - - Aditya90456 @@ -465,13 +412,8 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Aditya Bakshi - - - vaishnavipal1869 -
- vaishnavipal1869 -
- + + tanishirai @@ -487,26 +429,17 @@ We extend our heartfelt gratitude to all the amazing contributors who have made - - Shiva-Bajpai -
- Shiva Bajpai -
- - - - Pushpa472 + + Sourabh782
- Pushpa Vishwakarma + Sourabh Singh Rawat
- - - - Mansi07sharma + + Shiva-Bajpai
- Mansi Sharma + Shiva Bajpai
@@ -516,13 +449,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made MANI - - - meghanakn473 -
- K N Meghana -
- Ayush215mb @@ -530,6 +456,8 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Ayush Yadav + + smog-root @@ -544,8 +472,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Vaibhav._Y - - Vaibhav-Kumar-K-R @@ -574,13 +500,8 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Sapna Kul - - - Nikhil0-3 -
- Nikhil More -
- + + MutiatBash @@ -588,8 +509,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Bashua Mutiat - - Mohitranag18 @@ -604,20 +523,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Jai Dhingra - - - IkkiOcean -
- Vivek Prakash -
- - - - harjasae2001 -
- Harjas Singh -
- mishradev1 @@ -632,8 +537,6 @@ We extend our heartfelt gratitude to all the amazing contributors who have made CHIKATLA RAKESH - - AliGates915 @@ -697,7 +600,4 @@ Stay updated and engage with our community on social media: - [LinkedIn](https://www.linkedin.com/in/ramakrushna-biswal/) - [Email](mailto:ramakrushnabunty@gmail.com) -We are always here to help you! Don’t hesitate to connect with us and be part of the PlayCafe journey. - - - +We are always here to help you! Don’t hesitate to connect with us and be part of the PlayCafe journey. \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 8c71fad..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,11 +0,0 @@ -MONGO_URI= -EMAIL_USER=your_gmail -PORT=3000 -EMAIL_PASS=your_16_digit_pass -JWT_SECRET=secret -GOOGLE_CLIENT_ID=your_google_client_id -GOOGLE_CLIENT_SECRET=your_google_client_secret -FRONTEND_URL=your_frontend_url -CALLBACK_URL=http://localhost:3000/auth/google/callback -PROD_CALLBACK_URL=https://play-cafe.vercel.app/auth/google/callback -NODE_ENV=development \ No newline at end of file diff --git a/backend/controller/customer.controller.js b/backend/controller/customer.controller.js index b6195ec..3f494f2 100644 --- a/backend/controller/customer.controller.js +++ b/backend/controller/customer.controller.js @@ -87,17 +87,18 @@ async function loginCustomer(req, res) { const customerLoginSchema = z.object({ email: z.string().email("Invalid email address"), password: z.string().min(6, "Password must be at least 6 characters long"), + rememberMe: z.boolean().optional(), }); - const validation = customerLoginSchema.safeParse(req.body); if (!validation.success) { return res.status(400).json({ error: validation.error.errors }); } try { - const customer = await Customer.findOne({ email: req.body.email }); - + const { email, password, rememberMe } = req.body; + const customer = await Customer.findOne({ email }); + if (!customer) { return res.status(401).json({ error: "Invalid email or password" }); } @@ -105,36 +106,29 @@ async function loginCustomer(req, res) { return res.status(403).json({ error: "Account not verified. Please verify your email." }); } - const validPassword = await bcrypt.compare(req.body.password, customer.password); - + const validPassword = await bcrypt.compare(password, customer.password); if (!validPassword) { return res.status(401).json({ error: "Invalid email or password" }); } const payload = { - sub: customer._id, - name: customer.name, // Optional - role: "customer", // Optional - email: customer.email, // Optional - }; - - const token = jwt.sign( - payload, - process.env.JWT_SECRET, - { expiresIn: "1h" } // Expires in 1 hour - ); - - req.session.user = { - id: customer._id, + sub: customer._id, name: customer.name, + role: "customer", + email: customer.email, }; + const token = jwt.sign(payload, process.env.JWT_SECRET, { + expiresIn: rememberMe ? "7d" : "1h", // Set token expiry based on rememberMe option + }); + res.cookie("authToken", token, { - maxAge: 1000 * 60 * 60, - httpOnly: true, - secure: true, + maxAge: rememberMe ? 7 * 24 * 60 * 60 * 1000 : 60 * 60 * 1000, // 7 days or 1 hour + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", }); - + return res.json({ message: "Login successful", token, @@ -147,12 +141,12 @@ async function loginCustomer(req, res) { }); } catch (error) { console.error("Error during login:", error); - res.status(500).json({ error: "Internal server error" }); } } + async function resetPassword(req, res) { const customerResetPasswordSchema = z.object({ email: z.string().email("Invalid email address"), diff --git a/backend/controller/reservation.controller.js b/backend/controller/reservation.controller.js index 43019db..74643d9 100644 --- a/backend/controller/reservation.controller.js +++ b/backend/controller/reservation.controller.js @@ -1,7 +1,8 @@ const { z } = require("zod"); const Reservation = require("../models/reservation.model"); +const Customer = require("../models/customer.model"); // Import Customer model const logger = require("../config/logger"); -const { sendReservationConfirmation } = require("../config/nodemailer"); // Import your email function +const { sendReservationConfirmation } = require("../config/nodemailer"); // Import email function // Define the Zod schema for reservation validation const reservationSchema = z @@ -9,10 +10,12 @@ const reservationSchema = z guests: z.string(), date: z.string(), time: z.string(), - email: z.string().email(), // Include email validation in the schema + email: z.string().email(), // Include email validation + userId: z.string().optional(), // Make userId optional for validation }) .strict(); // Disallow unknown keys +// Controller to create a reservation async function createReservation(req, res) { try { const validationResult = reservationSchema.safeParse(req.body); @@ -28,13 +31,29 @@ async function createReservation(req, res) { errors: validationResult.error.errors, }); } + const userId = req.params.id - // Create the reservation in the database - const reservation = await Reservation.create(validationResult.data); + const { email, date, guests, time } = validationResult.data; + + // Find the customer by userId if provided + const customer = await Customer.findById(userId); + if (!customer) { + return res.status(404).json({ + success: false, + message: "Customer not found.", + }); + } + + // Create the reservation in the database with userId reference + const reservation = await Reservation.create({ + guests, + date, + time, + customer: customer._id, // Associate with customer + }); // Send a confirmation email try { - const { email, date, guests, time } = validationResult.data; await sendReservationConfirmation(email, { reservationDate: date, guests, @@ -45,10 +64,10 @@ async function createReservation(req, res) { logger.error("Error sending reservation confirmation email:", { message: emailError.message, }); - // Email error should not block the main reservation process, so no need to return a failure response + // Email error does not block reservation creation } - // Send the success response + // Respond with success res.status(201).json({ success: true, message: "Reservation created successfully", @@ -68,6 +87,49 @@ async function createReservation(req, res) { } } +// Controller to fetch all reservations for a specific user +async function fetchUserReservations(req, res) { + try { + const userId = req.params.id; // Extract user ID from route parameters + + if (!userId) { + return res.status(400).json({ + success: false, + message: "User ID is required.", + }); + } + + // Find all reservations associated with the customer + const reservations = await Reservation.find({ customer: userId }).populate("customer", "name email"); + + if (reservations.length === 0) { + logger.info(`No reservations found for user ID: ${userId}`); + return res.status(404).json({ + success: false, + message: "No reservations found for this user.", + }); + } + + res.status(200).json({ + success: true, + message: "Reservations retrieved successfully.", + data: reservations, + }); + } catch (error) { + logger.error("Error fetching user reservations:", { + message: error.message, + stack: error.stack, + userId: req.params.id, + }); + + res.status(500).json({ + success: false, + message: "An error occurred while fetching reservations.", + }); + } +} + module.exports = { createReservation, + fetchUserReservations }; diff --git a/backend/index.js b/backend/index.js index 5dfeab5..3c597f1 100644 --- a/backend/index.js +++ b/backend/index.js @@ -11,15 +11,16 @@ const app = express(); const port = process.env.PORT || 3000; const session = require("express-session"); const MongoStore = require("connect-mongo"); - // CORS configuration const corsOptions = { origin: ["http://localhost:5173", "https://play-cafe.vercel.app"], + credentials: true, optionsSuccessStatus: 200, }; app.use(cors(corsOptions)); + app.use(express.json()); app.use('/api', newsletterRoute); diff --git a/backend/middlewares/authCustomer.js b/backend/middlewares/authCustomer.js index eb17509..b65b508 100644 --- a/backend/middlewares/authCustomer.js +++ b/backend/middlewares/authCustomer.js @@ -1,24 +1,38 @@ const jwt = require("jsonwebtoken"); const logger = require("../config/logger"); const config = require("../config/secret"); +const Customer = require("../models/customer.model"); -const authenticateCustomer = (req, res, next) => { - const token = req.header("Authorization")?.split(" ")[1]; // Expecting "Bearer " - - if (token) { - jwt.verify(token, config.JWT_SECRET, (err, user) => { - if (err) { - if (err.name === "TokenExpiredError") { - return res.status(401).json({ message: "Token expired" }); - } - return res.status(403).json({ message: "Invalid token" }); - } - req.user = user; - logger.info(`Customer authenticated: ${JSON.stringify(user.username)}`); - next(); - }); - } else { - res.sendStatus(401); // Unauthorized + +const authenticateCustomer = async (req, res, next) => { + const token = req.header("Authorization")?.split(" ")[1]; + + if (!token) { + return res.status(401).json({ message: "Authorization token is missing" }); + } + + try { + const decoded = jwt.verify(token, config.JWT_SECRET); + + const user = await Customer.findById(decoded.sub); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + if (!user.isVerified) { + return res.status(403).json({ message: "Account not verified" }); + } + + req.user = user; + logger.info(`Customer authenticated: ${user.name}`); + next(); + } catch (err) { + if (err.name === "TokenExpiredError") { + return res.status(401).json({ message: "Token expired" }); + } + logger.error("Token verification failed:", err); + return res.status(403).json({ message: "Invalid token" }); } }; diff --git a/backend/middlewares/sessionMiddleware.js b/backend/middlewares/sessionMiddleware.js index aefe6d8..1d6f028 100644 --- a/backend/middlewares/sessionMiddleware.js +++ b/backend/middlewares/sessionMiddleware.js @@ -1,12 +1,13 @@ -const sessionMiddleware = async (req, res, next)=>{ - console.log(req.session.user); - - - if (req.session.user !== undefined) { - next(); +const sessionMiddleware = (req, res, next) => { + if (req.session.user && req.session.user.id) { + next(); // Continue if session is valid and has user ID } else { - res.status(401).send("Invalid session. Please log in again."); + res.status(401).json({ + success: false, + message: "Invalid session. Please log in again.", + }); } -} - -module.exports = sessionMiddleware; \ No newline at end of file + }; + + module.exports = sessionMiddleware; + \ No newline at end of file diff --git a/backend/models/customer.model.js b/backend/models/customer.model.js index ecc1780..d0bc27b 100644 --- a/backend/models/customer.model.js +++ b/backend/models/customer.model.js @@ -1,4 +1,3 @@ - const mongoose = require("mongoose"); const Schema = mongoose.Schema; @@ -20,18 +19,16 @@ const customerSchema = new Schema( verificationCode: { type: String, default: "", - }, otp: { - type: String, + type: String, }, otpExpiry: { - type: Date, + type: Date, }, isVerified: { - type: Boolean, + type: Boolean, default: false, - }, role: { type: String, @@ -52,7 +49,12 @@ const customerSchema = new Schema( ref: "Order", }, ], - + reservations: [ + { + type: Schema.Types.ObjectId, + ref: "Reservation", // Link to Reservation schema + }, + ], }, { timestamps: true } ); diff --git a/backend/models/reservation.model.js b/backend/models/reservation.model.js index c3eef15..a9f9c71 100644 --- a/backend/models/reservation.model.js +++ b/backend/models/reservation.model.js @@ -14,6 +14,11 @@ const reservationSchema = new Schema({ type: String, required: true, }, + customer: { + type: Schema.Types.ObjectId, + ref: "Customer", // Link back to Customer schema + required: true, + }, }); const Reservation = mongoose.model("Reservation", reservationSchema); diff --git a/backend/routes/eventRouter.js b/backend/routes/eventRouter.js index 7cdcece..a6e7c1d 100644 --- a/backend/routes/eventRouter.js +++ b/backend/routes/eventRouter.js @@ -5,6 +5,7 @@ const { getEvents, deleteEvent, } = require("../controller/event.controller"); +const authenticateCustomer = require("../middlewares/authCustomer"); const router = express.Router(); @@ -24,8 +25,11 @@ router.get("/", async (req, res) => { res.status(500).json({ error: "Internal server error" }); } }); -router.post("/create", createEvent); -router.get("/all", getEvents); -router.get("/delete", deleteEvent); + + +router.post("/create",authenticateCustomer, createEvent); +router.get("/all",authenticateCustomer, getEvents); +router.get("/delete",authenticateCustomer, deleteEvent); + module.exports = router; diff --git a/backend/routes/reservationRouter.js b/backend/routes/reservationRouter.js index 3e6dcfb..dd4eef5 100644 --- a/backend/routes/reservationRouter.js +++ b/backend/routes/reservationRouter.js @@ -1,9 +1,11 @@ const express = require("express"); -const { createReservation } = require("../controller/reservation.controller"); +const { createReservation, fetchUserReservations } = require("../controller/reservation.controller"); const sessionMiddleware = require("../middlewares/sessionMiddleware"); +const authenticateCustomer = require("../middlewares/authCustomer"); const router = express.Router(); -router.post("/create", sessionMiddleware, createReservation); +router.post("/create/:id", authenticateCustomer, createReservation); +router.get("/get/:id", authenticateCustomer, fetchUserReservations); router.get("/", (req, res) => { res.json({ message: "Welcome to the restaurant reservation API!", diff --git a/frontend/package.json b/frontend/package.json index 47cee43..c0c4b9c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "framer-motion": "^11.5.6", "gsap": "^3.12.5", "js-cookie": "^3.0.5", + "jwt-decode": "^4.0.0", "lucide-react": "^0.454.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/components/Pages/Dashboard.tsx b/frontend/src/components/Pages/Dashboard.tsx new file mode 100644 index 0000000..2e81c6b --- /dev/null +++ b/frontend/src/components/Pages/Dashboard.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Cookies from 'js-cookie'; +import { jwtDecode } from 'jwt-decode'; + +function Profile() { + const [reservations, setReservations] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; + + // Fetch reservation data from API + useEffect(() => { + const fetchReservations = async () => { + const authToken = Cookies.get('authToken'); // Retrieve the authToken from cookies + + if (!authToken) { + alert("Please sign in to view your reservations."); + navigate('/login'); + return; + } + + // Decode the token to get the user ID + let userId; + try { + const decodedToken = jwtDecode(authToken); + userId = decodedToken.sub; // Use `sub` based on the backend token payload + console.log("Decoded userId:", userId); // Debugging line + } catch (decodeError) { + console.error("Error decoding token:", decodeError); + alert("Invalid token. Please log in again."); + navigate('/login'); + return; + } + + try { + const response = await fetch( + `${API_URL}/api/reservation/get/${userId}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}`, // Pass the token in headers + }, + credentials: 'include', + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Failed to fetch reservations'); + } + + const data = await response.json(); + setReservations(data.data); + } catch (error) { + setError(error.message); + console.error('Error fetching reservations:', error); + } finally { + setLoading(false); // Set loading to false after the fetch operation + } + }; + + fetchReservations(); + }, [navigate]); + + return ( +
+
+

Your Reservations

+ + {error && ( +

+ {error} +

+ )} + + {loading ? ( +

Loading your reservations...

+ ) : ( +
+ {reservations.length > 0 ? ( + reservations.map((reservation, index) => ( + + )) + ) : ( +

No reservations found.

+ )} +
+ )} +
+
+ ); +} + +// Separate ReservationCard component for better readability +const ReservationCard = ({ reservation }) => ( +
+

Reservation Details

+

Guests: {reservation.guests}

+

Date: {new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }).format(new Date(reservation.date))}

+

Time: {reservation.time}

+

Email: {reservation.email}

+
+); + +export default Profile; diff --git a/frontend/src/components/Pages/Event.jsx b/frontend/src/components/Pages/Event.jsx index da8bfcd..96ad51b 100644 --- a/frontend/src/components/Pages/Event.jsx +++ b/frontend/src/components/Pages/Event.jsx @@ -14,6 +14,8 @@ import game from '../../assets/Boardgames/carrom.gif'; import spin from '../../assets/Boardgames/spin.gif'; import MainHOC from '../MainHOC'; const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +import { useNavigate } from 'react-router-dom'; +import Cookies from 'js-cookie'; const months = [ 'January', 'February', @@ -31,6 +33,16 @@ const months = [ function Event() { const [events, setEvents] = useState([]); const [error, setError] = useState(null); + const navigate = useNavigate(); + + const handleRegisterClick = () => { + const isAuthenticated = Boolean(Cookies.get('authToken')); + + if (!isAuthenticated) { + alert("Please sign in to register for the event."); + navigate('/login'); + } + }; useEffect(() => { const fetchData = async () => { try { @@ -183,7 +195,7 @@ function Event() { ))}
-
diff --git a/frontend/src/components/Pages/Login.jsx b/frontend/src/components/Pages/Login.jsx index e39743d..e3e5c1a 100644 --- a/frontend/src/components/Pages/Login.jsx +++ b/frontend/src/components/Pages/Login.jsx @@ -3,19 +3,15 @@ import photo from '../../assets/login.png'; import { Link, useNavigate } from 'react-router-dom'; import { message } from 'antd'; import Cookies from 'js-cookie'; -import { FaEye } from 'react-icons/fa'; -import { FaEyeSlash } from 'react-icons/fa6'; +import { FaEye, FaEyeSlash } from 'react-icons/fa'; const Login = () => { const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; - const [data, setData] = useState({ - email: '', - password: '', - }); + const [data, setData] = useState({ email: '', password: '' }); const [hidden, setHidden] = useState(true); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - + const [rememberMe, setRememberMe] = useState(false); // New state for Remember Me const navigate = useNavigate(); const handleChange = (e) => { @@ -26,19 +22,25 @@ const Login = () => { e.preventDefault(); setIsLoading(true); setError(null); + try { const response = await fetch(`${API_URL}/api/user/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(data), + body: JSON.stringify({ ...data, rememberMe }), // Include rememberMe in the body }); const result = await response.json(); - if (!response.ok) { - throw new Error(result.message || 'Login failed'); - } - Cookies.set('authToken', result.token, { expire: '1h', secure: true }); + if (!response.ok) throw new Error(result.message || 'Login failed'); + + // Set cookie expiration based on Remember Me option + Cookies.set('authToken', result.token, { + expires: rememberMe ? 7 : 1 / 24, // 7 days if Remember Me is checked, 1 hour otherwise + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + }); + message.success('Login successful'); navigate('/'); } catch (err) { @@ -100,6 +102,15 @@ const Login = () => { + + { { name: 'RESERVATION', path: '/reservation' }, { name: 'BOARDGAMES', path: '/boardgame' }, { name: 'MEMBERSHIP', path: '/membership' }, // Add Membership here + { name: 'PROFILE', path: '/dashboard' }, // Add Membership here ]; useEffect(() => { diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index 83ca6a1..2d8d058 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -22,6 +22,7 @@ import VerifyOtp from '../components/Pages/VerifyOtp'; import EmailVerify from '../components/Pages/EmailVerify'; import Membership from '../components/Membership'; import OtpRegisterVerify from '../components/Pages/verifyRegisterOtp'; +import Profile from '../components/Pages/Dashboard'; const router = createBrowserRouter( createRoutesFromElements( }> @@ -41,6 +42,7 @@ const router = createBrowserRouter( } /> } /> } /> + } /> )