diff --git a/README.md b/README.md index 423b10a0..604a9352 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,13 @@ We extend our heartfelt gratitude to all the amazing contributors who have made Vishnu Prasad Korada + + + Sumanbhadra +
+ Suman Bhadra +
+ sajalbatra diff --git a/backend/controller/event.controller.js b/backend/controller/event.controller.js index 6b29ca6e..36cf46aa 100644 --- a/backend/controller/event.controller.js +++ b/backend/controller/event.controller.js @@ -1,4 +1,5 @@ const logger = require("../config/logger"); +const Customer = require("../models/customer.model"); const Event = require("../models/events.model"); // Create a new event @@ -67,4 +68,56 @@ const getEvents = async (req, res) => { } }; -module.exports = { createEvent, getEvents, deleteEvent }; + +const bookEvent = async (req, res) => { + try { + const { customerId, eventId } = req.body; + + const customer = await Customer.findById(customerId); + const event = await Event.findById(eventId); + + if (!customer || !event) { + return res.status(404).json({ message: "Customer or Event not found" }); + } + + if (customer.bookedEvents.includes(eventId)) { + return res.status(400).json({ message: "Event already booked" }); + } + + customer.bookedEvents.push(eventId); + await customer.save(); + + event.bookedCustomers.push(customerId); + await event.save(); + + res.status(200).json({ message: "Event booked successfully!" }); + } catch (error) { + logger.error("Error booking event:", error); + res.status(500).json({ message: "Server error" }); + } +}; + +// Get all booked events for a customer +const getBookedEvents = async (req, res) => { + try { + const { customerId } = req.params; + + + const customer = await Customer.findById(customerId).populate("bookedEvents"); + + if (!customer) { + return res.status(404).json({ message: "Customer not found" }); + } + + res.status(200).json({ bookedEvents: customer.bookedEvents }); + } catch (error) { + logger.error("Error fetching event:", error); + res.status(500).json({ message: "Server error" }); + } +}; + +module.exports = { + getBookedEvents, +}; + +module.exports = { createEvent, getEvents, deleteEvent, bookEvent, getBookedEvents }; diff --git a/backend/index.js b/backend/index.js index b8d9e76b..1bb24c71 100644 --- a/backend/index.js +++ b/backend/index.js @@ -3,7 +3,7 @@ require("dotenv").config(); const cors = require("cors"); const mongoose = require("mongoose"); const logger = require("./config/logger"); -const errorMiddleware = require("./middlewares/errorMiddleware"); // Corrected typo +const errorMiddleware = require("./middlewares/errrorMiddleware"); // Corrected typo const passport = require("passport"); const { handleGoogleOAuth } = require("./controller/googleOAuth.controller"); const app = express(); diff --git a/backend/models/customer.model.js b/backend/models/customer.model.js index cd446f41..6047d222 100644 --- a/backend/models/customer.model.js +++ b/backend/models/customer.model.js @@ -1,4 +1,3 @@ -/* eslint-disable no-useless-escape */ const mongoose = require("mongoose"); const Schema = mongoose.Schema; @@ -27,6 +26,10 @@ const customerSchema = new Schema( }, bio: String, profilePicture: String, + bookedEvents: [{ + type: Schema.Types.ObjectId, + ref: "Event", // Reference to the Event model + }], }, { timestamps: true }, ); diff --git a/backend/models/events.model.js b/backend/models/events.model.js index 0aebe08a..1c74d6de 100644 --- a/backend/models/events.model.js +++ b/backend/models/events.model.js @@ -1,9 +1,4 @@ -/* eslint-disable no-unused-vars */ -/* eslint-disable no-useless-escape */ -// models/Event.js - const mongoose = require("mongoose"); -const { string } = require("zod"); // Define the Event schema const eventSchema = new mongoose.Schema({ @@ -41,7 +36,12 @@ const eventSchema = new mongoose.Schema({ message: (props) => `${props.value} is not a valid URL!`, }, }, + bookedCustomers: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'Customer' // Reference to the Customer model + }] }); + // Create the Event model const Event = mongoose.model("Event", eventSchema); diff --git a/backend/routes/eventRouter.js b/backend/routes/eventRouter.js index 7cdcece4..c66fc401 100644 --- a/backend/routes/eventRouter.js +++ b/backend/routes/eventRouter.js @@ -1,9 +1,14 @@ +// Add this at the top of eventRouter.js to check if it's being loaded correctly +console.log("eventRouter loaded"); + const express = require("express"); const logger = require("../config/logger"); const { createEvent, getEvents, deleteEvent, + bookEvent, + getBookedEvents, } = require("../controller/event.controller"); const router = express.Router(); @@ -16,6 +21,8 @@ router.get("/", async (req, res) => { endpoints: { CreateEvent: "/event/create", GetEvents: "/event/all", + bookEvents : "/event/book-events", + GetBookedEvents : "/event/get-booked-events/:customerId", }, documentation: "https://api-docs-url.com", }); @@ -26,6 +33,8 @@ router.get("/", async (req, res) => { }); router.post("/create", createEvent); router.get("/all", getEvents); -router.get("/delete", deleteEvent); +router.delete("/delete/:id", deleteEvent); +router.post("/book-events", bookEvent); +router.get("/get-booked-events/:customerId", getBookedEvents); module.exports = router; diff --git a/frontend/package.json b/frontend/package.json index 30175db5..e2a55a73 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@splidejs/react-splide": "^0.7.12", "@splidejs/splide": "^4.1.4", "@splidejs/splide-extension-auto-scroll": "^0.5.3", - "antd": "^5.21.2", + "antd": "^5.21.5", "autoprefixer": "^10.4.19", "axios": "^1.7.7", "clsx": "^2.1.1", diff --git a/frontend/src/components/Pages/HelpAndSupport.jsx b/frontend/src/components/Pages/HelpAndSupport.jsx new file mode 100644 index 00000000..a3bd1988 --- /dev/null +++ b/frontend/src/components/Pages/HelpAndSupport.jsx @@ -0,0 +1,116 @@ +import React, { useState } from 'react'; +import { MdKeyboardArrowDown } from 'react-icons/md'; +import { IoMdCall, IoMdMail } from 'react-icons/io'; + +export default function HelpAndSupport() { + const FAqs = [ + { + question: 'How to create an account?', + answer: + "To create an account, click on the 'Sign Up' button on the top right corner of the page. Fill in your details and click on 'Create Account'.", + }, + { + question: 'How to reset password?', + answer: + "To reset your password, click on 'Forgot Password' on the login page. Enter your email address and follow the instructions sent to your email.", + }, + { + question: 'What is Sip & Play?', + answer: + "Sip & Play is a board game cafe in Park Slope, Brooklyn. It's a place where you can come with friends and family to play board games, enjoy coffee, boba, sandwiches, and snacks.", + }, + { + question: 'How does it work?', + answer: + "You can come in with your friends and family to play any board game from our library of 300+ games. There's no cover charge, just pay for your food and drinks.", + }, + { + question: 'What kind of games do you have?', + answer: + 'We have a wide variety of board games, including classics like Monopoly and Scrabble, as well as newer and more unique games.', + }, + { + question: 'Can I bring my own food and drinks?', + answer: + 'No, we do not allow outside food or drinks. We have a full menu of food and drinks available for purchase.', + }, + { + question: 'Is there parking available?', + answer: + ' There is limited street parking available near the cafe. You may also want to consider using public transportation.', + }, + { + question: 'Is there a cover charge?', + answer: + 'No, there is no cover charge. Just pay for your food and drinks.', + }, + ]; + + const [activeFAQ, setActiveFAQ] = useState(null); + + const handleFAQClick = (index) => { + if (activeFAQ === index) { + setActiveFAQ(null); + } else { + setActiveFAQ(index); + } + }; + + return ( + <> +
+
+
+
+

+ Lost? Let's find your way. +

+

+ Explore our FAQs, tutorials, and user guides to learn how to + make the most of our platform. If you can't find the answer + you're looking for, don't hesitate to contact our friendly + support team. We're always happy to help. +

+
+
+
+
+ + {/* FAQ section */} + +
+

+ {' '} + Frequenty Asked Questions +

+ {FAqs.map((faq, index) => ( +
+
+
{ + handleFAQClick(index); + }} + > +

+ {faq.question} +

+ +
+

+ {faq.answer} +

+
+
+ ))} +
+ + ); +} diff --git a/frontend/src/components/Pages/Login.jsx b/frontend/src/components/Pages/Login.jsx index e39743d4..f7cfaf01 100644 --- a/frontend/src/components/Pages/Login.jsx +++ b/frontend/src/components/Pages/Login.jsx @@ -1,11 +1,10 @@ import React, { useState, useEffect } from 'react'; import photo from '../../assets/login.png'; import { Link, useNavigate } from 'react-router-dom'; -import { message } from 'antd'; +import { message } from 'antd'; // Ant Design message component import Cookies from 'js-cookie'; import { FaEye } from 'react-icons/fa'; import { FaEyeSlash } from 'react-icons/fa6'; - const Login = () => { const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; const [data, setData] = useState({ @@ -18,10 +17,19 @@ const Login = () => { const navigate = useNavigate(); + // Configuring the message to show at a certain top distance + message.config({ + top: 80, // Distance from the top of the viewport + duration: 2, // Auto close time in seconds + maxCount: 3, // Max number of messages + }); + + // Function to handle changes in the form inputs const handleChange = (e) => { setData({ ...data, [e.target.name]: e.target.value }); }; + // Function to handle form submission (login) const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); @@ -35,14 +43,40 @@ const Login = () => { body: JSON.stringify(data), }); const result = await response.json(); + if (!response.ok) { throw new Error(result.message || 'Login failed'); } + + // Successful login message with a light green line below + message.success({ + content: 'Login successful', + className: 'success-message', // Add custom class for styling + style: { + fontSize: '22px', + right: '50px', // Position it on the right side + position: 'fixed', // Fix it to the viewport + paddingTop: '10px', // Add padding to move the text down + paddingBottom: '10px', // Padding for balance + }, + }); + // Set token in cookies Cookies.set('authToken', result.token, { expire: '1h', secure: true }); - message.success('Login successful'); - navigate('/'); + navigate('/'); // Navigate to homepage after successful login } catch (err) { - setError(err.message || 'An error occurred. Please try again.'); + // Show error message if login fails with a red line below + message.error({ + content: 'Something went wrong while logging in.', + className: 'error-message', // Add custom class for styling + style: { + fontSize: '18px', + right: '50px', // Position it on the right side + position: 'fixed', // Fix it to the viewport + paddingTop: '10px', // Add padding to move the text down + paddingBottom: '10px', // Padding for balance + }, + }); + setError('An error occurred. Please try again.'); } finally { setIsLoading(false); } diff --git a/frontend/src/components/Pages/Signup.jsx b/frontend/src/components/Pages/Signup.jsx index b10ce1c5..ad46915a 100644 --- a/frontend/src/components/Pages/Signup.jsx +++ b/frontend/src/components/Pages/Signup.jsx @@ -1,10 +1,18 @@ -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; +import { message } from 'antd'; // Import Ant Design message import photo from '../../assets/login.png'; import { useNavigate, Link } from 'react-router-dom'; import { FaEye } from 'react-icons/fa'; import { FaEyeSlash } from 'react-icons/fa6'; import zxcvbn from 'zxcvbn'; // Password strength checker + +// Configure message globally +message.config({ + top: 80, // Align with the login page + duration: 2, // Duration of the message visibility +}); + const Signup = () => { const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; const navigate = useNavigate(); @@ -25,26 +33,43 @@ const Signup = () => { const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); + setError(null); // Reset error before submission + + // Frontend validation if (!data.email || !data.password || !data.name) { - setError('Please fill in all fields'); + message.error({ + content: 'Please fill in all fields.', + style: { fontSize: '18px' }, + }); setIsLoading(false); return; } if (data.password.length < 8) { - setError('Password must be at least 8 characters long'); + message.error({ + content: 'Password must be at least 8 characters long.', + style: { fontSize: '18px' }, + }); setIsLoading(false); return; } if (data.name.length < 3) { - setError('Name must be at least 3 characters long'); + message.error({ + content: 'Name must be at least 3 characters long.', + style: { fontSize: '18px' }, + }); setIsLoading(false); return; } if (!data.email.includes('@')) { - setError('Please enter a valid email address'); + message.error({ + content: 'Please enter a valid email address.', + style: { fontSize: '18px' }, + }); setIsLoading(false); return; } + + // Submit form data try { const response = await fetch(`${API_URL}/api/user/register`, { method: 'POST', @@ -52,16 +77,33 @@ const Signup = () => { body: JSON.stringify(data), }); const result = await response.json(); + if (!response.ok) { setIsLoading(false); - setError(result.error); + message.error({ + content: result.error || 'An error occurred during registration.', + style: { fontSize: '18px' }, + }); return; } - alert('Registered successfully! Please log in.'); - navigate('/'); + + // Successful registration message + message.success({ + content: 'Registered successfully! Please log in.', + style: { fontSize: '18px' }, + }); + + // Redirect to login page + navigate('/login'); } catch (error) { setError(error.message); console.error('Error:', error); + message.error({ + content: 'An error occurred while submitting the form.', + style: { fontSize: '18px' }, + }); + } finally { + setIsLoading(false); } }; @@ -139,11 +181,6 @@ const Signup = () => { Strength: {getPasswordStrengthText(passwordStrength)}

- {error && ( -
- {error} -
- )}

Already have an account? { name: 'About', link: '/about', }, + { + name: 'Help and Support', + link: '/help', + }, ]; const socialLink = [ { @@ -111,13 +116,13 @@ const Nav = () => {

About

{navLinks.map((item, index) => ( -
{item.name} - + ))}
diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index a14a06db..964706ca 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -21,6 +21,7 @@ import Admin from '../components/Pages/Admin'; import VerifyOtp from '../components/Pages/VerifyOtp'; import EmailVerify from '../components/Pages/EmailVerify'; import Membership from '../components/Membership'; +import HelpAndSupport from '../components/Pages/HelpAndSupport'; const router = createBrowserRouter( createRoutesFromElements( }> @@ -39,6 +40,7 @@ const router = createBrowserRouter( } /> } /> } /> + } /> )