DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Edited on

Develop Full Stack Event Management System

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)

  1. Initialize Next.js project:
   npx create-next-app@latest event-management-frontend
   cd event-management-frontend
Enter fullscreen mode Exit fullscreen mode
  1. Install Tailwind CSS:
   npm install -D tailwindcss postcss autoprefixer
   npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode
  1. Configure tailwind.config.js:
   module.exports = {
     content: [
       "./pages/**/*.{js,ts,jsx,tsx}",
       "./components/**/*.{js,ts,jsx,tsx}",
     ],
     theme: {
       extend: {},
     },
     plugins: [],
   }
Enter fullscreen mode Exit fullscreen mode
  1. Add Tailwind directives to styles/globals.css:
   @tailwind base;
   @tailwind components;
   @tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Backend (NestJS + Prisma)

  1. Initialize NestJS project:
   npm i -g @nestjs/cli
   nest new event-management-backend
   cd event-management-backend
Enter fullscreen mode Exit fullscreen mode
  1. Install Prisma and PostgreSQL client:
   npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express
   npm install @prisma/client
   npm install -D prisma
Enter fullscreen mode Exit fullscreen mode
  1. Initialize Prisma:
   npx prisma init
Enter fullscreen mode Exit fullscreen mode
  1. 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
   }
Enter fullscreen mode Exit fullscreen mode
  1. Run Prisma migrations:
   npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

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

  1. Deploy the Next.js application on Vercel.
  2. Connect your GitHub repository to Vercel for automatic deployments.

Backend

  1. Create a Dockerfile for the NestJS application.
  2. 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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/auth/signup.js

import SignupForm from '../../components/Auth/SignupForm';

const SignupPage = () => {
  return (
    <div>
      <SignupForm />
    </div>
  );
};

export default SignupPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/auth/login.js

import LoginForm from '../../components/Auth/LoginForm';

const LoginPage = () => {
  return (
    <div>
      <LoginForm />
    </div>
  );
};

export default LoginPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/auth/reset-password.js

import PasswordResetForm from '../../components/Auth/PasswordResetForm';

const PasswordResetPage = () => {
  return (
    <div>
      <PasswordResetForm />
    </div>
  );
};

export default PasswordResetPage;
Enter fullscreen mode Exit fullscreen mode

Styles

Make sure you have Tailwind CSS properly configured in your globals.css as mentioned in the initial setup.

API Endpoints

The above forms make POST requests to /api/auth/signup, /api/auth/login, and /api/auth/reset-password. You need to implement these endpoints in your backend (NestJS) to handle these requests.

This setup should give you a fully functioning frontend for user authentication in your event management system.

To implement user roles (Admin, Organizer, and Attendee) in the frontend using Next.js and Tailwind CSS, you'll need to create different components and pages for each role. Here's an example setup for each role's dashboard and basic functionality.

Project Structure

event-management-frontend
├── components
│   ├── Admin
│   │   ├── AdminDashboard.js
│   │   ├── ManageEvents.js
│   │   ├── ManageUsers.js
│   ├── Organizer
│   │   ├── OrganizerDashboard.js
│   │   ├── CreateEvent.js
│   │   ├── ManageOwnEvents.js
│   ├── Attendee
│   │   ├── AttendeeDashboard.js
│   │   ├── ViewEvents.js
│   │   ├── RegisterEvent.js
├── pages
│   ├── admin
│   │   ├── index.js
│   │   ├── manage-events.js
│   │   ├── manage-users.js
│   ├── organizer
│   │   ├── index.js
│   │   ├── create-event.js
│   │   ├── manage-events.js
│   ├── attendee
│   │   ├── index.js
│   │   ├── view-events.js
│   │   ├── register-event.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/admin/index.js

import AdminDashboard from '../../components/Admin/AdminDashboard';

const AdminPage = () => {
  return (
    <div>
      <AdminDashboard />
    </div>
  );
};

export default AdminPage;
Enter fullscreen mode Exit fullscreen mode

pages/admin/manage-events.js

import ManageEvents from '../../components/Admin/ManageEvents';

const ManageEventsPage = () => {
  return (
    <div>
      <ManageEvents />
    </div>
  );
};

export default ManageEventsPage;
Enter fullscreen mode Exit fullscreen mode

pages/admin/manage-users.js

import ManageUsers from '../../components/Admin/ManageUsers';

const ManageUsersPage = () => {
  return (
    <div>
      <ManageUsers />
    </div>
  );
};

export default ManageUsersPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/index.js

import OrganizerDashboard from '../../components/Organizer/OrganizerDashboard';

const OrganizerPage = () => {
  return (
    <div>
      <OrganizerDashboard />
    </div>
  );
};

export default OrganizerPage;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/create-event.js

import CreateEvent from '../../components/Organizer/CreateEvent';

const CreateEventPage = () => {
  return (
    <div>
      <CreateEvent />
    </div>
  );
};

export default CreateEventPage;


Enter fullscreen mode Exit fullscreen mode

pages/organizer/manage-events.js

import ManageOwnEvents from '../../components/Organizer/ManageOwnEvents';

const ManageOwnEventsPage = () => {
  return (
    <div>
      <ManageOwnEvents />
    </div>
  );
};

export default ManageOwnEventsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/index.js

import AttendeeDashboard from '../../components/Attendee/AttendeeDashboard';

const AttendeePage = () => {
  return (
    <div>
      <AttendeeDashboard />
    </div>
  );
};

export default AttendeePage;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/view-events.js

import ViewEvents from '../../components/Attendee/ViewEvents';

const ViewEventsPage = () => {
  return (
    <div>
      <ViewEvents />
    </div>
  );
};

export default ViewEventsPage;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/register-event.js

import RegisterEvent from '../../components/Attendee/RegisterEvent';

const RegisterEventPage = () => {
  return (
    <div>
      <RegisterEvent />
    </div>
  );
};

export default RegisterEventPage;
Enter fullscreen mode Exit fullscreen mode

Navigation and Authorization

You should also implement navigation and role-based authorization checks to ensure users only access the pages they are allowed to. This can be achieved using a combination of custom hooks, context providers, and middleware.

Styles

Make sure Tailwind CSS is properly configured in your globals.css.

With these components and pages, you can build a user role-based frontend for your event management system. The functionality for each role can be expanded as needed.

To implement event management features in the frontend using Next.js and Tailwind CSS, you'll need components and pages for creating, editing, deleting, viewing, and searching events. Below is an example setup for these functionalities.

Project Structure

event-management-frontend
├── components
│   ├── Organizer
│   │   ├── CreateEvent.js
│   │   ├── EditEvent.js
│   │   ├── DeleteEvent.js
│   │   ├── ManageOwnEvents.js
│   ├── Event
│   │   ├── EventDetails.js
│   │   ├── SearchEvents.js
├── pages
│   ├── organizer
│   │   ├── create-event.js
│   │   ├── edit-event.js
│   │   ├── manage-events.js
│   ├── events
│   │   ├── [id].js
│   │   ├── search.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/create-event.js

import CreateEvent from '../../components/Organizer/CreateEvent';

const CreateEventPage = () => {
  return (
    <div>
      <CreateEvent />
    </div>
  );
};

export default CreateEventPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/edit-event.js

import EditEvent from '../../components/Organizer/EditEvent';

const EditEventPage = () => {
  return (
    <div>
      <EditEvent

 />
    </div>
  );
};

export default EditEventPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/events/[id].js

import EventDetails from '../../components/Event/EventDetails';

const EventDetailsPage = () => {
  return (
    <div>
      <EventDetails />
    </div>
  );
};

export default EventDetailsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/events/search.js

import SearchEvents from '../../components/Event/SearchEvents';

const SearchEventsPage = () => {
  return (
    <div>
      <SearchEvents />
    </div>
  );
};

export default SearchEventsPage;
Enter fullscreen mode Exit fullscreen mode

Navigation and Authorization

Ensure you have navigation links set up and proper authorization checks for each role to access specific pages.

Styles

Make sure Tailwind CSS is properly configured in your globals.css.

With these components and pages, you can build a comprehensive event management system frontend using Next.js and Tailwind CSS.

To implement ticket management features in the frontend using Next.js and Tailwind CSS, you need components and pages for creating tickets, purchasing tickets, and viewing purchased tickets. Below is the example setup for these functionalities.

Project Structure

event-management-frontend
├── components
│   ├── Organizer
│   │   ├── CreateTickets.js
│   ├── Attendee
│   │   ├── PurchaseTickets.js
│   │   ├── ViewTickets.js
├── pages
│   ├── organizer
│   │   ├── create-tickets.js
│   ├── attendee
│   │   ├── purchase-tickets.js
│   │   ├── view-tickets.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/create-tickets.js

import CreateTickets from '../../components/Organizer/CreateTickets';

const CreateTicketsPage = () => {
  return (
    <div>
      <CreateTickets />
    </div>
  );
};

export default CreateTicketsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/purchase-tickets.js

import PurchaseTickets from '../../components/Attendee/PurchaseTickets';

const PurchaseTicketsPage = () => {
  return (
    <div>
      <PurchaseTickets />
    </div>
  );
};

export default PurchaseTicketsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/view-tickets.js

import ViewTickets from '../../components/Attendee/ViewTickets';

const ViewTicketsPage = () => {
  return (
    <div>
      <ViewTickets />
    </div>
  );
};

export default ViewTicketsPage;
Enter fullscreen mode Exit fullscreen mode

Navigation and Authorization

Ensure you have navigation links set up and proper authorization checks

for each role to access specific pages.

Styles

Make sure Tailwind CSS is properly configured in your globals.css.

With these components and pages, you can build a comprehensive ticket management system frontend using Next.js and Tailwind CSS.

To implement a notification system in the frontend using Next.js and Tailwind CSS, you'll need components for displaying in-app notifications and mechanisms to trigger email notifications (typically handled by the backend). Below is an example setup for in-app notifications and placeholders for triggering email notifications.

Project Structure

event-management-frontend
├── components
│   ├── Notifications
│   │   ├── InAppNotifications.js
├── pages
│   ├── notifications
│   │   ├── index.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/notifications/index.js

import InAppNotifications from '../../components/Notifications/InAppNotifications';

const NotificationsPage = () => {
  return (
    <div>
      <InAppNotifications />
    </div>
  );
};

export default NotificationsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Summary

  • In-App Notifications: The InAppNotifications component displays real-time notifications. The notifications are fetched from the backend using an API call.
  • Email Notifications: Email notifications are triggered by making an API call to the backend whenever an important action occurs (e.g., ticket purchase).

Make sure to implement the necessary backend logic to handle these API endpoints and send emails.

To integrate a payment gateway in your frontend for ticket purchases, you can use popular options like Stripe. Below is an example of how to integrate Stripe into your Next.js application for handling ticket purchases.

Project Structure

event-management-frontend
├── components
│   ├── Attendee
│   │   ├── PurchaseTickets.js
│   │   ├── CheckoutForm.js
├── pages
│   ├── attendee
│   │   ├── purchase-tickets.js
│   │   ├── success.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

1. Setting Up Stripe

First, install the necessary Stripe packages:

npm install @stripe/stripe-js @stripe/react-stripe-js
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/purchase-tickets.js

import PurchaseTickets from '../../components/Attendee/PurchaseTickets';

const PurchaseTicketsPage = () => {
  return (
    <div>
      <PurchaseTickets />
    </div>
  );
};

export default PurchaseTicketsPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Summary

  • Checkout Form: The CheckoutForm component handles the Stripe payment process.
  • Purchase Tickets: The PurchaseTickets component handles the ticket selection and triggers the checkout process.
  • Success Page: A simple page to display after successful payment.

This setup integrates Stripe into your Next.js application for handling payments. Ensure you have the necessary backend setup to create payment intents and handle webhook events from Stripe for a complete integration.

To create dashboards for Admin, Organizer, and Attendee roles in your Next.js application using Tailwind CSS, you will need to set up the structure and components for each dashboard. Each dashboard will have different views and functionalities according to the role.

Project Structure

event-management-frontend
├── components
│   ├── Admin
│   │   ├── AdminDashboard.js
│   ├── Organizer
│   │   ├── OrganizerDashboard.js
│   ├── Attendee
│   │   ├── AttendeeDashboard.js
├── pages
│   ├── admin
│   │   ├── dashboard.js
│   ├── organizer
│   │   ├── dashboard.js
│   ├── attendee
│   │   ├── dashboard.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/admin/dashboard.js

import AdminDashboard from '../../components/Admin/AdminDashboard';

const AdminDashboardPage = () => {
  return (
    <div>
      <AdminDashboard />
    </div>
  );
};

export default AdminDashboardPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/organizer/dashboard.js

import OrganizerDashboard from '../../components/Organizer/OrganizerDashboard';

const OrganizerDashboardPage = () => {
  return (
    <div>
      <OrganizerDashboard />
    </div>
  );
};

export default OrganizerDashboardPage;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

pages/attendee/dashboard.js

import AttendeeDashboard from '../../components/Attendee/AttendeeDashboard';

const AttendeeDashboardPage = () => {
  return (
    <div>
      <AttendeeDashboard />
    </div>
  );
};

export default AttendeeDashboardPage;
Enter fullscreen mode Exit fullscreen mode

Summary

  • Admin Dashboard: Displays system metrics, user management, and event management.
  • Organizer Dashboard: Shows organizer's events and ticket sales.
  • Attendee Dashboard: Shows registered events and purchased tickets.

Each dashboard fetches data from the backend using Axios and displays it in tables. Tailwind CSS is used for styling. Make sure your backend APIs are set up to provide the necessary data for each dashboard.

To implement analytics for events and users in your Next.js application, you can use charting libraries such as Chart.js or Recharts. For this example, we'll use Recharts, a popular charting library for React, to display event performance and user engagement insights.

Project Structure

event-management-frontend
├── components
│   ├── Analytics
│   │   ├── EventAnalytics.js
│   │   ├── UserAnalytics.js
├── pages
│   ├── analytics
│   │   ├── event.js
│   │   ├── user.js
└── styles
    └── globals.css
Enter fullscreen mode Exit fullscreen mode

1. Install Recharts

First, install Recharts:

npm install recharts
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

4. Analytics Pages

pages/analytics/event.js

import EventAnalytics from '../../components/Analytics/EventAnalytics';

const EventAnalyticsPage = () => {
  return (
    <div>
      <EventAnalytics />
    </div>
  );
};

export default EventAnalyticsPage;
Enter fullscreen mode Exit fullscreen mode

pages/analytics/user.js

import UserAnalytics from '../../components/Analytics/UserAnalytics';

const UserAnalyticsPage = () => {
  return (
    <div>
      <UserAnalytics />
    </div>
  );
};

export default UserAnalyticsPage;
Enter fullscreen mode Exit fullscreen mode

Summary

  • Event Analytics: Displays insights into event performance using bar charts to show ticket sales and attendee demographics.
  • User Analytics: Shows insights into user behavior and engagement using line charts.

Each component fetches data from the backend using Axios and displays it using Recharts. Tailwind CSS is used for styling. Ensure your backend APIs are set up to provide the necessary data for each analytic view.

Here is a detailed setup for the backend of your full-stack event management system using NestJS, Prisma, PostgreSQL, and GraphQL. Let's start with the basic setup for user authentication.

Backend Setup with NestJS

  1. Initialize a NestJS Project

    npm i -g @nestjs/cli
    nest new event-management-backend
    
  2. 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
    
  3. 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
    
  4. 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();
        });
      }
    }
    
  5. 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;
}
```
Enter fullscreen mode Exit fullscreen mode
  1. 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;
    }
    
  2. 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 {}
    
  3. 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

  1. Start the NestJS Server

    npm run start:dev
    
  2. Test the GraphQL API

    Access the GraphQL playground at http://localhost:3000/graphql and test the authentication queries and mutations:

    mutation {
      signup(userInput: {email: "test@example.com", password: "password"}) {
        access_token
      }
    }
    
    mutation {
      login(userInput: {email: "test@example.com", password: "password"}) {
        access_token
      }
    }
    

This setup provides the foundational authentication system. You can now build upon this by adding other features like event management, ticket management, notifications, and more.

Sure, let's extend the backend to support user roles and permissions. We'll add role-based access control to our authentication system. Here’s how we can achieve that:

  1. 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
   }
Enter fullscreen mode Exit fullscreen mode

Run the Prisma migration:

   npx prisma migrate dev --name add-roles
Enter fullscreen mode Exit fullscreen mode
  1. 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;
     }
   }
Enter fullscreen mode Exit fullscreen mode

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);
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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));
     }
   }
Enter fullscreen mode Exit fullscreen mode

Create roles.decorator.ts:

   import { SetMetadata } from '@nestjs/common';
   import { Role } from '@prisma/client';

   export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

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.
   }
Enter fullscreen mode Exit fullscreen mode

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 [];
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. 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
     }
   }
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Run the Prisma migration:

npx prisma migrate dev --name add-events
Enter fullscreen mode Exit fullscreen mode

Create Event Module in NestJS

  1. 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[];
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
  1. 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' } },
        ],
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. 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);
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. 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
     }
   }
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Run the Prisma migration:

npx prisma migrate dev --name add-ticket-purchases
Enter fullscreen mode Exit fullscreen mode

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 },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Create dto/purchase-ticket.input.ts:

import { InputType, Field } from '@nestjs/graphql';

@InputType()
export class PurchaseTicketInput {
  @Field()
  ticketId: number;

  @Field()
  quantity: number;
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. 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
     }
   }
Enter fullscreen mode Exit fullscreen mode

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

  1. Install Nodemailer
   npm install nodemailer
Enter fullscreen mode Exit fullscreen mode
  1. 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);
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Update Environment Variables

Add the following to your .env file:

   EMAIL_USER=your-email@gmail.com
   EMAIL_PASS=your-email-password
Enter fullscreen mode Exit fullscreen mode
  1. 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 },
       });
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

Setting Up In-App Notifications

  1. Install WebSockets
   npm install @nestjs/websockets @nestjs/platform-socket.io
Enter fullscreen mode Exit fullscreen mode
  1. 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);
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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 },
       });
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. 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
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode

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

  1. Install Stripe SDK
   npm install stripe @nestjs/stripe
Enter fullscreen mode Exit fullscreen mode
  1. 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);
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Update Environment Variables

Add the following to your .env file:

   STRIPE_SECRET_KEY=your-stripe-secret-key
   STRIPE_PUBLIC_KEY=your-stripe-public-key
Enter fullscreen mode Exit fullscreen mode
  1. Create Payment DTOs

Create dto/create-payment.dto.ts:

   export class CreatePaymentDto {
     amount: number;
     currency: string;
   }
Enter fullscreen mode Exit fullscreen mode

Create dto/confirm-payment.dto.ts:

   export class ConfirmPaymentDto {
     paymentIntentId: string;
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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;
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode
  1. 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 {}
Enter fullscreen mode Exit fullscreen mode

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 },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. Test Payment Functionality

Access the GraphQL playground at http://localhost:3000/graphql and test the payment flow:

  1. Create a payment intent:

      mutation {
        createPaymentIntent(createPaymentDto: { amount: 5000, currency: "usd" }) {
          client_secret
        }
      }
    
  2. Confirm the payment intent:

      mutation {
        confirmPaymentIntent(confirmPaymentDto: { paymentIntentId: "pi_1JHYLR2eZvKYlo2C8kIfmQqN" }) {
          status
        }
      }
    
  3. 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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. 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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

Running the Backend

  1. Start the NestJS Server
   npm run start:dev
Enter fullscreen mode Exit fullscreen mode
  1. Test Analytics Functionality

Access the GraphQL playground at http://localhost:3000/graphql and test the analytics queries:

  • Event Analytics:

     query {
       getEventPerformance(eventId: 1) {
         ticketSales {
           _sum {
             totalPrice
           }
           _count {
             id
           }
         }
         attendeeDemographics {
           age
           gender
           location
         }
       }
     }
    
  • User Analytics:

     query {
       getUserEngagement(userId: 1) {
         eventsAttended
         totalSpent {
           _sum {
             totalPrice
           }
         }
       }
    
       getUserBehavior {
         activeUsers {
           id
           email
           tickets {
             eventId
           }
         }
         userDemographics {
           age
           gender
           location
           _count {
             id
           }
         }
       }
     }
    

This setup provides the necessary backend functionality for event performance and user behavior analytics. You can further extend these functionalities as needed.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content is generated by AI.

Top comments (0)