Core Features & Requirements for the Event Management Website
🔐 User Authentication
Registration & login system
Secure authentication using JWT or OAuth
📅 Event Management
Create, update, and delete events
Store and display event details (title, date, location, description, price, etc.)
🧾 Booking System
Users can book tickets
Handle payments (simulation or integration)
Store booking records in the database
👤 User Dashboard
View booked events
Manage bookings
Receive notifications
🧰 Admin Controls
Admin dashboard to:
Manage events
Monitor user activity
View analytics
🌐 Frontend Requirements
Responsive UI
UI built with React.js
Design follows UI/UX best practices
🔧 Backend Requirements
Built with Node.js and Express
Use of a database (MongoDB or SQL)
Develop RESTful APIs for:
User management
Event handling
Booking system
Payments
💾 Database Requirements
Models for:
Users
Events
Bookings
Transactions (optional)
📊 Advanced Features (Optional but Encouraged)
✅ Event recommendations based on user interests
✅ Advanced search & filtering
✅ User preferences & bookmarks
✅ Commenting system
✅ Social media sharing
✅ Real-time updates (e.g., new events shown live)
✅ Admin analytics dashboard
🧪 Testing
Unit tests
Integration tests (e.g., Jest or Mocha)
🚀 Deployment
Deployed on hosting platforms like:
Netlify (frontend)
Heroku/AWS/Vercel (backend)
📄 Documentation
User manual
API documentation
Setup instructions for developers
🔐 Compliance & Maintenance
Monitor performance
Ensure data privacy (GDPR, CCPA)
import React, { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import API from '../api/api';
import { toast } from 'react-toastify';
import './eventList.css';
const categoryColors = {
Conference: 'conference',
Workshop: 'workshop',
Webinar: 'webinar',
Meetup: 'meetup',
Singing: 'singing',
Dancing: 'dancing',
Other: 'other',
};
const categoryIcons = {
Conference: '',
Workshop: '',
Webinar: '💻',
Meetup: '🤝',
Singing: '🎤',
Dancing: '💃',
Other: '📌',
};
const EventList = () => {
const [events, setEvents] = useState([]);
const [editEvent, setEditEvent] = useState(null);
const [formData, setFormData] = useState({
title: '', description: '', date: '', location: '', price: '', seats: '',
category: 'Other'
});
const [showPayment, setShowPayment] = useState(false);
const [paymentData, setPaymentData] = useState({});
const [processing, setProcessing] = useState(false);
const [cardInfo, setCardInfo] = useState({
cardNumber: '',
nameOnCard: '',
expiry: '',
cvv: ''
});
const [filters, setFilters] = useState({
search: '',
category: '',
startDate: '',
endDate: '',
sort: 'latest'
});
const navigate = useNavigate();
const isAdmin = localStorage.getItem('role') === 'admin';
const fetchEvents = useCallback(() => {
const query = new URLSearchParams();
if (filters.search) query.append('search', filters.search);
if (filters.category) query.append('category', filters.category);
if (filters.startDate) query.append('startDate', filters.startDate);
if (filters.endDate) query.append('endDate', filters.endDate);
if (filters.sort) query.append('sort', filters.sort);
API.get(`/events?${query.toString()}`)
.then(res => setEvents(res.data))
.catch(() => toast.error('❌ Failed to load events'));
}, [filters]);
useEffect(() => {
fetchEvents();
}, [fetchEvents]);
const handleFilterChange = (e) => {
setFilters({ ...filters, [e.target.name]: e.target.value });
};
const openPaymentModal = (event, seats) => {
setPaymentData({ event, seats, total: (event.price || 0) * seats });
setShowPayment(true);
};
const handleBook = async (eventId, maxSeats) => {
const token = localStorage.getItem('token');
if (!token) {
toast.warn('⚠️ Please login to book');
navigate('/login');
return;
}
const seats = prompt(`Enter number of seats (1 - ${maxSeats}):`);
const seatsBooked = parseInt(seats);
if (!seats || isNaN(seatsBooked) || seatsBooked < 1 || seatsBooked > maxSeats)
{
toast.warn(`⚠️ Enter valid seats between 1 and ${maxSeats}`);
return;
}
const selectedEvent = events.find(e => e._id === eventId);
if (!selectedEvent) {
toast.error('❌ Event not found');
return;
}
// ✅ Skip payment modal for free events
if (!selectedEvent.price || selectedEvent.price === 0) {
try {
await API.post('/bookings',
{ event: selectedEvent._id, seatsBooked, paymentType: 'Free' },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success('✅ Free Event Booked Successfully');
fetchEvents();
} catch (err) {
toast.error('❌ Booking failed');
}
return;
}
// If paid event, open payment modal
openPaymentModal(selectedEvent, seatsBooked);
};
const isValidCardDetails = () => {
const { cardNumber, nameOnCard, expiry, cvv } = cardInfo;
const cardRegex = /^\d{16}$/;
const nameRegex = /^[a-zA-Z ]{2,}$/;
const expiryRegex = /^(0[1-9]|1[0-2])\/\d{2}$/;
const cvvRegex = /^\d{3}$/;
if (!cardRegex.test(cardNumber)) {
toast.warning('⚠️ Card number must be 16 digits');
return false;
}
if (!nameRegex.test(nameOnCard)) {
toast.warning('⚠️ Enter a valid name');
return false;
}
if (!expiryRegex.test(expiry)) {
toast.warning('⚠️ Expiry must be MM/YY');
return false;
}
if (!cvvRegex.test(cvv)) {
toast.warning('⚠️ CVV must be 3 digits');
return false;
}
return true;
};
const confirmPayment = async () => {
const token = localStorage.getItem('token');
if (!isValidCardDetails()) return;
setProcessing(true);
setTimeout(async () => {
try {
await API.post('/bookings',
{ event: paymentData.event._id, seatsBooked: paymentData.seats,
paymentType: 'Credit Card' },
{ headers: { Authorization: `Bearer ${token}` } }
);
toast.success('✅ Payment & Booking Successful');
setShowPayment(false);
setCardInfo({ cardNumber: '', nameOnCard: '', expiry: '', cvv: '' });
fetchEvents();
} catch (err) {
toast.error('❌ Payment failed');
} finally {
setProcessing(false);
}
}, 1500);
};
const handleViewDetails = (eventId) => navigate(`/event/${eventId}`);
const handleDelete = async (eventId) => {
if (window.confirm('Are you sure you want to delete this event?')) {
try {
await API.delete(`/events/${eventId}`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
toast.success(' Event deleted');
fetchEvents();
} catch (err) {
toast.error('❌ Failed to delete event');
}
}
};
const handleEdit = (event) => {
setEditEvent(event._id);
setFormData({
title: event.title,
description: event.description,
date: event.date.substring(0, 10),
location: event.location,
price: event.price,
seats: event.seats,
category: event.category,
});
};
const handleUpdate = async () => {
try {
await API.put(`/events/${editEvent}`, formData, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
toast.success('✏️ Event updated');
setEditEvent(null);
fetchEvents();
} catch (err) {
toast.error('❌ Failed to update event');
}
};
return (
<div className="event-wrapper">
<h2 className="event-heading">🎉 Explore Events</h2>
{/* Filter Bar */}
<div className="filter-bar">
<input
type="text"
name="search"
placeholder="🔍 Search events..."
value={filters.search}
onChange={handleFilterChange}
/>
<select name="category" value={filters.category}
onChange={handleFilterChange}>
<option value="">All Categories</option>
{Object.keys(categoryIcons).map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<input type="date" name="startDate" value={filters.startDate}
onChange={handleFilterChange} />
<input type="date" name="endDate" value={filters.endDate}
onChange={handleFilterChange} />
<select name="sort" value={filters.sort} onChange={handleFilterChange}>
<option value="latest">Latest</option>
<option value="oldest">Oldest</option>
</select>
</div>
{/* Event Grid */}
<div className="event-grid">
{events.map(e => {
const category = e.category || 'Other';
const tagClass = categoryColors[category] || 'other';
const icon = categoryIcons[category] || '📌';
return (
<div key={e._id} className="event-card">
<span className={`event-tag ${tagClass}`}>
{icon} {category}
</span>
<h3 className="event-title">{e.title}</h3>
<p className="event-desc">{e.description.substring(0, 100)}...</p>
<div className="event-info">
<p><strong>Date:</strong> {new Date(e.date).toDateString()}</p>
<p><strong>Location:</strong> {e.location}</p>
<p><strong>Price:</strong> ₹{e.price || 'Free'}</p>
<p><strong>Seats Left:</strong> {e.seats}</p>
</div>
<div className="event-actions">
<button
onClick={() => handleBook(e._id, e.seats)}
disabled={e.seats === 0}
className={e.seats === 0 ? 'btn disabled' : 'btn book'}
>
{e.seats === 0 ? 'Sold Out' : 'Book'}
</button>
<button onClick={() => handleViewDetails(e._id)} className="btn
details">View Details</button>
{isAdmin && (
<>
<button onClick={() => handleEdit(e)} className="btn
edit">Edit</button>
<button onClick={() => handleDelete(e._id)} className="btn
delete">Delete</button>
</>
)}
</div>
</div>
);
})}
</div>
{/* Payment Modal */}
{showPayment && (
<div className="payment-modal">
<div className="payment-box">
<h3>💳 Simulated Payment</h3>
<p><strong>Event:</strong> {paymentData.event.title}</p>
<p><strong>Seats:</strong> {paymentData.seats}</p>
<p><strong>Total:</strong> ₹{paymentData.total}</p>
<input placeholder="1234 5678 9012 3456" value={cardInfo.cardNumber}
onChange={(e) => setCardInfo({ ...cardInfo, cardNumber: e.target.value })} />
<input placeholder="Name on Card" value={cardInfo.nameOnCard}
onChange={(e) => setCardInfo({ ...cardInfo, nameOnCard: e.target.value })} />
<input placeholder="MM/YY" value={cardInfo.expiry} onChange={(e) =>
setCardInfo({ ...cardInfo, expiry: e.target.value })} />
<input placeholder="CVV" value={cardInfo.cvv} onChange={(e) =>
setCardInfo({ ...cardInfo, cvv: e.target.value })} />
<button onClick={confirmPayment} className="pay-btn"
disabled={processing}>{processing ? 'Processing...' : `Pay ₹$
{paymentData.total}`}</button>
<button onClick={() => setShowPayment(false)} className="cancel-
btn">Cancel</button>
</div>
</div>
)}
{/* Edit Form */}
{editEvent && (
<div className="edit-form">
<h3>✏️ Edit Event</h3>
<input value={formData.title} onChange={e => setFormData({ ...formData,
title: e.target.value })} placeholder="Title" />
<textarea value={formData.description} onChange={e =>
setFormData({ ...formData, description: e.target.value })}
placeholder="Description" />
<input type="date" value={formData.date} onChange={e =>
setFormData({ ...formData, date: e.target.value })} />
<input value={formData.location} onChange={e =>
setFormData({ ...formData, location: e.target.value })} placeholder="Location" />
<input type="number" value={formData.price} onChange={e =>
setFormData({ ...formData, price: e.target.value })} placeholder="Price" />
<input type="number" value={formData.seats} onChange={e =>
setFormData({ ...formData, seats: e.target.value })} placeholder="Seats" />
<select value={formData.category} onChange={e =>
setFormData({ ...formData, category: e.target.value })}>
{Object.keys(categoryIcons).map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<button onClick={handleUpdate} className="btn update">Save</button>
<button onClick={() => setEditEvent(null)} className="btn
cancel">Cancel</button>
</div>
)}
</div>
);
};
export default EventList;
import Booking from '../models/Booking.js';
import Event from '../models/Event.js';
import User from '../models/User.js'; // Optional
import mongoose from 'mongoose';
// ✅ Book an Event with free event payment type handling
export const bookEvent = async (req, res) => {
try {
const { event, seatsBooked, paymentType } = req.body;
if (!event || !seatsBooked) {
return res.status(400).json({ message: 'Event and seat count required' });
}
const foundEvent = await Event.findById(event);
if (!foundEvent) {
return res.status(404).json({ message: 'Event not found' });
}
if (seatsBooked > foundEvent.seats) {
return res.status(400).json({ message: `Only ${foundEvent.seats} seats
available` });
}
// ✅ Auto-set paymentType to 'Free' if event is free
let finalPaymentType = paymentType || 'Credit Card';
if (foundEvent.price === 0) {
finalPaymentType = 'Free';
}
const booking = new Booking({
event,
seatsBooked,
user: req.user._id,
paymentType: finalPaymentType,
status: 'Booked'
});
await booking.save();
foundEvent.seats -= seatsBooked;
await foundEvent.save();
const io = req.app.get('io');
io.emit('notification', {
type: 'booking',
message: ` New booking by user ${req.user._id} for "${foundEvent.title}"`,
timestamp: new Date(),
});
res.status(201).json({ message: 'Event booked successfully' });
} catch (error) {
console.error('Booking error:', error);
res.status(500).json({ message: 'Booking failed', error });
}
};
// ✅ Get All Bookings of Logged-in User (with safe deleted event handling)
export const getMyBookings = async (req, res) => {
try {
const bookings = await Booking.find({ user: req.user._id }).populate('event');
const sanitizedBookings = bookings.map(b => {
const plainBooking = b.toObject();
if (!b.event) {
plainBooking.eventDeleted = true;
}
return plainBooking;
});
res.status(200).json(sanitizedBookings);
} catch (error) {
console.error('Fetch bookings error:', error);
res.status(500).json({ message: 'Failed to fetch your bookings', error });
}
};
// ✅ Cancel a Booking with ObjectId validation and safe event restoration
export const cancelBooking = async (req, res) => {
try {
const bookingId = req.params.id;
if (!mongoose.Types.ObjectId.isValid(bookingId)) {
return res.status(400).json({ message: 'Invalid booking ID format' });
}
const booking = await Booking.findById(bookingId).populate('event');
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
if (booking.user.toString() !== req.user._id.toString()) {
return res.status(403).json({ message: 'Unauthorized to cancel this
booking' });
}
if (booking.status === 'Canceled') {
return res.status(200).json({ message: 'Booking was already canceled' });
}
booking.status = 'Canceled';
await booking.save();
if (booking.event) {
booking.event.seats += booking.seatsBooked;
await booking.event.save();
const io = req.app.get('io');
io.emit('notification', {
type: 'cancel',
message: `❌ Booking for "${booking.event.title}" cancelled by user $
{req.user._id}`,
timestamp: new Date(),
});
}
res.status(200).json({ message: 'Booking cancelled successfully' });
} catch (error) {
console.error('Cancellation error:', error);
res.status(500).json({ message: 'Cancellation failed', error });
}
};
// ✅ Get Booking by ID with safe deleted event flag
export const getBookingById = async (req, res) => {
try {
const bookingId = req.params.id;
if (!mongoose.Types.ObjectId.isValid(bookingId)) {
return res.status(400).json({ message: 'Invalid booking ID format' });
}
const booking = await Booking.findById(bookingId)
.populate('event')
.populate('user', 'name email');
if (!booking) {
return res.status(404).json({ message: 'Booking not found' });
}
if (booking.user._id.toString() !== req.user._id.toString()) {
return res.status(403).json({ message: 'Unauthorized to view this ticket' });
}
const sanitizedBooking = booking.toObject();
if (!booking.event) {
sanitizedBooking.eventDeleted = true;
}
res.status(200).json(sanitizedBooking);
} catch (error) {
console.error('Fetch booking by ID error:', error);
res.status(500).json({ message: 'Failed to fetch booking', error });
}
};