Develop Full Stack Event Management System

RMAG news

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. Let me know if you need further assistance with the backend or any other part of the project!

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. If you need assistance with any other features or the backend implementation, let me know!

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. If you need further assistance or have specific requirements, feel free to ask!

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. If you need further assistance or have specific requirements, feel free to ask!

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. If you need further assistance or have specific requirements, feel free to ask!

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. If you need further assistance or have specific requirements, feel free to ask!

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. If you need further customization or specific features, feel free to ask!

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. If you need further customization or specific features, feel free to ask!

Sure, I’ll provide you with 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 and dto/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. Let me know if you need further assistance with any specific feature!

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.

Disclaimer: This content is generated by AI.

Please follow and like us:
Pin Share