Creating a full-stack event management system with Next.js, NestJS, and Tailwind CSS involves several key features and functionalities. Here's an outline of the features, the architecture, and the documentation to guide you through the development process:
Features and Functionalities
1. User Authentication
- Sign Up: Users can create an account.
- Login: Users can log in to their account.
- Password Reset: Users can reset their password.
2. User Roles
- Admin: Can manage events, users, and system settings.
- Organizer: Can create and manage their own events.
- Attendee: Can view and register for events.
3. Event Management
- Create Event: Organizers can create events with details like title, description, date, time, venue, and ticket information.
- Edit Event: Organizers can edit their events.
- Delete Event: Organizers can delete their events.
- View Event: Users can view event details.
- Search Events: Users can search for events by various criteria (e.g., date, location, type).
4. Ticket Management
- Create Tickets: Organizers can create different types of tickets for an event.
- Purchase Tickets: Attendees can purchase tickets.
- View Tickets: Attendees can view their purchased tickets.
5. Notification System
- Email Notifications: Users receive email notifications for important actions (e.g., event creation, ticket purchase).
- In-App Notifications: Real-time notifications within the app.
6. Payment Integration
- Payment Gateway: Integrate with a payment gateway for ticket purchases.
7. Dashboard
- Admin Dashboard: Overview of system metrics, user management, event management.
- Organizer Dashboard: Overview of their events, ticket sales, attendee list.
- Attendee Dashboard: Overview of registered events and purchased tickets.
8. Analytics
- Event Analytics: Insights into event performance (e.g., ticket sales, attendee demographics).
- User Analytics: Insights into user behavior and engagement.
Architecture and Tech Stack
Frontend
- Next.js: For server-side rendering and frontend development.
- Tailwind CSS: For styling the frontend components.
- React: Core library for building UI components.
Backend
- NestJS: For building the server-side application with TypeScript.
- PostgreSQL: For the database.
- Prisma: ORM for database interactions.
- GraphQL: API for communication between frontend and backend.
Deployment
- Vercel: For deploying the Next.js application.
- Heroku: For deploying the NestJS backend.
- Docker: For containerizing the applications.
Documentation
1. Setting Up the Development Environment
Prerequisites
- Node.js
- npm or yarn
- PostgreSQL
- Docker
Frontend (Next.js + Tailwind CSS)
- Initialize Next.js project:
npx create-next-app@latest event-management-frontend
cd event-management-frontend
- Install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
- Configure
tailwind.config.js
:
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
- Add Tailwind directives to
styles/globals.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
Backend (NestJS + Prisma)
- Initialize NestJS project:
npm i -g @nestjs/cli
nest new event-management-backend
cd event-management-backend
- Install Prisma and PostgreSQL client:
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express
npm install @prisma/client
npm install -D prisma
- Initialize Prisma:
npx prisma init
- Configure
prisma/schema.prisma
for PostgreSQL:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
role String
events Event[]
tickets Ticket[]
}
model Event {
id Int @id @default(autoincrement())
title String
description String
date DateTime
organizer User @relation(fields: [organizerId], references: [id])
organizerId Int
tickets Ticket[]
}
model Ticket {
id Int @id @default(autoincrement())
type String
price Float
event Event @relation(fields: [eventId], references: [id])
eventId Int
attendee User @relation(fields: [attendeeId], references: [id])
attendeeId Int
}
- Run Prisma migrations:
npx prisma migrate dev --name init
2. Building the Features
User Authentication
- Implement signup, login, and password reset in NestJS.
- Create corresponding frontend pages in Next.js.
Event Management
- Create APIs for creating, editing, deleting, and viewing events in NestJS.
- Develop frontend components and pages for event management in Next.js.
Ticket Management
- Develop APIs for ticket creation, purchase, and viewing in NestJS.
- Create frontend components and pages for ticket management in Next.js.
Notification System
- Implement email notifications using a service like SendGrid.
- Develop in-app notifications using WebSockets.
Payment Integration
- Integrate with a payment gateway like Stripe or PayPal in NestJS.
- Implement payment workflows in the frontend.
Dashboard
- Build admin, organizer, and attendee dashboards with necessary metrics and features.
Analytics
- Use a library like Chart.js to display analytics in the frontend.
- Implement backend logic for aggregating and providing analytics data.
Deployment
Frontend
- Deploy the Next.js application on Vercel.
- Connect your GitHub repository to Vercel for automatic deployments.
Backend
- Create a Dockerfile for the NestJS application.
- Deploy the NestJS application on Heroku using Docker.
This documentation provides a comprehensive guide to developing a full-stack event management system. Feel free to ask for more detailed information or specific code examples for any part of the process.
Sure! Here’s how you can implement the frontend for user authentication (Sign Up, Login, Password Reset) using Next.js and Tailwind CSS.
Project Structure
event-management-frontend
├── components
│ ├── Auth
│ │ ├── LoginForm.js
│ │ ├── SignupForm.js
│ │ ├── PasswordResetForm.js
├── pages
│ ├── auth
│ │ ├── login.js
│ │ ├── signup.js
│ │ ├── reset-password.js
│ ├── index.js
└── styles
└── globals.css
1. Sign Up
components/Auth/SignupForm.js
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const SignupForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleSignup = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
try {
await axios.post('/api/auth/signup', { email, password });
router.push('/auth/login');
} catch (err) {
setError(err.response.data.message);
}
};
return (
<div className="max-w-md mx-auto mt-10">
<h1 className="text-2xl font-bold mb-6">Sign Up</h1>
<form onSubmit={handleSignup}>
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Confirm Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
{error && <p className="text-red-500">{error}</p>}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Sign Up
</button>
</form>
</div>
);
};
export default SignupForm;
pages/auth/signup.js
import SignupForm from '../../components/Auth/SignupForm';
const SignupPage = () => {
return (
<div>
<SignupForm />
</div>
);
};
export default SignupPage;
2. Login
components/Auth/LoginForm.js
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleLogin = async (e) => {
e.preventDefault();
try {
await axios.post('/api/auth/login', { email, password });
router.push('/');
} catch (err) {
setError(err.response.data.message);
}
};
return (
<div className="max-w-md mx-auto mt-10">
<h1 className="text-2xl font-bold mb-6">Login</h1>
<form onSubmit={handleLogin}>
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
{error && <p className="text-red-500">{error}</p>}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Login
</button>
</form>
</div>
);
};
export default LoginForm;
pages/auth/login.js
import LoginForm from '../../components/Auth/LoginForm';
const LoginPage = () => {
return (
<div>
<LoginForm />
</div>
);
};
export default LoginPage;
3. Password Reset
components/Auth/PasswordResetForm.js
import { useState } from 'react';
import axios from 'axios';
const PasswordResetForm = () => {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const handlePasswordReset = async (e) => {
e.preventDefault();
try {
await axios.post('/api/auth/reset-password', { email });
setMessage('A password reset link has been sent to your email');
} catch (err) {
setError(err.response.data.message);
}
};
return (
<div className="max-w-md mx-auto mt-10">
<h1 className="text-2xl font-bold mb-6">Reset Password</h1>
<form onSubmit={handlePasswordReset}>
<div className="mb-4">
<label className="block text-gray-700">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
{message && <p className="text-green-500">{message}</p>}
{error && <p className="text-red-500">{error}</p>}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Reset Password
</button>
</form>
</div>
);
};
export default PasswordResetForm;
pages/auth/reset-password.js
import PasswordResetForm from '../../components/Auth/PasswordResetForm';
const PasswordResetPage = () => {
return (
<div>
<PasswordResetForm />
</div>
);
};
export default PasswordResetPage;
Styles
Make sure you have Tailwind CSS properly configured in your globals.css
as mentioned in the initial setup.
API Endpoints
The above forms make POST requests to /api/auth/signup
, /api/auth/login
, and /api/auth/reset-password
. You need to implement these endpoints in your backend (NestJS) to handle these requests.
This setup should give you a fully functioning frontend for user authentication in your event management system.
To implement user roles (Admin, Organizer, and Attendee) in the frontend using Next.js and Tailwind CSS, you'll need to create different components and pages for each role. Here's an example setup for each role's dashboard and basic functionality.
Project Structure
event-management-frontend
├── components
│ ├── Admin
│ │ ├── AdminDashboard.js
│ │ ├── ManageEvents.js
│ │ ├── ManageUsers.js
│ ├── Organizer
│ │ ├── OrganizerDashboard.js
│ │ ├── CreateEvent.js
│ │ ├── ManageOwnEvents.js
│ ├── Attendee
│ │ ├── AttendeeDashboard.js
│ │ ├── ViewEvents.js
│ │ ├── RegisterEvent.js
├── pages
│ ├── admin
│ │ ├── index.js
│ │ ├── manage-events.js
│ │ ├── manage-users.js
│ ├── organizer
│ │ ├── index.js
│ │ ├── create-event.js
│ │ ├── manage-events.js
│ ├── attendee
│ │ ├── index.js
│ │ ├── view-events.js
│ │ ├── register-event.js
└── styles
└── globals.css
1. Admin
components/Admin/AdminDashboard.js
const AdminDashboard = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Admin Dashboard</h1>
<div className="flex space-x-4">
<a href="/admin/manage-events" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">Manage Events</a>
<a href="/admin/manage-users" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">Manage Users</a>
</div>
</div>
);
};
export default AdminDashboard;
components/Admin/ManageEvents.js
const ManageEvents = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Manage Events</h1>
{/* Add functionality to list and manage all events */}
</div>
);
};
export default ManageEvents;
components/Admin/ManageUsers.js
const ManageUsers = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Manage Users</h1>
{/* Add functionality to list and manage all users */}
</div>
);
};
export default ManageUsers;
pages/admin/index.js
import AdminDashboard from '../../components/Admin/AdminDashboard';
const AdminPage = () => {
return (
<div>
<AdminDashboard />
</div>
);
};
export default AdminPage;
pages/admin/manage-events.js
import ManageEvents from '../../components/Admin/ManageEvents';
const ManageEventsPage = () => {
return (
<div>
<ManageEvents />
</div>
);
};
export default ManageEventsPage;
pages/admin/manage-users.js
import ManageUsers from '../../components/Admin/ManageUsers';
const ManageUsersPage = () => {
return (
<div>
<ManageUsers />
</div>
);
};
export default ManageUsersPage;
2. Organizer
components/Organizer/OrganizerDashboard.js
const OrganizerDashboard = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Organizer Dashboard</h1>
<div className="flex space-x-4">
<a href="/organizer/create-event" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">Create Event</a>
<a href="/organizer/manage-events" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">Manage Events</a>
</div>
</div>
);
};
export default OrganizerDashboard;
components/Organizer/CreateEvent.js
import { useState } from 'react';
import axios from 'axios';
const CreateEvent = () => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [date, setDate] = useState('');
const [time, setTime] = useState('');
const [venue, setVenue] = useState('');
const handleCreateEvent = async (e) => {
e.preventDefault();
try {
await axios.post('/api/events', { title, description, date, time, venue });
alert('Event created successfully');
} catch (err) {
console.error(err);
alert('Error creating event');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Create Event</h1>
<form onSubmit={handleCreateEvent}>
<div className="mb-4">
<label className="block text-gray-700">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
></textarea>
</div>
<div className="mb-4">
<label className="block text-gray-700">Date</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Time</label>
<input
type="time"
value={time}
onChange={(e) => setTime(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Venue</label>
<input
type="text"
value={venue}
onChange={(e) => setVenue(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Create Event
</button>
</form>
</div>
);
};
export default CreateEvent;
components/Organizer/ManageOwnEvents.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const ManageOwnEvents = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/organizer/events');
setEvents(response.data);
};
fetchEvents();
}, []);
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Manage Your Events</h1>
<div>
{events.map((event) => (
<div key={event.id} className="mb-4 p-4 border rounded">
<h2 className="text-2xl font-bold">{event.title}</h2>
<p>{event.description}</p>
<p>{new Date(event.date).toLocaleDateString()} {event.time}</p>
<p>{event.venue}</p>
{/* Add buttons for editing and deleting the event */}
</div>
))}
</div>
</div>
);
};
export default ManageOwnEvents;
pages/organizer/index.js
import OrganizerDashboard from '../../components/Organizer/OrganizerDashboard';
const OrganizerPage = () => {
return (
<div>
<OrganizerDashboard />
</div>
);
};
export default OrganizerPage;
pages/organizer/create-event.js
import CreateEvent from '../../components/Organizer/CreateEvent';
const CreateEventPage = () => {
return (
<div>
<CreateEvent />
</div>
);
};
export default CreateEventPage;
pages/organizer/manage-events.js
import ManageOwnEvents from '../../components/Organizer/ManageOwnEvents';
const ManageOwnEventsPage = () => {
return (
<div>
<ManageOwnEvents />
</div>
);
};
export default ManageOwnEventsPage;
3. Attendee
components/Attendee/AttendeeDashboard.js
const AttendeeDashboard = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Attendee Dashboard</h1>
<div className="flex space-x-4">
<a href="/attendee/view-events" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">View Events</a>
<a href="/attendee/register-event" className="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700">Register for Event</a>
</div>
</div>
);
};
export default AttendeeDashboard;
components/Attendee/ViewEvents.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const ViewEvents = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/events');
setEvents(response.data);
};
fetchEvents();
}, []);
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">View Events</h1>
<div>
{events.map((event) => (
<div key={event.id} className="mb-4 p-4 border rounded">
<h2 className="text-2xl font-bold">{event.title}</h2>
<p>{event.description}</p>
<p>{new Date(event.date).toLocaleDateString()} {event.time}</p>
<p>{event.venue}</p>
</div>
))}
</div>
</div>
);
};
export default ViewEvents;
components/Attendee/RegisterEvent.js
import { useState } from 'react';
import axios from 'axios';
const RegisterEvent = () => {
const [eventId, setEventId] = useState('');
const handleRegister = async (e) => {
e.preventDefault();
try {
await axios.post(`/api/events/${eventId}/register`);
alert('Successfully registered for the event');
} catch (err) {
console.error(err);
alert('Error registering for the event');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Register for Event</h1>
<form onSubmit={handleRegister}>
<div className="mb-4">
<label className="block text-gray-700">Event ID</label>
<input
type="text"
value={eventId}
onChange={(e) => setEventId(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Register
</button>
</form>
</div>
);
};
export default RegisterEvent;
pages/attendee/index.js
import AttendeeDashboard from '../../components/Attendee/AttendeeDashboard';
const AttendeePage = () => {
return (
<div>
<AttendeeDashboard />
</div>
);
};
export default AttendeePage;
pages/attendee/view-events.js
import ViewEvents from '../../components/Attendee/ViewEvents';
const ViewEventsPage = () => {
return (
<div>
<ViewEvents />
</div>
);
};
export default ViewEventsPage;
pages/attendee/register-event.js
import RegisterEvent from '../../components/Attendee/RegisterEvent';
const RegisterEventPage = () => {
return (
<div>
<RegisterEvent />
</div>
);
};
export default RegisterEventPage;
Navigation and Authorization
You should also implement navigation and role-based authorization checks to ensure users only access the pages they are allowed to. This can be achieved using a combination of custom hooks, context providers, and middleware.
Styles
Make sure Tailwind CSS is properly configured in your globals.css
.
With these components and pages, you can build a user role-based frontend for your event management system. The functionality for each role can be expanded as needed.
To implement event management features in the frontend using Next.js and Tailwind CSS, you'll need components and pages for creating, editing, deleting, viewing, and searching events. Below is an example setup for these functionalities.
Project Structure
event-management-frontend
├── components
│ ├── Organizer
│ │ ├── CreateEvent.js
│ │ ├── EditEvent.js
│ │ ├── DeleteEvent.js
│ │ ├── ManageOwnEvents.js
│ ├── Event
│ │ ├── EventDetails.js
│ │ ├── SearchEvents.js
├── pages
│ ├── organizer
│ │ ├── create-event.js
│ │ ├── edit-event.js
│ │ ├── manage-events.js
│ ├── events
│ │ ├── [id].js
│ │ ├── search.js
└── styles
└── globals.css
1. Create Event
components/Organizer/CreateEvent.js
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const CreateEvent = () => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [date, setDate] = useState('');
const [time, setTime] = useState('');
const [venue, setVenue] = useState('');
const [ticketInfo, setTicketInfo] = useState('');
const router = useRouter();
const handleCreateEvent = async (e) => {
e.preventDefault();
try {
await axios.post('/api/events', { title, description, date, time, venue, ticketInfo });
router.push('/organizer/manage-events');
} catch (err) {
console.error(err);
alert('Error creating event');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Create Event</h1>
<form onSubmit={handleCreateEvent}>
<div className="mb-4">
<label className="block text-gray-700">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
></textarea>
</div>
<div className="mb-4">
<label className="block text-gray-700">Date</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Time</label>
<input
type="time"
value={time}
onChange={(e) => setTime(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Venue</label>
<input
type="text"
value={venue}
onChange={(e) => setVenue(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Ticket Information</label>
<input
type="text"
value={ticketInfo}
onChange={(e) => setTicketInfo(e.target.value)}
className="w-full px-3 py-2 border rounded"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Create Event
</button>
</form>
</div>
);
};
export default CreateEvent;
pages/organizer/create-event.js
import CreateEvent from '../../components/Organizer/CreateEvent';
const CreateEventPage = () => {
return (
<div>
<CreateEvent />
</div>
);
};
export default CreateEventPage;
2. Edit Event
components/Organizer/EditEvent.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useRouter } from 'next/router';
const EditEvent = () => {
const router = useRouter();
const { id } = router.query;
const [event, setEvent] = useState(null);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [date, setDate] = useState('');
const [time, setTime] = useState('');
const [venue, setVenue] = useState('');
const [ticketInfo, setTicketInfo] = useState('');
useEffect(() => {
if (id) {
axios.get(`/api/events/${id}`)
.then(response => {
const eventData = response.data;
setEvent(eventData);
setTitle(eventData.title);
setDescription(eventData.description);
setDate(eventData.date);
setTime(eventData.time);
setVenue(eventData.venue);
setTicketInfo(eventData.ticketInfo);
})
.catch(error => console.error('Error fetching event:', error));
}
}, [id]);
const handleEditEvent = async (e) => {
e.preventDefault();
try {
await axios.put(`/api/events/${id}`, { title, description, date, time, venue, ticketInfo });
router.push('/organizer/manage-events');
} catch (err) {
console.error(err);
alert('Error editing event');
}
};
if (!event) return <div>Loading...</div>;
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Edit Event</h1>
<form onSubmit={handleEditEvent}>
<div className="mb-4">
<label className="block text-gray-700">Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
></textarea>
</div>
<div className="mb-4">
<label className="block text-gray-700">Date</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Time</label>
<input
type="time"
value={time}
onChange={(e) => setTime(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Venue</label>
<input
type="text"
value={venue}
onChange={(e) => setVenue(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Ticket Information</label>
<input
type="text"
value={ticketInfo}
onChange={(e) => setTicketInfo(e.target.value)}
className="w-full px-3 py-2 border rounded"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Edit Event
</button>
</form>
</div>
);
};
export default EditEvent;
pages/organizer/edit-event.js
import EditEvent from '../../components/Organizer/EditEvent';
const EditEventPage = () => {
return (
<div>
<EditEvent
/>
</div>
);
};
export default EditEventPage;
3. Delete Event
components/Organizer/DeleteEvent.js
import axios from 'axios';
import { useRouter } from 'next/router';
const DeleteEvent = ({ eventId }) => {
const router = useRouter();
const handleDeleteEvent = async () => {
try {
await axios.delete(`/api/events/${eventId}`);
router.push('/organizer/manage-events');
} catch (err) {
console.error(err);
alert('Error deleting event');
}
};
return (
<button
onClick={handleDeleteEvent}
className="bg-red-500 text-white py-2 px-4 rounded hover:bg-red-700"
>
Delete Event
</button>
);
};
export default DeleteEvent;
4. Manage Own Events
components/Organizer/ManageOwnEvents.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import Link from 'next/link';
const ManageOwnEvents = () => {
const [events, setEvents] = useState([]);
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/events/mine');
setEvents(response.data);
};
fetchEvents();
}, []);
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Manage My Events</h1>
<div>
{events.map((event) => (
<div key={event.id} className="mb-4 p-4 border rounded">
<h2 className="text-2xl font-bold">{event.title}</h2>
<p>{event.description}</p>
<p>{new Date(event.date).toLocaleDateString()} {event.time}</p>
<p>{event.venue}</p>
<div className="flex space-x-4 mt-4">
<Link href={`/organizer/edit-event?id=${event.id}`}>
<a className="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-700">Edit</a>
</Link>
<DeleteEvent eventId={event.id} />
</div>
</div>
))}
</div>
</div>
);
};
export default ManageOwnEvents;
5. View Event
components/Event/EventDetails.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const EventDetails = () => {
const router = useRouter();
const { id } = router.query;
const [event, setEvent] = useState(null);
useEffect(() => {
if (id) {
axios.get(`/api/events/${id}`)
.then(response => {
setEvent(response.data);
})
.catch(error => console.error('Error fetching event:', error));
}
}, [id]);
if (!event) return <div>Loading...</div>;
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">{event.title}</h1>
<p className="mb-4">{event.description}</p>
<p className="mb-2">Date: {new Date(event.date).toLocaleDateString()}</p>
<p className="mb-2">Time: {event.time}</p>
<p className="mb-2">Venue: {event.venue}</p>
<p className="mb-2">Tickets: {event.ticketInfo}</p>
</div>
);
};
export default EventDetails;
pages/events/[id].js
import EventDetails from '../../components/Event/EventDetails';
const EventDetailsPage = () => {
return (
<div>
<EventDetails />
</div>
);
};
export default EventDetailsPage;
6. Search Events
components/Event/SearchEvents.js
import { useState } from 'react';
import axios from 'axios';
const SearchEvents = () => {
const [query, setQuery] = useState('');
const [events, setEvents] = useState([]);
const handleSearch = async (e) => {
e.preventDefault();
try {
const response = await axios.get(`/api/events/search?query=${query}`);
setEvents(response.data);
} catch (err) {
console.error(err);
alert('Error searching events');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Search Events</h1>
<form onSubmit={handleSearch} className="mb-6">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full px-3 py-2 border rounded mb-4"
placeholder="Search by title, date, location..."
required
/>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Search
</button>
</form>
<div>
{events.map((event) => (
<div key={event.id} className="mb-4 p-4 border rounded">
<h2 className="text-2xl font-bold">{event.title}</h2>
<p>{event.description}</p>
<p>{new Date(event.date).toLocaleDateString()} {event.time}</p>
<p>{event.venue}</p>
</div>
))}
</div>
</div>
);
};
export default SearchEvents;
pages/events/search.js
import SearchEvents from '../../components/Event/SearchEvents';
const SearchEventsPage = () => {
return (
<div>
<SearchEvents />
</div>
);
};
export default SearchEventsPage;
Navigation and Authorization
Ensure you have navigation links set up and proper authorization checks for each role to access specific pages.
Styles
Make sure Tailwind CSS is properly configured in your globals.css
.
With these components and pages, you can build a comprehensive event management system frontend using Next.js and Tailwind CSS.
To implement ticket management features in the frontend using Next.js and Tailwind CSS, you need components and pages for creating tickets, purchasing tickets, and viewing purchased tickets. Below is the example setup for these functionalities.
Project Structure
event-management-frontend
├── components
│ ├── Organizer
│ │ ├── CreateTickets.js
│ ├── Attendee
│ │ ├── PurchaseTickets.js
│ │ ├── ViewTickets.js
├── pages
│ ├── organizer
│ │ ├── create-tickets.js
│ ├── attendee
│ │ ├── purchase-tickets.js
│ │ ├── view-tickets.js
└── styles
└── globals.css
1. Create Tickets
components/Organizer/CreateTickets.js
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const CreateTickets = () => {
const [eventId, setEventId] = useState('');
const [ticketType, setTicketType] = useState('');
const [price, setPrice] = useState('');
const [quantity, setQuantity] = useState('');
const router = useRouter();
const handleCreateTickets = async (e) => {
e.preventDefault();
try {
await axios.post(`/api/events/${eventId}/tickets`, { ticketType, price, quantity });
router.push('/organizer/manage-events');
} catch (err) {
console.error(err);
alert('Error creating tickets');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Create Tickets</h1>
<form onSubmit={handleCreateTickets}>
<div className="mb-4">
<label className="block text-gray-700">Event ID</label>
<input
type="text"
value={eventId}
onChange={(e) => setEventId(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Ticket Type</label>
<input
type="text"
value={ticketType}
onChange={(e) => setTicketType(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Price</label>
<input
type="number"
value={price}
onChange={(e) => setPrice(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">Quantity</label>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Create Tickets
</button>
</form>
</div>
);
};
export default CreateTickets;
pages/organizer/create-tickets.js
import CreateTickets from '../../components/Organizer/CreateTickets';
const CreateTicketsPage = () => {
return (
<div>
<CreateTickets />
</div>
);
};
export default CreateTicketsPage;
2. Purchase Tickets
components/Attendee/PurchaseTickets.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const PurchaseTickets = () => {
const [events, setEvents] = useState([]);
const [selectedEvent, setSelectedEvent] = useState('');
const [tickets, setTickets] = useState([]);
const [selectedTicket, setSelectedTicket] = useState('');
const [quantity, setQuantity] = useState(1);
const router = useRouter();
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/events');
setEvents(response.data);
};
fetchEvents();
}, []);
useEffect(() => {
if (selectedEvent) {
const fetchTickets = async () => {
const response = await axios.get(`/api/events/${selectedEvent}/tickets`);
setTickets(response.data);
};
fetchTickets();
}
}, [selectedEvent]);
const handlePurchase = async (e) => {
e.preventDefault();
try {
await axios.post(`/api/tickets/purchase`, { eventId: selectedEvent, ticketId: selectedTicket, quantity });
alert('Successfully purchased tickets');
router.push('/attendee/view-tickets');
} catch (err) {
console.error(err);
alert('Error purchasing tickets');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Purchase Tickets</h1>
<form onSubmit={handlePurchase}>
<div className="mb-4">
<label className="block text-gray-700">Select Event</label>
<select
value={selectedEvent}
onChange={(e) => setSelectedEvent(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Event</option>
{events.map(event => (
<option key={event.id} value={event.id}>{event.title}</option>
))}
</select>
</div>
{selectedEvent && (
<>
<div className="mb-4">
<label className="block text-gray-700">Select Ticket</label>
<select
value={selectedTicket}
onChange={(e) => setSelectedTicket(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Ticket</option>
{tickets.map(ticket => (
<option key={ticket.id} value={ticket.id}>{ticket.ticketType} - ${ticket.price}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="block text-gray-700">Quantity</label>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
className="w-full px-3 py-2 border rounded"
min="1"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Purchase Tickets
</button>
</>
)}
</form>
</div>
);
};
export default PurchaseTickets;
pages/attendee/purchase-tickets.js
import PurchaseTickets from '../../components/Attendee/PurchaseTickets';
const PurchaseTicketsPage = () => {
return (
<div>
<PurchaseTickets />
</div>
);
};
export default PurchaseTicketsPage;
3. View Tickets
components/Attendee/ViewTickets.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const ViewTickets = () => {
const [tickets, setTickets] = useState([]);
useEffect(() => {
const fetchTickets = async () => {
const response = await axios.get('/api/tickets');
setTickets(response.data);
};
fetchTickets();
}, []);
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">My Tickets</h1>
<div>
{tickets.map((ticket) => (
<div key={ticket.id} className="mb-4 p-4 border rounded">
<h2 className="text-2xl font-bold">{ticket.event.title}</h2>
<p>Type: {ticket.ticketType}</p>
<p>Price: ${ticket.price}</p>
<p>Quantity: {ticket.quantity}</p>
<p>Date: {new Date(ticket.event.date).toLocaleDateString()}</p>
<p>Time: {ticket.event.time}</p>
<p>Venue: {ticket.event.venue}</p>
</div>
))}
</div>
</div>
);
};
export default ViewTickets;
pages/attendee/view-tickets.js
import ViewTickets from '../../components/Attendee/ViewTickets';
const ViewTicketsPage = () => {
return (
<div>
<ViewTickets />
</div>
);
};
export default ViewTicketsPage;
Navigation and Authorization
Ensure you have navigation links set up and proper authorization checks
for each role to access specific pages.
Styles
Make sure Tailwind CSS is properly configured in your globals.css
.
With these components and pages, you can build a comprehensive ticket management system frontend using Next.js and Tailwind CSS.
To implement a notification system in the frontend using Next.js and Tailwind CSS, you'll need components for displaying in-app notifications and mechanisms to trigger email notifications (typically handled by the backend). Below is an example setup for in-app notifications and placeholders for triggering email notifications.
Project Structure
event-management-frontend
├── components
│ ├── Notifications
│ │ ├── InAppNotifications.js
├── pages
│ ├── notifications
│ │ ├── index.js
└── styles
└── globals.css
1. In-App Notifications
components/Notifications/InAppNotifications.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const InAppNotifications = () => {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const fetchNotifications = async () => {
const response = await axios.get('/api/notifications');
setNotifications(response.data);
};
fetchNotifications();
}, []);
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Notifications</h1>
<div>
{notifications.map((notification) => (
<div key={notification.id} className="mb-4 p-4 border rounded bg-gray-100">
<h2 className="text-2xl font-bold">{notification.title}</h2>
<p>{notification.message}</p>
<p className="text-sm text-gray-500">{new Date(notification.createdAt).toLocaleString()}</p>
</div>
))}
</div>
</div>
);
};
export default InAppNotifications;
pages/notifications/index.js
import InAppNotifications from '../../components/Notifications/InAppNotifications';
const NotificationsPage = () => {
return (
<div>
<InAppNotifications />
</div>
);
};
export default NotificationsPage;
Triggering Email Notifications
Email notifications are typically handled by the backend. Here's an example of how you might trigger email notifications from the frontend by making API calls to your backend.
Example: Triggering Email Notification on Ticket Purchase
components/Attendee/PurchaseTickets.js
(Modified)
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
const PurchaseTickets = () => {
const [events, setEvents] = useState([]);
const [selectedEvent, setSelectedEvent] = useState('');
const [tickets, setTickets] = useState([]);
const [selectedTicket, setSelectedTicket] = useState('');
const [quantity, setQuantity] = useState(1);
const router = useRouter();
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/events');
setEvents(response.data);
};
fetchEvents();
}, []);
useEffect(() => {
if (selectedEvent) {
const fetchTickets = async () => {
const response = await axios.get(`/api/events/${selectedEvent}/tickets`);
setTickets(response.data);
};
fetchTickets();
}
}, [selectedEvent]);
const handlePurchase = async (e) => {
e.preventDefault();
try {
await axios.post(`/api/tickets/purchase`, { eventId: selectedEvent, ticketId: selectedTicket, quantity });
alert('Successfully purchased tickets');
// Trigger email notification
await axios.post(`/api/notifications/email`, {
type: 'TICKET_PURCHASE',
userId: 'current_user_id', // Replace with actual user ID
eventId: selectedEvent,
ticketId: selectedTicket,
});
router.push('/attendee/view-tickets');
} catch (err) {
console.error(err);
alert('Error purchasing tickets');
}
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Purchase Tickets</h1>
<form onSubmit={handlePurchase}>
<div className="mb-4">
<label className="block text-gray-700">Select Event</label>
<select
value={selectedEvent}
onChange={(e) => setSelectedEvent(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Event</option>
{events.map(event => (
<option key={event.id} value={event.id}>{event.title}</option>
))}
</select>
</div>
{selectedEvent && (
<>
<div className="mb-4">
<label className="block text-gray-700">Select Ticket</label>
<select
value={selectedTicket}
onChange={(e) => setSelectedTicket(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Ticket</option>
{tickets.map(ticket => (
<option key={ticket.id} value={ticket.id}>{ticket.ticketType} - ${ticket.price}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="block text-gray-700">Quantity</label>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
className="w-full px-3 py-2 border rounded"
min="1"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Purchase Tickets
</button>
</>
)}
</form>
</div>
);
};
export default PurchaseTickets;
Summary
-
In-App Notifications: The
InAppNotifications
component displays real-time notifications. The notifications are fetched from the backend using an API call. - Email Notifications: Email notifications are triggered by making an API call to the backend whenever an important action occurs (e.g., ticket purchase).
Make sure to implement the necessary backend logic to handle these API endpoints and send emails.
To integrate a payment gateway in your frontend for ticket purchases, you can use popular options like Stripe. Below is an example of how to integrate Stripe into your Next.js application for handling ticket purchases.
Project Structure
event-management-frontend
├── components
│ ├── Attendee
│ │ ├── PurchaseTickets.js
│ │ ├── CheckoutForm.js
├── pages
│ ├── attendee
│ │ ├── purchase-tickets.js
│ │ ├── success.js
└── styles
└── globals.css
1. Setting Up Stripe
First, install the necessary Stripe packages:
npm install @stripe/stripe-js @stripe/react-stripe-js
2. Checkout Form Component
components/Attendee/CheckoutForm.js
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useState } from 'react';
const CheckoutForm = ({ eventId, ticketId, quantity }) => {
const stripe = useStripe();
const elements = useElements();
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (event) => {
event.preventDefault();
setLoading(true);
if (!stripe || !elements) {
return;
}
const cardElement = elements.getElement(CardElement);
try {
const { data: clientSecret } = await axios.post('/api/create-payment-intent', {
eventId,
ticketId,
quantity,
});
const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: 'Test User',
},
},
});
if (stripeError) {
setError(`Payment failed: ${stripeError.message}`);
setLoading(false);
return;
}
if (paymentIntent.status === 'succeeded') {
alert('Payment successful');
router.push('/attendee/success');
}
} catch (error) {
setError(`Payment failed: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="w-full max-w-lg mx-auto p-6">
<CardElement className="border p-4 rounded mb-4" />
{error && <div className="text-red-500 mb-4">{error}</div>}
<button
type="submit"
disabled={!stripe || loading}
className={`w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700 ${loading && 'opacity-50 cursor-not-allowed'}`}
>
{loading ? 'Processing...' : 'Pay Now'}
</button>
</form>
);
};
export default CheckoutForm;
3. Purchase Tickets Component
components/Attendee/PurchaseTickets.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import CheckoutForm from './CheckoutForm';
const stripePromise = loadStripe('your-publishable-key-from-stripe');
const PurchaseTickets = () => {
const [events, setEvents] = useState([]);
const [selectedEvent, setSelectedEvent] = useState('');
const [tickets, setTickets] = useState([]);
const [selectedTicket, setSelectedTicket] = useState('');
const [quantity, setQuantity] = useState(1);
const [checkout, setCheckout] = useState(false);
const router = useRouter();
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/events');
setEvents(response.data);
};
fetchEvents();
}, []);
useEffect(() => {
if (selectedEvent) {
const fetchTickets = async () => {
const response = await axios.get(`/api/events/${selectedEvent}/tickets`);
setTickets(response.data);
};
fetchTickets();
}
}, [selectedEvent]);
const handlePurchase = async (e) => {
e.preventDefault();
setCheckout(true);
};
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Purchase Tickets</h1>
<form onSubmit={handlePurchase}>
<div className="mb-4">
<label className="block text-gray-700">Select Event</label>
<select
value={selectedEvent}
onChange={(e) => setSelectedEvent(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Event</option>
{events.map(event => (
<option key={event.id} value={event.id}>{event.title}</option>
))}
</select>
</div>
{selectedEvent && (
<>
<div className="mb-4">
<label className="block text-gray-700">Select Ticket</label>
<select
value={selectedTicket}
onChange={(e) => setSelectedTicket(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
>
<option value="">Select Ticket</option>
{tickets.map(ticket => (
<option key={ticket.id} value={ticket.id}>{ticket.ticketType} - ${ticket.price}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="block text-gray-700">Quantity</label>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
className="w-full px-3 py-2 border rounded"
min="1"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-700"
>
Proceed to Checkout
</button>
</>
)}
</form>
{checkout && (
<Elements stripe={stripePromise}>
<CheckoutForm eventId={selectedEvent} ticketId={selectedTicket} quantity={quantity} />
</Elements>
)}
</div>
);
};
export default PurchaseTickets;
pages/attendee/purchase-tickets.js
import PurchaseTickets from '../../components/Attendee/PurchaseTickets';
const PurchaseTicketsPage = () => {
return (
<div>
<PurchaseTickets />
</div>
);
};
export default PurchaseTicketsPage;
4. Success Page
pages/attendee/success.js
const SuccessPage = () => {
return (
<div className="max-w-4xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Payment Successful</h1>
<p className="mb-4">Thank you for your purchase. You will receive an email confirmation shortly.</p>
<a href="/attendee/view-tickets" className="text-blue-500 hover:underline">View Your Tickets</a>
</div>
);
};
export default SuccessPage;
Summary
-
Checkout Form: The
CheckoutForm
component handles the Stripe payment process. -
Purchase Tickets: The
PurchaseTickets
component handles the ticket selection and triggers the checkout process. - Success Page: A simple page to display after successful payment.
This setup integrates Stripe into your Next.js application for handling payments. Ensure you have the necessary backend setup to create payment intents and handle webhook events from Stripe for a complete integration.
To create dashboards for Admin, Organizer, and Attendee roles in your Next.js application using Tailwind CSS, you will need to set up the structure and components for each dashboard. Each dashboard will have different views and functionalities according to the role.
Project Structure
event-management-frontend
├── components
│ ├── Admin
│ │ ├── AdminDashboard.js
│ ├── Organizer
│ │ ├── OrganizerDashboard.js
│ ├── Attendee
│ │ ├── AttendeeDashboard.js
├── pages
│ ├── admin
│ │ ├── dashboard.js
│ ├── organizer
│ │ ├── dashboard.js
│ ├── attendee
│ │ ├── dashboard.js
└── styles
└── globals.css
1. Admin Dashboard
components/Admin/AdminDashboard.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const AdminDashboard = () => {
const [metrics, setMetrics] = useState({});
const [users, setUsers] = useState([]);
const [events, setEvents] = useState([]);
useEffect(() => {
const fetchMetrics = async () => {
const response = await axios.get('/api/admin/metrics');
setMetrics(response.data);
};
const fetchUsers = async () => {
const response = await axios.get('/api/admin/users');
setUsers(response.data);
};
const fetchEvents = async () => {
const response = await axios.get('/api/admin/events');
setEvents(response.data);
};
fetchMetrics();
fetchUsers();
fetchEvents();
}, []);
return (
<div className="max-w-7xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Admin Dashboard</h1>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">System Metrics</h2>
<div className="grid grid-cols-3 gap-6">
<div className="bg-white p-6 rounded shadow">
<h3 className="text-xl font-bold">Total Users</h3>
<p className="text-3xl">{metrics.totalUsers}</p>
</div>
<div className="bg-white p-6 rounded shadow">
<h3 className="text-xl font-bold">Total Events</h3>
<p className="text-3xl">{metrics.totalEvents}</p>
</div>
<div className="bg-white p-6 rounded shadow">
<h3 className="text-xl font-bold">Total Tickets Sold</h3>
<p className="text-3xl">{metrics.totalTicketsSold}</p>
</div>
</div>
</div>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">User Management</h2>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="border px-4 py-2">ID</th>
<th className="border px-4 py-2">Name</th>
<th className="border px-4 py-2">Email</th>
<th className="border px-4 py-2">Role</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td className="border px-4 py-2">{user.id}</td>
<td className="border px-4 py-2">{user.name}</td>
<td className="border px-4 py-2">{user.email}</td>
<td className="border px-4 py-2">{user.role}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Event Management</h2>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="border px-4 py-2">ID</th>
<th className="border px-4 py-2">Title</th>
<th className="border px-4 py-2">Organizer</th>
<th className="border px-4 py-2">Date</th>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id}>
<td className="border px-4 py-2">{event.id}</td>
<td className="border px-4 py-2">{event.title}</td>
<td className="border px-4 py-2">{event.organizer}</td>
<td className="border px-4 py-2">{new Date(event.date).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default AdminDashboard;
pages/admin/dashboard.js
import AdminDashboard from '../../components/Admin/AdminDashboard';
const AdminDashboardPage = () => {
return (
<div>
<AdminDashboard />
</div>
);
};
export default AdminDashboardPage;
2. Organizer Dashboard
components/Organizer/OrganizerDashboard.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const OrganizerDashboard = () => {
const [events, setEvents] = useState([]);
const [ticketSales, setTicketSales] = useState([]);
useEffect(() => {
const fetchEvents = async () => {
const response = await axios.get('/api/organizer/events');
setEvents(response.data);
};
const fetchTicketSales = async () => {
const response = await axios.get('/api/organizer/ticket-sales');
setTicketSales(response.data);
};
fetchEvents();
fetchTicketSales();
}, []);
return (
<div className="max-w-7xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Organizer Dashboard</h1>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Your Events</h2>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="border px-4 py-2">ID</th>
<th className="border px-4 py-2">Title</th>
<th className="border px-4 py-2">Date</th>
<th className="border px-4 py-2">Venue</th>
<th className="border px-4 py-2">Tickets Sold</th>
</tr>
</thead>
<tbody>
{events.map(event => (
<tr key={event.id}>
<td className="border px-4 py-2">{event.id}</td>
<td className="border px-4 py-2">{event.title}</td>
<td className="border px-4 py-2">{new Date(event.date).toLocaleDateString()}</td>
<td className="border px-4 py-2">{event.venue}</td>
<td className="border px-4 py-2">{ticketSales[event.id]}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default OrganizerDashboard;
pages/organizer/dashboard.js
import OrganizerDashboard from '../../components/Organizer/OrganizerDashboard';
const OrganizerDashboardPage = () => {
return (
<div>
<OrganizerDashboard />
</div>
);
};
export default OrganizerDashboardPage;
3. Attendee Dashboard
components/Attendee/AttendeeDashboard.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const AttendeeDashboard = () => {
const [registeredEvents, setRegisteredEvents] = useState([]);
const [purchasedTickets, setPurchasedTickets] = useState([]);
useEffect(() => {
const fetchRegisteredEvents = async () => {
const response = await axios.get('/api/attendee/registered-events');
setRegisteredEvents(response.data);
};
const fetchPurchasedTickets = async () => {
const response = await axios.get('/api/attendee/purchased-tickets');
setPurchasedTickets(response.data);
};
fetchRegisteredEvents();
fetchPurchasedTickets();
}, []);
return (
<div className="max-w-7xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Attendee Dashboard</h1>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Registered Events</h2>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="border
px-4 py-2">ID</th>
<th className="border px-4 py-2">Title</th>
<th className="border px-4 py-2">Date</th>
<th className="border px-4 py-2">Venue</th>
</tr>
</thead>
<tbody>
{registeredEvents.map(event => (
<tr key={event.id}>
<td className="border px-4 py-2">{event.id}</td>
<td className="border px-4 py-2">{event.title}</td>
<td className="border px-4 py-2">{new Date(event.date).toLocaleDateString()}</td>
<td className="border px-4 py-2">{event.venue}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Purchased Tickets</h2>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="border px-4 py-2">Ticket ID</th>
<th className="border px-4 py-2">Event</th>
<th className="border px-4 py-2">Quantity</th>
<th className="border px-4 py-2">Price</th>
</tr>
</thead>
<tbody>
{purchasedTickets.map(ticket => (
<tr key={ticket.id}>
<td className="border px-4 py-2">{ticket.id}</td>
<td className="border px-4 py-2">{ticket.eventTitle}</td>
<td className="border px-4 py-2">{ticket.quantity}</td>
<td className="border px-4 py-2">${ticket.price}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default AttendeeDashboard;
pages/attendee/dashboard.js
import AttendeeDashboard from '../../components/Attendee/AttendeeDashboard';
const AttendeeDashboardPage = () => {
return (
<div>
<AttendeeDashboard />
</div>
);
};
export default AttendeeDashboardPage;
Summary
- Admin Dashboard: Displays system metrics, user management, and event management.
- Organizer Dashboard: Shows organizer's events and ticket sales.
- Attendee Dashboard: Shows registered events and purchased tickets.
Each dashboard fetches data from the backend using Axios and displays it in tables. Tailwind CSS is used for styling. Make sure your backend APIs are set up to provide the necessary data for each dashboard.
To implement analytics for events and users in your Next.js application, you can use charting libraries such as Chart.js or Recharts. For this example, we'll use Recharts, a popular charting library for React, to display event performance and user engagement insights.
Project Structure
event-management-frontend
├── components
│ ├── Analytics
│ │ ├── EventAnalytics.js
│ │ ├── UserAnalytics.js
├── pages
│ ├── analytics
│ │ ├── event.js
│ │ ├── user.js
└── styles
└── globals.css
1. Install Recharts
First, install Recharts:
npm install recharts
2. Event Analytics Component
components/Analytics/EventAnalytics.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts';
const EventAnalytics = () => {
const [ticketSales, setTicketSales] = useState([]);
const [attendeeDemographics, setAttendeeDemographics] = useState([]);
useEffect(() => {
const fetchTicketSales = async () => {
const response = await axios.get('/api/analytics/ticket-sales');
setTicketSales(response.data);
};
const fetchAttendeeDemographics = async () => {
const response = await axios.get('/api/analytics/attendee-demographics');
setAttendeeDemographics(response.data);
};
fetchTicketSales();
fetchAttendeeDemographics();
}, []);
return (
<div className="max-w-7xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">Event Analytics</h1>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Ticket Sales</h2>
<ResponsiveContainer width="100%" height={400}>
<BarChart data={ticketSales}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="event" />
<YAxis />
<Tooltip />
<Bar dataKey="sales" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">Attendee Demographics</h2>
<ResponsiveContainer width="100%" height={400}>
<BarChart data={attendeeDemographics}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="ageGroup" />
<YAxis />
<Tooltip />
<Bar dataKey="attendees" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
);
};
export default EventAnalytics;
3. User Analytics Component
components/Analytics/UserAnalytics.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts';
const UserAnalytics = () => {
const [userBehavior, setUserBehavior] = useState([]);
const [userEngagement, setUserEngagement] = useState([]);
useEffect(() => {
const fetchUserBehavior = async () => {
const response = await axios.get('/api/analytics/user-behavior');
setUserBehavior(response.data);
};
const fetchUserEngagement = async () => {
const response = await axios.get('/api/analytics/user-engagement');
setUserEngagement(response.data);
};
fetchUserBehavior();
fetchUserEngagement();
}, []);
return (
<div className="max-w-7xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">User Analytics</h1>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">User Behavior</h2>
<ResponsiveContainer width="100%" height={400}>
<LineChart data={userBehavior}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="activeUsers" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</div>
<div className="mb-6">
<h2 className="text-2xl font-bold mb-4">User Engagement</h2>
<ResponsiveContainer width="100%" height={400}>
<LineChart data={userEngagement}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="engagement" stroke="#82ca9d" />
</LineChart>
</ResponsiveContainer>
</div>
</div>
);
};
export default UserAnalytics;
4. Analytics Pages
pages/analytics/event.js
import EventAnalytics from '../../components/Analytics/EventAnalytics';
const EventAnalyticsPage = () => {
return (
<div>
<EventAnalytics />
</div>
);
};
export default EventAnalyticsPage;
pages/analytics/user.js
import UserAnalytics from '../../components/Analytics/UserAnalytics';
const UserAnalyticsPage = () => {
return (
<div>
<UserAnalytics />
</div>
);
};
export default UserAnalyticsPage;
Summary
- Event Analytics: Displays insights into event performance using bar charts to show ticket sales and attendee demographics.
- User Analytics: Shows insights into user behavior and engagement using line charts.
Each component fetches data from the backend using Axios and displays it using Recharts. Tailwind CSS is used for styling. Ensure your backend APIs are set up to provide the necessary data for each analytic view.
Here is a detailed setup for the backend of your full-stack event management system using NestJS, Prisma, PostgreSQL, and GraphQL. Let's start with the basic setup for user authentication.
Backend Setup with NestJS
-
Initialize a NestJS Project
npm i -g @nestjs/cli nest new event-management-backend
-
Install Required Dependencies
cd event-management-backend npm install @nestjs/graphql @nestjs/apollo apollo-server-express graphql npm install @prisma/client npm install bcryptjs npm install @nestjs/jwt passport-jwt @nestjs/passport passport npm install class-validator class-transformer
-
Set Up Prisma
npx prisma init
Update your
prisma/schema.prisma
file:
datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) email String @unique password String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
Update your
.env
file with your PostgreSQL connection string:
DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
Run the Prisma migration:
npx prisma migrate dev --name init
-
Configure Prisma in NestJS
Create
prisma.module.ts
:
import { Module } from '@nestjs/common'; import { PrismaService } from './prisma.service'; @Module({ providers: [PrismaService], exports: [PrismaService], }) export class PrismaModule {}
Create
prisma.service.ts
:
import { Injectable, OnModuleInit, INestApplication } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { async onModuleInit() { await this.$connect(); } async enableShutdownHooks(app: INestApplication) { this.$on('beforeExit', async () => { await app.close(); }); } }
-
Setup Authentication Module
Create
auth.module.ts
:
import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthResolver } from './auth.resolver'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { PrismaModule } from '../prisma/prisma.module'; import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ PrismaModule, PassportModule, JwtModule.register({ secret: 'your_secret_key', // use a better secret in production signOptions: { expiresIn: '60m' }, }), ], providers: [AuthService, AuthResolver, JwtStrategy], }) export class AuthModule {}
Create
auth.service.ts
:
import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { PrismaService } from '../prisma/prisma.service'; import * as bcrypt from 'bcryptjs'; @Injectable() export class AuthService { constructor( private readonly prisma: PrismaService, private readonly jwtService: JwtService, ) {} async validateUser(email: string, password: string): Promise<any> { const user = await this.prisma.user.findUnique({ where: { email } }); if (user && (await bcrypt.compare(password, user.password))) { const { password, ...result } = user; return result; } return null; } async login(user: any) { const payload = { email: user.email, sub: user.id }; return { access_token: this.jwtService.sign(payload), }; } async signup(email: string, password: string) { const hashedPassword = await bcrypt.hash(password, 10); const user = await this.prisma.user.create({ data: { email, password: hashedPassword, }, }); return user; } }
Create
auth.resolver.ts
:
import { Resolver, Mutation, Args } from '@nestjs/graphql'; import { AuthService } from './auth.service'; import { AuthResponse } from './dto/auth-response'; import { UserInput } from './dto/user-input'; @Resolver() export class AuthResolver { constructor(private readonly authService: AuthService) {} @Mutation(() => AuthResponse) async login(@Args('userInput') userInput: UserInput) { const user = await this.authService.validateUser(userInput.email, userInput.password); if (!user) { throw new Error('Invalid credentials'); } return this.authService.login(user); } @Mutation(() => AuthResponse) async signup(@Args('userInput') userInput: UserInput) { const user = await this.authService.signup(userInput.email, userInput.password); return this.authService.login(user); } }
Create DTOs (
dto/auth-response.ts
anddto/user-input.ts
):
// auth-response.ts import { Field, ObjectType } from '@nestjs/graphql'; @ObjectType() export class AuthResponse { @Field() access_token: string; }
```typescript
// user-input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class UserInput {
@Field()
email: string;
@Field()
password: string;
}
```
-
Setup JWT Strategy
Create
jwt.strategy.ts
:
import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { JwtPayload } from './jwt-payload.interface'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private readonly prisma: PrismaService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'your_secret_key', // use a better secret in production }); } async validate(payload: JwtPayload) { const user = await this.prisma.user.findUnique({ where: { id: payload.sub } }); if (!user) { throw new UnauthorizedException(); } return user; } }
Create
jwt-payload.interface.ts
:
export interface JwtPayload { email: string; sub: number; }
-
Add GraphQL Module
Configure
graphql.module.ts
:
import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), sortSchema: true, }), ], }) export class GraphqlModule {}
-
Integrate All Modules in App Module
Update
app.module.ts
:
import { Module } from '@nestjs/common'; import { AuthModule } from './auth/auth.module'; import { PrismaModule } from './prisma/prisma.module'; import { GraphqlModule } from './graphql/graphql.module'; @Module({ imports: [ AuthModule, PrismaModule, GraphqlModule, ], }) export class AppModule {}
Running the Backend
-
Start the NestJS Server
npm run start:dev
-
Test the GraphQL API
Access the GraphQL playground at
http://localhost:3000/graphql
and test the authentication queries and mutations:
mutation { signup(userInput: {email: "test@example.com", password: "password"}) { access_token } } mutation { login(userInput: {email: "test@example.com", password: "password"}) { access_token } }
This setup provides the foundational authentication system. You can now build upon this by adding other features like event management, ticket management, notifications, and more.
Sure, let's extend the backend to support user roles and permissions. We'll add role-based access control to our authentication system. Here’s how we can achieve that:
- Update Prisma Schema for Roles
Add roles and related fields to prisma/schema.prisma
:
model User {
id Int @id @default(autoincrement())
email String @unique
password String
role Role @default(ATTENDEE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
ADMIN
ORGANIZER
ATTENDEE
}
Run the Prisma migration:
npx prisma migrate dev --name add-roles
- Update User Registration to Assign Roles
Update auth.service.ts
to assign roles during signup:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcryptjs';
import { Role } from '@prisma/client';
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.prisma.user.findUnique({ where: { email } });
if (user && (await bcrypt.compare(password, user.password))) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { email: user.email, sub: user.id, role: user.role };
return {
access_token: this.jwtService.sign(payload),
};
}
async signup(email: string, password: string, role: Role) {
const hashedPassword = await bcrypt.hash(password, 10);
const user = await this.prisma.user.create({
data: {
email,
password: hashedPassword,
role,
},
});
return user;
}
}
Update auth.resolver.ts
to accept role during signup:
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { AuthService } from './auth.service';
import { AuthResponse } from './dto/auth-response';
import { UserInput } from './dto/user-input';
import { Role } from '@prisma/client';
@Resolver()
export class AuthResolver {
constructor(private readonly authService: AuthService) {}
@Mutation(() => AuthResponse)
async login(@Args('userInput') userInput: UserInput) {
const user = await this.authService.validateUser(userInput.email, userInput.password);
if (!user) {
throw new Error('Invalid credentials');
}
return this.authService.login(user);
}
@Mutation(() => AuthResponse)
async signup(@Args('userInput') userInput: UserInput, @Args('role') role: Role) {
const user = await this.authService.signup(userInput.email, userInput.password, role);
return this.authService.login(user);
}
}
- Create Guard for Role-Based Authorization
Create roles.guard.ts
:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '@prisma/client';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.role?.includes(role));
}
}
Create roles.decorator.ts
:
import { SetMetadata } from '@nestjs/common';
import { Role } from '@prisma/client';
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
- Protect Routes with Role-Based Guards
Update event-related resolvers and services to use the role-based guard:
Example for event management (events.module.ts
, events.service.ts
, events.resolver.ts
):
Create events.module.ts
:
import { Module } from '@nestjs/common';
import { EventsService } from './events.service';
import { EventsResolver } from './events.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [EventsService, EventsResolver],
})
export class EventsModule {}
Create events.service.ts
:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class EventsService {
constructor(private readonly prisma: PrismaService) {}
// Add methods for event creation, deletion, etc.
}
Create events.resolver.ts
:
import { Resolver, Mutation, Args, Query } from '@nestjs/graphql';
import { EventsService } from './events.service';
import { UseGuards } from '@nestjs/common';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { Role } from '@prisma/client';
@Resolver()
export class EventsResolver {
constructor(private readonly eventsService: EventsService) {}
@Mutation(() => Boolean)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async createEvent(
@Args('title') title: string,
@Args('description') description: string,
// Add other event fields
) {
// Call service method to create event
return true;
}
@Mutation(() => Boolean)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async deleteEvent(@Args('id') id: number) {
// Call service method to delete event
return true;
}
@Query(() => [Event])
async events() {
// Call service method to get events
return [];
}
}
- Integrate Event Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Role-Based Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the role-based mutations and queries:
mutation {
signup(userInput: {email: "organizer@example.com", password: "password"}, role: ORGANIZER) {
access_token
}
}
mutation {
login(userInput: {email: "organizer@example.com", password: "password"}) {
access_token
}
}
mutation {
createEvent(title: "Event Title", description: "Event Description") {
success
}
}
This setup provides a robust foundation for role-based access control in your event management system. You can now add more roles and permissions as needed, ensuring each user has access to only the appropriate features and functionalities.
Let's extend our backend to include event management functionality. We'll implement the ability for organizers to create, edit, and delete events, and for users to view and search for events. We'll start by updating the Prisma schema and then proceed to the NestJS service and resolver layers.
Update Prisma Schema for Events
Update prisma/schema.prisma
to include the Event
model:
model Event {
id Int @id @default(autoincrement())
title String
description String
date DateTime
time String
venue String
tickets Ticket[]
organizer User @relation(fields: [organizerId], references: [id])
organizerId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Ticket {
id Int @id @default(autoincrement())
type String
price Float
quantity Int
event Event @relation(fields: [eventId], references: [id])
eventId Int
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
role Role @default(ATTENDEE)
events Event[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
ADMIN
ORGANIZER
ATTENDEE
}
Run the Prisma migration:
npx prisma migrate dev --name add-events
Create Event Module in NestJS
- Create Event DTOs
Create dto/create-event.input.ts
:
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateEventInput {
@Field()
title: string;
@Field()
description: string;
@Field()
date: Date;
@Field()
time: string;
@Field()
venue: string;
@Field(() => [CreateTicketInput])
tickets: CreateTicketInput[];
}
Create dto/create-ticket.input.ts
:
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateTicketInput {
@Field()
type: string;
@Field()
price: number;
@Field()
quantity: number;
}
Create dto/update-event.input.ts
:
import { InputType, Field, PartialType } from '@nestjs/graphql';
import { CreateEventInput } from './create-event.input';
@InputType()
export class UpdateEventInput extends PartialType(CreateEventInput) {
@Field()
id: number;
}
- Create Event Service
Create events.service.ts
:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateEventInput } from './dto/create-event.input';
import { UpdateEventInput } from './dto/update-event.input';
@Injectable()
export class EventsService {
constructor(private readonly prisma: PrismaService) {}
async createEvent(createEventInput: CreateEventInput, organizerId: number) {
const { tickets, ...eventData } = createEventInput;
const event = await this.prisma.event.create({
data: {
...eventData,
organizer: { connect: { id: organizerId } },
tickets: {
create: tickets,
},
},
});
return event;
}
async updateEvent(updateEventInput: UpdateEventInput) {
const { id, tickets, ...eventData } = updateEventInput;
const event = await this.prisma.event.update({
where: { id },
data: {
...eventData,
tickets: {
deleteMany: { eventId: id },
create: tickets,
},
},
});
return event;
}
async deleteEvent(id: number) {
await this.prisma.event.delete({ where: { id } });
return true;
}
async getEvent(id: number) {
return this.prisma.event.findUnique({ where: { id } });
}
async getEvents() {
return this.prisma.event.findMany();
}
async searchEvents(searchTerm: string) {
return this.prisma.event.findMany({
where: {
OR: [
{ title: { contains: searchTerm, mode: 'insensitive' } },
{ description: { contains: searchTerm, mode: 'insensitive' } },
{ venue: { contains: searchTerm, mode: 'insensitive' } },
],
},
});
}
}
- Create Event Resolver
Create events.resolver.ts
:
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { EventsService } from './events.service';
import { CreateEventInput } from './dto/create-event.input';
import { UpdateEventInput } from './dto/update-event.input';
import { Event } from '@prisma/client';
import { UseGuards } from '@nestjs/common';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { Role } from '@prisma/client';
@Resolver('Event')
export class EventsResolver {
constructor(private readonly eventsService: EventsService) {}
@Mutation(() => Event)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async createEvent(
@Args('createEventInput') createEventInput: CreateEventInput,
@Args('organizerId') organizerId: number,
) {
return this.eventsService.createEvent(createEventInput, organizerId);
}
@Mutation(() => Event)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async updateEvent(@Args('updateEventInput') updateEventInput: UpdateEventInput) {
return this.eventsService.updateEvent(updateEventInput);
}
@Mutation(() => Boolean)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async deleteEvent(@Args('id') id: number) {
return this.eventsService.deleteEvent(id);
}
@Query(() => Event)
async event(@Args('id') id: number) {
return this.eventsService.getEvent(id);
}
@Query(() => [Event])
async events() {
return this.eventsService.getEvents();
}
@Query(() => [Event])
async searchEvents(@Args('searchTerm') searchTerm: string) {
return this.eventsService.searchEvents(searchTerm);
}
}
- Create Event Module
Create events.module.ts
:
import { Module } from '@nestjs/common';
import { EventsService } from './events.service';
import { EventsResolver } from './events.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [EventsService, EventsResolver],
})
export class EventsModule {}
- Integrate Event Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Event Management Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the event management queries and mutations:
mutation {
createEvent(
createEventInput: {
title: "Sample Event"
description: "This is a sample event."
date: "2024-08-01T00:00:00.000Z"
time: "18:00"
venue: "Sample Venue"
tickets: [
{ type: "General", price: 100.0, quantity: 100 }
{ type: "VIP", price: 200.0, quantity: 50 }
]
}
organizerId: 1
) {
id
title
description
date
time
venue
tickets {
type
price
quantity
}
}
}
mutation {
updateEvent(
updateEventInput: {
id: 1
title: "Updated Event Title"
description: "Updated description."
date: "2024-08-02T00:00:00.000Z"
time: "19:00"
venue: "Updated Venue"
tickets: [
{ type: "General", price: 120.0, quantity: 100 }
{ type: "VIP", price: 250.0, quantity: 50 }
]
}
) {
id
title
description
date
time
venue
tickets {
type
price
quantity
}
}
}
mutation {
deleteEvent(id: 1)
}
query {
event(id: 1) {
id
title
description
date
time
venue
tickets {
type
price
quantity
}
}
}
query {
events {
id
title
description
date
time
venue
}
}
query {
searchEvents(searchTerm: "Sample") {
id
title
description
date
time
venue
}
}
This setup provides the necessary backend functionality for event management, allowing organizers to create, update, and delete events, and enabling users to view and search for events. You can further extend these functionalities as needed.
To add ticket management to our event management system, we'll extend our backend to include the functionality for creating tickets, purchasing tickets, and viewing purchased tickets. We'll update the Prisma schema to track ticket purchases, and then add the necessary service and resolver methods in NestJS.
Update Prisma Schema for Ticket Purchases
Update prisma/schema.prisma
to include the TicketPurchase
model:
model TicketPurchase {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
ticket Ticket @relation(fields: [ticketId], references: [id])
ticketId Int
quantity Int
totalPrice Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Ticket {
id Int @id @default(autoincrement())
type String
price Float
quantity Int
event Event @relation(fields: [eventId], references: [id])
eventId Int
purchases TicketPurchase[]
}
model Event {
id Int @id @default(autoincrement())
title String
description String
date DateTime
time String
venue String
tickets Ticket[]
organizer User @relation(fields: [organizerId], references: [id])
organizerId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
role Role @default(ATTENDEE)
events Event[]
ticketPurchases TicketPurchase[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
ADMIN
ORGANIZER
ATTENDEE
}
Run the Prisma migration:
npx prisma migrate dev --name add-ticket-purchases
Create Ticket Management Service
Create tickets.service.ts
:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTicketInput } from './dto/create-ticket.input';
import { PurchaseTicketInput } from './dto/purchase-ticket.input';
@Injectable()
export class TicketsService {
constructor(private readonly prisma: PrismaService) {}
async createTicket(createTicketInput: CreateTicketInput, eventId: number) {
const ticket = await this.prisma.ticket.create({
data: {
...createTicketInput,
event: { connect: { id: eventId } },
},
});
return ticket;
}
async purchaseTicket(purchaseTicketInput: PurchaseTicketInput, userId: number) {
const { ticketId, quantity } = purchaseTicketInput;
const ticket = await this.prisma.ticket.findUnique({ where: { id: ticketId } });
if (!ticket || ticket.quantity < quantity) {
throw new Error('Insufficient ticket quantity');
}
const totalPrice = ticket.price * quantity;
const purchase = await this.prisma.ticketPurchase.create({
data: {
user: { connect: { id: userId } },
ticket: { connect: { id: ticketId } },
quantity,
totalPrice,
},
});
await this.prisma.ticket.update({
where: { id: ticketId },
data: {
quantity: ticket.quantity - quantity,
},
});
return purchase;
}
async getUserTickets(userId: number) {
return this.prisma.ticketPurchase.findMany({
where: { userId },
include: { ticket: true, event: true },
});
}
}
Create Ticket Management Resolvers
Create tickets.resolver.ts
:
import { Resolver, Mutation, Args, Query } from '@nestjs/graphql';
import { TicketsService } from './tickets.service';
import { CreateTicketInput } from './dto/create-ticket.input';
import { PurchaseTicketInput } from './dto/purchase-ticket.input';
import { Ticket, TicketPurchase } from '@prisma/client';
import { UseGuards } from '@nestjs/common';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { Role } from '@prisma/client';
@Resolver('Ticket')
export class TicketsResolver {
constructor(private readonly ticketsService: TicketsService) {}
@Mutation(() => Ticket)
@Roles(Role.ORGANIZER)
@UseGuards(RolesGuard)
async createTicket(
@Args('createTicketInput') createTicketInput: CreateTicketInput,
@Args('eventId') eventId: number,
) {
return this.ticketsService.createTicket(createTicketInput, eventId);
}
@Mutation(() => TicketPurchase)
@Roles(Role.ATTENDEE)
@UseGuards(RolesGuard)
async purchaseTicket(
@Args('purchaseTicketInput') purchaseTicketInput: PurchaseTicketInput,
@Args('userId') userId: number,
) {
return this.ticketsService.purchaseTicket(purchaseTicketInput, userId);
}
@Query(() => [TicketPurchase])
@Roles(Role.ATTENDEE)
@UseGuards(RolesGuard)
async userTickets(@Args('userId') userId: number) {
return this.ticketsService.getUserTickets(userId);
}
}
Create Ticket Management DTOs
Create dto/create-ticket.input.ts
:
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateTicketInput {
@Field()
type: string;
@Field()
price: number;
@Field()
quantity: number;
}
Create dto/purchase-ticket.input.ts
:
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class PurchaseTicketInput {
@Field()
ticketId: number;
@Field()
quantity: number;
}
Create Ticket Module
Create tickets.module.ts
:
import { Module } from '@nestjs/common';
import { TicketsService } from './tickets.service';
import { TicketsResolver } from './tickets.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [TicketsService, TicketsResolver],
})
export class TicketsModule {}
Integrate Ticket Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Ticket Management Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the ticket management queries and mutations:
mutation {
createTicket(
createTicketInput: {
type: "General"
price: 100.0
quantity: 100
}
eventId: 1
) {
id
type
price
quantity
}
}
mutation {
purchaseTicket(
purchaseTicketInput: {
ticketId: 1
quantity: 2
}
userId: 1
) {
id
quantity
totalPrice
ticket {
type
price
}
}
}
query {
userTickets(userId: 1) {
id
quantity
totalPrice
ticket {
type
price
}
createdAt
}
}
This setup provides the necessary backend functionality for ticket management, allowing organizers to create tickets, attendees to purchase tickets, and users to view their purchased tickets. You can further extend these functionalities as needed.
To implement a notification system in the backend, we'll need to integrate both email notifications and in-app notifications. We'll use a service like SendGrid or Nodemailer for email notifications and a real-time mechanism like WebSockets for in-app notifications.
Setting Up Email Notifications
- Install Nodemailer
npm install nodemailer
- Create Email Service
Create email.service.ts
:
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
@Injectable()
export class EmailService {
private transporter;
constructor() {
this.transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
}
async sendMail(to: string, subject: string, text: string) {
const mailOptions = {
from: process.env.EMAIL_USER,
to,
subject,
text,
};
await this.transporter.sendMail(mailOptions);
}
}
- Update Environment Variables
Add the following to your .env
file:
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-email-password
- Integrate Email Service in Ticket Service
Update tickets.service.ts
to send an email notification after purchasing a ticket:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTicketInput } from './dto/create-ticket.input';
import { PurchaseTicketInput } from './dto/purchase-ticket.input';
import { EmailService } from '../email/email.service';
@Injectable()
export class TicketsService {
constructor(
private readonly prisma: PrismaService,
private readonly emailService: EmailService,
) {}
async createTicket(createTicketInput: CreateTicketInput, eventId: number) {
const ticket = await this.prisma.ticket.create({
data: {
...createTicketInput,
event: { connect: { id: eventId } },
},
});
return ticket;
}
async purchaseTicket(purchaseTicketInput: PurchaseTicketInput, userId: number) {
const { ticketId, quantity } = purchaseTicketInput;
const ticket = await this.prisma.ticket.findUnique({ where: { id: ticketId } });
if (!ticket || ticket.quantity < quantity) {
throw new Error('Insufficient ticket quantity');
}
const totalPrice = ticket.price * quantity;
const purchase = await this.prisma.ticketPurchase.create({
data: {
user: { connect: { id: userId } },
ticket: { connect: { id: ticketId } },
quantity,
totalPrice,
},
});
await this.prisma.ticket.update({
where: { id: ticketId },
data: {
quantity: ticket.quantity - quantity,
},
});
const user = await this.prisma.user.findUnique({ where: { id: userId } });
// Send email notification
await this.emailService.sendMail(
user.email,
'Ticket Purchase Confirmation',
`You have successfully purchased ${quantity} tickets for ${ticket.type} at ${ticket.event.title}. Total price: ${totalPrice}`,
);
return purchase;
}
async getUserTickets(userId: number) {
return this.prisma.ticketPurchase.findMany({
where: { userId },
include: { ticket: true, event: true },
});
}
}
- Create Email Module
Create email.module.ts
:
import { Module } from '@nestjs/common';
import { EmailService } from './email.service';
@Module({
providers: [EmailService],
exports: [EmailService],
})
export class EmailModule {}
- Integrate Email Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { EmailModule } from './email/email.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
EmailModule,
],
})
export class AppModule {}
Setting Up In-App Notifications
- Install WebSockets
npm install @nestjs/websockets @nestjs/platform-socket.io
- Create Notification Gateway
Create notification.gateway.ts
:
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer() server: Server;
async handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
async handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
@SubscribeMessage('message')
async onMessage(client: Socket, message: string) {
console.log(message);
}
async sendNotification(event: string, data: any) {
this.server.emit(event, data);
}
}
- Integrate Notification Gateway in Ticket Service
Update tickets.service.ts
to send an in-app notification after purchasing a ticket:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTicketInput } from './dto/create-ticket.input';
import { PurchaseTicketInput } from './dto/purchase-ticket.input';
import { EmailService } from '../email/email.service';
import { NotificationGateway } from '../notification/notification.gateway';
@Injectable()
export class TicketsService {
constructor(
private readonly prisma: PrismaService,
private readonly emailService: EmailService,
private readonly notificationGateway: NotificationGateway,
) {}
async createTicket(createTicketInput: CreateTicketInput, eventId: number) {
const ticket = await this.prisma.ticket.create({
data: {
...createTicketInput,
event: { connect: { id: eventId } },
},
});
return ticket;
}
async purchaseTicket(purchaseTicketInput: PurchaseTicketInput, userId: number) {
const { ticketId, quantity } = purchaseTicketInput;
const ticket = await this.prisma.ticket.findUnique({ where: { id: ticketId } });
if (!ticket || ticket.quantity < quantity) {
throw new Error('Insufficient ticket quantity');
}
const totalPrice = ticket.price * quantity;
const purchase = await this.prisma.ticketPurchase.create({
data: {
user: { connect: { id: userId } },
ticket: { connect: { id: ticketId } },
quantity,
totalPrice,
},
});
await this.prisma.ticket.update({
where: { id: ticketId },
data: {
quantity: ticket.quantity - quantity,
},
});
const user = await this.prisma.user.findUnique({ where: { id: userId } });
// Send email notification
await this.emailService.sendMail(
user.email,
'Ticket Purchase Confirmation',
`You have successfully purchased ${quantity} tickets for ${ticket.type} at ${ticket.event.title}. Total price: ${totalPrice}`,
);
// Send in-app notification
await this.notificationGateway.sendNotification('ticketPurchased', {
userId,
message: `You have successfully purchased ${quantity} tickets for ${ticket.type} at ${ticket.event.title}.`,
});
return purchase;
}
async getUserTickets(userId: number) {
return this.prisma.ticketPurchase.findMany({
where: { userId },
include: { ticket: true, event: true },
});
}
}
- Create Notification Module
Create notification.module.ts
:
import { Module } from '@nestjs/common';
import { NotificationGateway } from './notification.gateway';
@Module({
providers: [NotificationGateway],
exports: [NotificationGateway],
})
export class NotificationModule {}
- Integrate Notification Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { EmailModule } from './email/email.module';
import { NotificationModule } from './notification/notification.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
EmailModule,
NotificationModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Notification Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the ticket purchase mutation:
mutation {
purchaseTicket(
purchaseTicketInput: {
ticketId: 1
quantity: 2
}
userId: 1
) {
id
quantity
totalPrice
ticket {
type
price
}
}
}
Check the console output for WebSocket connection messages and email inbox for the notification email.
This setup provides the necessary backend functionality for both email and in-app notifications. You can further extend these functionalities as needed.
To integrate a payment gateway for ticket purchases, we'll use Stripe. Stripe provides robust APIs for handling payments, subscriptions, and more. We'll integrate Stripe into our NestJS backend.
Setting Up Stripe
- Install Stripe SDK
npm install stripe @nestjs/stripe
- Create Stripe Service
Create stripe.service.ts
:
import { Injectable } from '@nestjs/common';
import Stripe from 'stripe';
@Injectable()
export class StripeService {
private stripe: Stripe;
constructor() {
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2020-08-27',
});
}
async createPaymentIntent(amount: number, currency: string) {
return await this.stripe.paymentIntents.create({
amount,
currency,
});
}
async confirmPaymentIntent(paymentIntentId: string) {
return await this.stripe.paymentIntents.confirm(paymentIntentId);
}
}
- Update Environment Variables
Add the following to your .env
file:
STRIPE_SECRET_KEY=your-stripe-secret-key
STRIPE_PUBLIC_KEY=your-stripe-public-key
- Create Payment DTOs
Create dto/create-payment.dto.ts
:
export class CreatePaymentDto {
amount: number;
currency: string;
}
Create dto/confirm-payment.dto.ts
:
export class ConfirmPaymentDto {
paymentIntentId: string;
}
- Create Payment Resolver
Create payment.resolver.ts
:
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { StripeService } from './stripe.service';
import { CreatePaymentDto } from './dto/create-payment.dto';
import { ConfirmPaymentDto } from './dto/confirm-payment.dto';
@Resolver()
export class PaymentResolver {
constructor(private readonly stripeService: StripeService) {}
@Mutation(() => String)
async createPaymentIntent(@Args('createPaymentDto') createPaymentDto: CreatePaymentDto) {
const paymentIntent = await this.stripeService.createPaymentIntent(
createPaymentDto.amount,
createPaymentDto.currency,
);
return paymentIntent.client_secret;
}
@Mutation(() => String)
async confirmPaymentIntent(@Args('confirmPaymentDto') confirmPaymentDto: ConfirmPaymentDto) {
const paymentIntent = await this.stripeService.confirmPaymentIntent(confirmPaymentDto.paymentIntentId);
return paymentIntent.status;
}
}
- Create Payment Module
Create payment.module.ts
:
import { Module } from '@nestjs/common';
import { StripeService } from './stripe.service';
import { PaymentResolver } from './payment.resolver';
@Module({
providers: [StripeService, PaymentResolver],
})
export class PaymentModule {}
- Integrate Payment Module in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { EmailModule } from './email/email.module';
import { NotificationModule } from './notification/notification.module';
import { PaymentModule } from './payment/payment.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
EmailModule,
NotificationModule,
PaymentModule,
],
})
export class AppModule {}
Integrate Payment Flow in Ticket Service
Update tickets.service.ts
to create a payment intent before confirming the ticket purchase:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTicketInput } from './dto/create-ticket.input';
import { PurchaseTicketInput } from './dto/purchase-ticket.input';
import { EmailService } from '../email/email.service';
import { NotificationGateway } from '../notification/notification.gateway';
import { StripeService } from '../payment/stripe.service';
@Injectable()
export class TicketsService {
constructor(
private readonly prisma: PrismaService,
private readonly emailService: EmailService,
private readonly notificationGateway: NotificationGateway,
private readonly stripeService: StripeService,
) {}
async createTicket(createTicketInput: CreateTicketInput, eventId: number) {
const ticket = await this.prisma.ticket.create({
data: {
...createTicketInput,
event: { connect: { id: eventId } },
},
});
return ticket;
}
async purchaseTicket(purchaseTicketInput: PurchaseTicketInput, userId: number) {
const { ticketId, quantity, paymentIntentId } = purchaseTicketInput;
const ticket = await this.prisma.ticket.findUnique({ where: { id: ticketId } });
if (!ticket || ticket.quantity < quantity) {
throw new Error('Insufficient ticket quantity');
}
const totalPrice = ticket.price * quantity;
const paymentIntent = await this.stripeService.confirmPaymentIntent(paymentIntentId);
if (paymentIntent.status !== 'succeeded') {
throw new Error('Payment failed');
}
const purchase = await this.prisma.ticketPurchase.create({
data: {
user: { connect: { id: userId } },
ticket: { connect: { id: ticketId } },
quantity,
totalPrice,
},
});
await this.prisma.ticket.update({
where: { id: ticketId },
data: {
quantity: ticket.quantity - quantity,
},
});
const user = await this.prisma.user.findUnique({ where: { id: userId } });
// Send email notification
await this.emailService.sendMail(
user.email,
'Ticket Purchase Confirmation',
`You have successfully purchased ${quantity} tickets for ${ticket.type} at ${ticket.event.title}. Total price: ${totalPrice}`,
);
// Send in-app notification
await this.notificationGateway.sendNotification('ticketPurchased', {
userId,
message: `You have successfully purchased ${quantity} tickets for ${ticket.type} at ${ticket.event.title}.`,
});
return purchase;
}
async getUserTickets(userId: number) {
return this.prisma.ticketPurchase.findMany({
where: { userId },
include: { ticket: true, event: true },
});
}
}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Payment Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the payment flow:
-
Create a payment intent:
mutation { createPaymentIntent(createPaymentDto: { amount: 5000, currency: "usd" }) { client_secret } }
-
Confirm the payment intent:
mutation { confirmPaymentIntent(confirmPaymentDto: { paymentIntentId: "pi_1JHYLR2eZvKYlo2C8kIfmQqN" }) { status } }
-
Purchase a ticket:
mutation { purchaseTicket( purchaseTicketInput: { ticketId: 1 quantity: 2 paymentIntentId: "pi_1JHYLR2eZvKYlo2C8kIfmQqN" } userId: 1 ) { id quantity totalPrice ticket { type price } } }
This setup provides the necessary backend functionality for integrating a payment gateway (Stripe) for ticket purchases. You can further extend these functionalities as needed.
To create a dashboard for different user roles (Admin, Organizer, and Attendee), we will need to build appropriate resolvers and services to fetch and aggregate the required data. We'll create separate services and resolvers for each type of dashboard.
Admin Dashboard
admin-dashboard.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class AdminDashboardService {
constructor(private readonly prisma: PrismaService) {}
async getSystemMetrics() {
const userCount = await this.prisma.user.count();
const eventCount = await this.prisma.event.count();
const ticketSales = await this.prisma.ticketPurchase.count();
return { userCount, eventCount, ticketSales };
}
async getUsers() {
return this.prisma.user.findMany();
}
async getEvents() {
return this.prisma.event.findMany();
}
}
admin-dashboard.resolver.ts
import { Resolver, Query } from '@nestjs/graphql';
import { AdminDashboardService } from './admin-dashboard.service';
@Resolver()
export class AdminDashboardResolver {
constructor(private readonly adminDashboardService: AdminDashboardService) {}
@Query(() => String)
async getSystemMetrics() {
return this.adminDashboardService.getSystemMetrics();
}
@Query(() => [User])
async getUsers() {
return this.adminDashboardService.getUsers();
}
@Query(() => [Event])
async getEvents() {
return this.adminDashboardService.getEvents();
}
}
admin-dashboard.module.ts
import { Module } from '@nestjs/common';
import { AdminDashboardService } from './admin-dashboard.service';
import { AdminDashboardResolver } from './admin-dashboard.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [AdminDashboardService, AdminDashboardResolver],
})
export class AdminDashboardModule {}
Organizer Dashboard
organizer-dashboard.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class OrganizerDashboardService {
constructor(private readonly prisma: PrismaService) {}
async getOrganizerEvents(organizerId: number) {
return this.prisma.event.findMany({
where: { organizerId },
include: {
tickets: true,
purchases: true,
},
});
}
async getEventAttendees(eventId: number) {
return this.prisma.ticketPurchase.findMany({
where: { eventId },
include: { user: true },
});
}
}
organizer-dashboard.resolver.ts
import { Resolver, Query, Args, Int } from '@nestjs/graphql';
import { OrganizerDashboardService } from './organizer-dashboard.service';
@Resolver()
export class OrganizerDashboardResolver {
constructor(private readonly organizerDashboardService: OrganizerDashboardService) {}
@Query(() => [Event])
async getOrganizerEvents(@Args('organizerId', { type: () => Int }) organizerId: number) {
return this.organizerDashboardService.getOrganizerEvents(organizerId);
}
@Query(() => [TicketPurchase])
async getEventAttendees(@Args('eventId', { type: () => Int }) eventId: number) {
return this.organizerDashboardService.getEventAttendees(eventId);
}
}
organizer-dashboard.module.ts
import { Module } from '@nestjs/common';
import { OrganizerDashboardService } from './organizer-dashboard.service';
import { OrganizerDashboardResolver } from './organizer-dashboard.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [OrganizerDashboardService, OrganizerDashboardResolver],
})
export class OrganizerDashboardModule {}
Attendee Dashboard
attendee-dashboard.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class AttendeeDashboardService {
constructor(private readonly prisma: PrismaService) {}
async getUserEvents(userId: number) {
return this.prisma.ticketPurchase.findMany({
where: { userId },
include: { event: true, ticket: true },
});
}
}
attendee-dashboard.resolver.ts
import { Resolver, Query, Args, Int } from '@nestjs/graphql';
import { AttendeeDashboardService } from './attendee-dashboard.service';
@Resolver()
export class AttendeeDashboardResolver {
constructor(private readonly attendeeDashboardService: AttendeeDashboardService) {}
@Query(() => [TicketPurchase])
async getUserEvents(@Args('userId', { type: () => Int }) userId: number) {
return this.attendeeDashboardService.getUserEvents(userId);
}
}
attendee-dashboard.module.ts
import { Module } from '@nestjs/common';
import { AttendeeDashboardService } from './attendee-dashboard.service';
import { AttendeeDashboardResolver } from './attendee-dashboard.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [AttendeeDashboardService, AttendeeDashboardResolver],
})
export class AttendeeDashboardModule {}
Integrate Dashboard Modules in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { EmailModule } from './email/email.module';
import { NotificationModule } from './notification/notification.module';
import { PaymentModule } from './payment/payment.module';
import { AdminDashboardModule } from './admin-dashboard/admin-dashboard.module';
import { OrganizerDashboardModule } from './organizer-dashboard/organizer-dashboard.module';
import { AttendeeDashboardModule } from './attendee-dashboard/attendee-dashboard.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
EmailModule,
NotificationModule,
PaymentModule,
AdminDashboardModule,
OrganizerDashboardModule,
AttendeeDashboardModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Dashboard Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the dashboard queries:
-
Admin Dashboard:
query { getSystemMetrics { userCount eventCount ticketSales } getUsers { id email role } getEvents { id title date organizer { id name } } }
-
Organizer Dashboard:
query { getOrganizerEvents(organizerId: 1) { id title tickets { id type price quantity } purchases { id user { id email } quantity totalPrice } } getEventAttendees(eventId: 1) { id user { id email } quantity totalPrice } }
-
Attendee Dashboard:
query { getUserEvents(userId: 1) { id event { id title date } ticket { type price } quantity totalPrice } }
This setup provides the necessary backend functionality for the different dashboards (Admin, Organizer, and Attendee). You can further extend these functionalities as needed.
To implement analytics for event performance and user behavior, we'll create dedicated services and resolvers to handle these tasks. These services will fetch and process data from the database to provide insights.
Event Analytics
event-analytics.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class EventAnalyticsService {
constructor(private readonly prisma: PrismaService) {}
async getEventPerformance(eventId: number) {
const ticketSales = await this.prisma.ticketPurchase.aggregate({
_sum: { totalPrice: true },
_count: { id: true },
where: { eventId },
});
const attendeeDemographics = await this.prisma.user.findMany({
where: {
tickets: {
some: { eventId },
},
},
select: {
age: true,
gender: true,
location: true,
},
});
return { ticketSales, attendeeDemographics };
}
}
event-analytics.resolver.ts
import { Resolver, Query, Args, Int } from '@nestjs/graphql';
import { EventAnalyticsService } from './event-analytics.service';
@Resolver()
export class EventAnalyticsResolver {
constructor(private readonly eventAnalyticsService: EventAnalyticsService) {}
@Query(() => String)
async getEventPerformance(@Args('eventId', { type: () => Int }) eventId: number) {
return this.eventAnalyticsService.getEventPerformance(eventId);
}
}
event-analytics.module.ts
import { Module } from '@nestjs/common';
import { EventAnalyticsService } from './event-analytics.service';
import { EventAnalyticsResolver } from './event-analytics.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [EventAnalyticsService, EventAnalyticsResolver],
})
export class EventAnalyticsModule {}
User Analytics
user-analytics.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UserAnalyticsService {
constructor(private readonly prisma: PrismaService) {}
async getUserEngagement(userId: number) {
const eventsAttended = await this.prisma.ticketPurchase.count({
where: { userId },
});
const totalSpent = await this.prisma.ticketPurchase.aggregate({
_sum: { totalPrice: true },
where: { userId },
});
return { eventsAttended, totalSpent };
}
async getUserBehavior() {
const activeUsers = await this.prisma.user.findMany({
where: {
tickets: {
some: {},
},
},
select: {
id: true,
email: true,
tickets: {
select: { eventId: true },
},
},
});
const userDemographics = await this.prisma.user.groupBy({
by: ['age', 'gender', 'location'],
_count: { id: true },
});
return { activeUsers, userDemographics };
}
}
user-analytics.resolver.ts
import { Resolver, Query, Args, Int } from '@nestjs/graphql';
import { UserAnalyticsService } from './user-analytics.service';
@Resolver()
export class UserAnalyticsResolver {
constructor(private readonly userAnalyticsService: UserAnalyticsService) {}
@Query(() => String)
async getUserEngagement(@Args('userId', { type: () => Int }) userId: number) {
return this.userAnalyticsService.getUserEngagement(userId);
}
@Query(() => String)
async getUserBehavior() {
return this.userAnalyticsService.getUserBehavior();
}
}
user-analytics.module.ts
import { Module } from '@nestjs/common';
import { UserAnalyticsService } from './user-analytics.service';
import { UserAnalyticsResolver } from './user-analytics.resolver';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [UserAnalyticsService, UserAnalyticsResolver],
})
export class UserAnalyticsModule {}
Integrate Analytics Modules in App Module
Update app.module.ts
:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { GraphqlModule } from './graphql/graphql.module';
import { EventsModule } from './events/events.module';
import { TicketsModule } from './tickets/tickets.module';
import { EmailModule } from './email/email.module';
import { NotificationModule } from './notification/notification.module';
import { PaymentModule } from './payment/payment.module';
import { AdminDashboardModule } from './admin-dashboard/admin-dashboard.module';
import { OrganizerDashboardModule } from './organizer-dashboard/organizer-dashboard.module';
import { AttendeeDashboardModule } from './attendee-dashboard/attendee-dashboard.module';
import { EventAnalyticsModule } from './event-analytics/event-analytics.module';
import { UserAnalyticsModule } from './user-analytics/user-analytics.module';
@Module({
imports: [
AuthModule,
PrismaModule,
GraphqlModule,
EventsModule,
TicketsModule,
EmailModule,
NotificationModule,
PaymentModule,
AdminDashboardModule,
OrganizerDashboardModule,
AttendeeDashboardModule,
EventAnalyticsModule,
UserAnalyticsModule,
],
})
export class AppModule {}
Running the Backend
- Start the NestJS Server
npm run start:dev
- Test Analytics Functionality
Access the GraphQL playground at http://localhost:3000/graphql
and test the analytics queries:
-
Event Analytics:
query { getEventPerformance(eventId: 1) { ticketSales { _sum { totalPrice } _count { id } } attendeeDemographics { age gender location } } }
-
User Analytics:
query { getUserEngagement(userId: 1) { eventsAttended totalSpent { _sum { totalPrice } } } getUserBehavior { activeUsers { id email tickets { eventId } } userDemographics { age gender location _count { id } } } }
This setup provides the necessary backend functionality for event performance and user behavior analytics. You can further extend these functionalities as needed.
If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!
Disclaimer: This content is generated by AI.
Top comments (0)