Build an Event Management App with Laravel and Twilio SendGrid

December 30, 2024
Written by
Caleb Oki
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Build an Event Management App with Laravel and Twilio SendGrid

Event management platforms are quite common in today's interconnected world.

They serve as the backbone for organizing gatherings, from intimate workshops to grand conferences, providing a seamless bridge between hosts and attendees. Such platforms not only streamline the planning process but also enhance the overall event experience through efficient management and communication tools.

In this tutorial, I'll show you how to build "EventVoyage", an event management application using Laravel and Twilio SendGrid.

Prerequisites

To complete the tutorial you will need the following:

Application overview

Before we begin building the application, I want to give you a broad overview of the application's user flow.

The EventVoyage application is a minimal viable product (MVP) designed to demonstrate the intricacies of event management and participation; offering a streamlined user experience for both event organizers and attendees.

Once logged in, event organizers are welcomed into their dashboard, a central hub where they can view, create, and manage their events. The process of creating an event is intuitive, requiring organizers to input details such as the event's title, description, date, time, and location.

Upon the creation or editing of an event, Twilio SendGrid steps in to send email notifications, confirming actions taken by the organizers and informing attendees of any updates.

Attendees, on the other hand, are presented with a curated list of upcoming and past events, allowing them to explore and register for events with ease. Each registration triggers an automatic email confirmation, ensuring attendees are well-informed and their participation is recorded. Attendees also benefit from the ability to manage their registrations, including the option to unregister from events.

Email notifications, which would be powered by Twilio SendGrid, play a pivotal role throughout the user journey. They serve as a reliable communication channel between organizers and attendees. Both groups have access to interactive dashboards tailored to their needs, displaying relevant information such as upcoming events, past events, and registration statuses.

Build the app

Set up the Laravel application

The first thing we need to do is to create a new Laravel application. To create the project, named EventVoyage, open your terminal and run the following command:

curl -s "https://laravel.build/eventvoyage" | bash

This command downloads and runs a script to set up your Laravel project and integrates Laravel Sail; a light-weight command-line interface for interacting with Docker, to build a local development environment with all of the services and dependencies which the application needs.

At the end of the process, you'll be asked the following:

Please provide your password so we can make some final adjustments to your application's permissions.
Feel free to use any of the other methods of bootstrapping Laravel applications if you prefer them.

Once completed, navigate into your project directory:

cd eventvoyage

To start your Docker containers, including all the services e.g., MySQL and Redis, execute the following command:

./vendor/bin/sail up -d

Finally, open another terminal in your project directory. There, run the initial database migrations to populate the database with the default tables from Laravel by running the following command in a new terminal tab or session:

./vendor/bin/sail artisan migrate

Once the application's started, you can access the application in your web browser at http://localhost.

Screenshot of Laravel's documentation, Laravel News, and Laracasts sections on their official website.

Set up authentication with Laravel Breeze

Next, we will give your application a head-start by installing Laravel Breeze, a simple implementation of authentication features, such as login, registration, email verification, and password reset.

To install Breeze, execute the following commands:

./vendor/bin/sail composer require laravel/breeze --dev
./vendor/bin/sail artisan breeze:install blade

After installing Breeze, compile the frontend assets with:

./vendor/bin/sail npm run dev

If you refresh your Laravel application in the browser, you should now see a Register link at the top-right. Follow that link to see the registration form provided by Laravel Breeze.

Laravel Breeze registration page

Then, register a new account and log in.

Set up Twilio SendGrid

To integrate Twilio SendGrid into the application, first, sign in to your SendGrid account and obtain your API key. To obtain your API key, navigate to Settings > API Keys. Once there, click on the "Create API Key" button in the top right corner of the page. Give the new API key a name, accept the default permission of “Full Access”, then click “Create & View”.

Keep the generated API Key visible for the time being, or copy it somewhere safe — as it will only be displayed once.

Then, update your .env file with the following details:

APP_NAME=EventVoyage
SENDGRID_API_KEY="<your_sendgrid_api_key_here>"
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD="${SENDGRID_API_KEY}"
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="<your_email@example.com>"
MAIL_FROM_NAME="${APP_NAME}"

After that, replace <your_sendgrid_api_key_here> with your newly generated API key, and < your_email@example.com> with your Single Sender Verified email address. This configuration tells Laravel to use SendGrid as the mail driver, and sets the default from address and name for your emails.

Create and update events

Now you'll work on the core feature that enables users to create events.

Add models, migrations, and controllers

To allow users to create events, you will need to create models, migrations, and controllers. You can create a model, migration, and resource controller for the events with the following command:

./vendor/bin/sail php artisan make:model -mrc Event

Executing this command generates three essential files:

  • Firstly, app/Models/Event.php is created as the Eloquent model, which serves as the ORM (Object-Relational Mapping) component, linking the event data structure to the application's database.
  • Secondly, a database migration file ending with _create_events_table.php  is created within the database/migrations directory. This migration outlines the schema for the event table in the database, specifying how it should be constructed. Lastly, app/Http/Controllers/EventController.php is the HTTP controller. This controller is responsible for handling incoming requests related to events and generating the appropriate responses, thereby bridging the gap between the user interface and the database operations.

In addition to the event resources we just generated, let's create a registration resource. This will handle all the registration logistics between a user and an event. Run the following command to create the resource:

./vendor/bin/sail php artisan make:model -mrc Registration
public function up(): void 
{ 
    Schema::create('events', function (Blueprint $table) { 
        $table->id(); 
        $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 
        $table->string('title'); 
        $table->string('description'); 
        $table->date('date'); 
        $table->time('time'); 
        $table->string('location'); 
        $table->timestamps();
    }); 
}

In the updated up() method, several fields were defined in the events table to store event details. These include a primary key id, a user_id as a foreign key linked to the users table with cascading delete functionality, and fields for the title, description, date, time, and location of the event. Additionally, Laravel's timestamps() method automatically adds created_at and updated_at columns to track when each event was created and last updated.

Similarly, update the up() method of the migration file ending with _create_registration_table.php with the following:

public function up(): void 
{
    Schema::create('registrations', function (Blueprint $table) { 
        $table->id(); 
        $table->foreignId('user_id')->constrained()->cascadeOnDelete();
        $table->foreignId('event_id')->constrained()->cascadeOnDelete(); 
        $table->timestamps(); 
    }); 
}

This structures the registrations table to track user registrations for events. The setup includes an id as the primary key, and two foreign keys: user_id and event_id.

The user_id links the registration to a specific user, and event_id associates it with a particular event, both employing constraints for referential integrity and cascading deletes to remove registrations if a user or event is deleted. The inclusion of timestamps() automatically adds created_at and updated_at columns, allowing the application to record when a registration is created or modified.

Run the migration again to update the database with these changes:

./vendor/bin/sail artisan migrate

Set up the relationship

Now, you need to establish a relationship between an event and a user, and a relationship between a registration and an event. Update app\Models\Event.php with the following properties and methods:

protected $guarded = []; 
protected $casts = [ 
    'date' => 'date',
];

public function registrations() 
{
    return $this->hasMany(Registration::class); 
}

public function isUserRegistered($userId) 
{
    return $this->registrations()->where('user_id', $userId)->exists();
}

Then, add the following properties and methods to the app\Models\Registration.php file:

protected $guarded = []; 

public function user() 
{
    return $this->belongsTo(User::class); 
}

public function event() 
{
    return $this->belongsTo(Event::class);
}

After that, add the following properties and methods to the app\Models\User.php file.

public function events(): HasMany
{
    return $this->hasMany(Event::class); 
}

public function registrations() 
{
    return $this->hasMany(Registration::class); 
}

After that, add the following import:

use Illuminate\Database\Eloquent\Relations\HasMany;

In the Event model, you have introduced methods to not only protect all attributes from mass assignment via the $guarded property, but also to establish a one-to-many relationship with the Registration model. This is achieved through the registrations() method, indicating that an event can have multiple registrations.

Additionally, the isUserRegistered() method utilizes this relationship to check if a specific user has registered for the event, enhancing the model's functionality. The $casts property is set to ensure the date attribute is correctly cast to a Date instance, facilitating easier date manipulation.

In the Registration model, similar protection against mass assignment is provided. The model defines relationships back to the User and Event models, signifying that each registration is linked to a single user and a single event. These relationships are important for navigating between users, their registrations, and the events they are interested in.

Lastly, the User model outlines the user's connections to events and registrations. Through the events() method, a one-to-many relationship is established, showing that a user can create multiple events. The registrations method further connects a user to their event registrations.

Add the routing configuration

To configure URLs for your controller, leverage the resource controller, utilizing the Route::resource() statement to define a comprehensive set of routes that follow a conventional URL structure.

You can secure these routes with two pieces of middleware:

  • The auth middleware, which restricts access to authenticated users
  • The verified middleware, which further limits access to users who have verified their accounts.

For the registration route, you will need to apply the only() method alongside the Route::resource() statement. This method allows you to explicitly define which controller actions are accessible, as you would only be needing the store() and destroy() methods here.At the end of the routes/web.php file, add the following to set up the routes:

Route::resource('events', EventController::class)
  ->middleware(['auth', 'verified']); 
Route::resource('registrations', RegistrationController::class)
  ->only(['store', 'destroy']);

Ensure you also include the necessary (below) use statements for EventController and RegistrationController` at the beginning of the file:

use App\Http\Controllers\EventController; 
use App\Http\Controllers\RegistrationController;

This setup maps the controller actions to specific routes, ensuring a structured and secure way to handle event and registration management within the application.

Create Mailables

You now need to create Mailable classes that will allow users to send emails, by running the following command:

./vendor/bin/sail php artisan make:mail EventCreated --markdown=mail.events.created

This generates an EventCreated mailable class with a corresponding Markdown template. Open this file, app\Mail\EventCreated.php, and update the content() method with the following:

public function content(): Content 
{ 
    return new Content( 
        markdown: 'mail.events.created', 
        with: [ 
            'title' => $this->event->title, 
            'date' => $this->event->date->format('F j, Y'), 
            'location' => $this->event->location, 
            'eventID' => $this->event->id, 
        ], 
    );
 }

This method constructs and returns a Content object for an email, utilizing a Markdown template with specific event details, such as title, date, location, and event ID. Ensure you also include the necessary use statement for the Event model at the top of the file, and pass a protected instance of the Event model in the constructor, as shown in the code below:

use App\Models\Event;

public function __construct(protected Event $event) { }

Secondly, generate a similar Mailable class for emails sent when an event is updated, by running the following command:

./vendor/bin/sail php artisan make:mail EventUpdated --markdown=mail.events.updated

Then, in app\Mail\EventUpdated.php update the content() method:

public function content(): Content 
{ 
    return new Content( 
        markdown: 'mail.events.updated', 
        with: [ 
            'title' => $this->event->title, 
            date' => $this->event->date->format('F j, Y'), 
            'location' => $this->event->location, 
            'eventID' => $this->event->id, 
        ],
    );
 }

Then, import the Event model, and create an Event instance in the constructor

use App\Models\Event;

public function __construct(protected Event $event) { }

This creates a dependency between the Event model and the EventUpdated mailable class.

Finally, generate a Mailable class for when a user registers for an event:

./vendor/bin/sail php artisan make:mail EventRegistered --markdown=mail.events.registered

Then, in app\Mail\EventRegistered.php, update the content() method, with the following:

public function content(): Content 
{ 
    return new Content( 
        markdown: 'mail.events.registered', 
        with: [ 
            'title' => $this->event->title, 
            'date' => $this->event->date->format('F j, Y'), 
            'location' => $this->event->location, 
            'eventID' => $this->event->id, 
        ],
    );
 }

Finally, pass the Event model into the constructor of the EventRegistered Mailable class. This creates a dependency between the Event model and the EventRegistered mailable class.

use App\Models\Event;

public function __construct(protected Event $event) { }

Add the EventController's methods

Before updating the Markdown templates that were generated, you're first going to build out the controller methods. To handle displaying an event, in app/Http/Controllers/EventController.php import the following files:

use App\Mail\EventCreated; 
use App\Mail\EventUpdated; 
use Carbon\Carbon; 
use Exception; 
use Illuminate\Http\RedirectResponse; 
use Illuminate\Support\Facades\Mail; 
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

Then update the stub index() method with the following:

public function index() 
{
    $upcomingEvents = Event::where('date', '>=', Carbon::today()->toDateString())->get(); 
    $pastEvents = Event::where('date', '<', Carbon::today()->toDateString())->get(); 
    return view('events.index', compact('upcomingEvents', 'pastEvents')); 
}

The method retrieves and categorizes events into two groups: upcoming events, which are those scheduled for today or in the future, and past events, which are those that have already occurred.

The method utilizes the Carbon library to compare event dates against the current date, ensuring accurate categorization. Then, it passes these categorized events to the yet to be created events.index view.

Next, handle displaying the form for creating an event by updating the stub create() method with the following:

public function create() 
{ 
    return view('events.create');
}

This returns the yet to be created view for creating an event.

Next, handle the logic that saves an event in the database. Update the stub store() method with the following:

public function store(Request $request): RedirectResponse 
{ 
    $validated = $request->validate(([
         'title' => 'required|string|max:255', 
        'description' => 'required|string', 
        'date' => 'required|date', 
        'time' => 'required', 
        'location' => 'required|string|max:255',
    ]));


    $event = $request->user()->events()->create($validated); 
    try { 
        Mail::to($request->user())->send(new EventCreated($event)); 
    } catch (Exception $e) { 
        Log::error('Email sending failed: ' . $e->getMessage()); 
        return back()->with('error', 'Failed to send email.'); 
    }

    return redirect()->route('events.index')->with('success', 'Event created successfully!');
}
This method:
  • validates the incoming request data for creating an event
  • creates the event associated with the current user
  • attempts to send an email notification about the event creation
  • logs any email sending failures
  • and finally redirects to the events index page.

To display a single event, update the show() method with the following:

public function show(Event $event) 
{
    return view('events.show', compact('event')); 
}

This method displays a specific event by returning the events.show view with the event data passed to it.

Next, add the method that displays a form for editing an event. Your edit() method should look like this:

public function edit(Event $event) 
{
    if (auth()->id() !== $event->user_id) { 
        return redirect()
                ->route('events.index')
                ->with('error', 'You are not authorized to edit this event.');
    } 
    return view('events.edit', compact('event')); 
}

This method checks if the currently authenticated user is the creator of the specified event. If so, it returns the view to edit the event. If the user is not authorized, it redirects them to the yet to be created events index page with an error message indicating the lack of authorization.

Finally, to update an event, enter the following for the update() method:

public function update(Request $request, Event $event) 
{ 
    if (auth()->id() !== $event->user_id) { 
        return back()->with('error', 'You are not authorized to edit this event.'); 
    } 

    $validated = $request->validate([
        'title' => 'required|string|max:255', 
        'description' => 'required|string', 
        'date' => 'required|date', 
        'time' => 'required', 
        'location' => 'required|string|max:255', 
    ]); 

    $event->update($validated); 

    try { 
        Mail::to($request->user())->send(new EventUpdated($event)); 
    } catch (Exception $e) { 
        Log::error('Email sending failed: ' . $e->getMessage()); 
        return back()->with('error', 'Failed to send email.'); 
    }

    $registeredUsers = $event->registrations()
        ->with('user')
        ->get()
        ->pluck('user'); 
    foreach ($registeredUsers as $user) { 
        try { 
            Mail::to($user->email)->send(new EventUpdated($event)); 
        } catch (Exception $e) { 
            Log::error("Email sending failed to registered user (ID: {$user->id}): " . $e->getMessage()); 
        } 
    } 

    return redirect()
        ->route('events.show', $event->id)
        ->with('success', 'Event updated successfully.');
}

This method updates an event after validating the current user's authorization and the request data. It sends an email notification about the event update to the event creator and all registered users. If email sending fails, it logs the error and returns an error message. Then, it redirects to the event's detail page with a success message.

Add the RegistrationController's methods

Now, you need to create the logistics of users registering and unregistering for an event. First, make sure the following files are imported on the top of  app/Http/Controllers/RegisterController.php:

use App\Models\Event; 
use App\Mail\EventRegistered; 
use Illuminate\Support\Facades\Mail; 
use Exception; 
use Illuminate\Support\Facades\Log;

Next, update the store() method with the following:

public function store(Request $request) 
{ 
    $user_id = auth()->id(); 
    $event_id = $request->event_id; 
    $exists = Registration::where('user_id', $user_id)->where('event_id', $event_id)->exists(); 

    if ($exists) { 
        return back()->with('error', 'You are already registered for this event.'); 
    }

    Registration::create([ 'user_id' => $user_id, 'event_id' => $event_id, ]); 
    $event = Event::findOrFail($event_id); 
    try { 
        Mail::to($request->user())->send(new EventRegistered($event)); 
    } catch (Exception $e) { 
        Log::error('Email sending failed: ' . $e->getMessage()); 
        return back()->with('error', 'Failed to send email.'); 
    } 

    return back()->with('success', 'Registration successful.');
}

This method handles the registration process for an event. It first checks if the currently authenticated user is already registered for the specified event to prevent duplicate registrations. If not already registered, it proceeds to create a new registration record linking the user to the event. 

After successfully registering, it attempts to send an email notification to the user confirming their registration. If the email sending process fails, it logs the error and returns an error message. Then, the method concludes by returning a success message indicating the successful registration.

Finally update the destroy() method with the following:

public function destroy($registrationId) 
{ 
    $registration = Registration::findOrFail($registrationId); 
    if ($registration->user_id !== auth()->id()) { 
        return back()->with('error', 'You do not have permission to cancel this registration.'); 
    } 
    $registration->delete(); 
    return back()->with('success', 'Registration canceled successfully.'); 
}

This method facilitates the cancellation of an event registration. It locates the registration by its ID and checks if the currently authenticated user is the one who made the registration. If the user is not the registrant, it returns an error message indicating the lack of permission to cancel. If the check passes, it deletes the registration from the database and returns a success message confirming the cancellation.

Implement the dashboard

Now, you need to build out the functionality that lists events created and attended by a user. Run the following command to generate a dashboard controller:

./vendor/bin/sail php artisan make:controller DashboardController

Then, replace the content of this controller (app/Http/Controllers/DashboardController.php) with the following:

<?php

namespace App\Http\Controllers;

use App\Models\Event;
use Carbon\Carbon;

class DashboardController extends Controller
{
    public function dashboard()
    {
        $userEvents = Event::where('user_id', auth()->id())->get();
        return view('dashboard', compact('userEvents'));
    }

    public function attending() {
        $userId = auth()->id();
        $today = Carbon::today()->toDateString();

        $registeredUpcomingEvents = Event::whereHas('registrations', function ($query) use ($userId) {
            $query->where('user_id', $userId);
        })
        ->where('date', '>=', $today)
        ->get();
        $registeredPastEvents = Event::whereHas('registrations', function ($query) use ($userId) {
            $query->where('user_id', $userId);
        })
        ->where('date', '<', $today)
        ->get();

        return view('attending', compact('registeredUpcomingEvents', 'registeredPastEvents'));
    }
}

The dashboard method retrieves all events created by the authenticated user and passes them to the dashboard view. The attending method fetches events the user is registered to attend, separating them into upcoming and past events based on the current date. Both sets of data are then passed to the attending view. This allows users to view their own events and the events they are attending, organized by date.

Given these new methods, you need to update the routes\web.php file. First, include the dashboard controller at the top of the file

use App\Http\Controllers\DashboardController;

Then, replace the default dashboard route with routes to the DashboardController classes:

Route::get('/dashboard', [DashboardController::class, 'dashboard'])
    ->name('dashboard')
    ->middleware(['auth', 'verified']);

Route::get('/attending', [DashboardController::class, 'attending'])
    ->name('attending')
    ->middleware(['auth', 'verified']);

The routes are protected by two middleware classes: auth and verified, meaning that only authenticated and email-verified users can access this route.

Create the application's views

Now, you should create the application's views. The views are typically located in the resources\views directory. To speed up development, Tailwind CSS was used for styling on all views.

Create the homepage view

First, create the homepage. In the welcome.blade.php file, replace the content with the following:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">


    <title>{{ config('app.name') }}</title>


    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />


    <!-- Styles -->
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="antialiased">
<div class="flex flex-col min-h-[100vh]">
    <header class="px-4 lg:px-6 h-14 flex items-center">
        <a class="flex items-center justify-center" href="{{ url('/') }}">
            <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="h-6 w-6"
          >
            <rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect>
            <line x1="16" x2="16" y1="2" y2="6"></line>
            <line x1="8" x2="8" y1="2" y2="6"></line>
            <line x1="3" x2="21" y1="10" y2="10"></line>
          </svg>
            <!-- SVG Logo -->
            <span class="sr-only">{{ config('app.name') }}</span>
        </a>
        <nav class="ml-auto flex gap-4 sm:gap-6">
            @if (Route::has('login'))
                
                @auth
                    <a href="{{ url('/dashboard') }}" class="text-sm font-medium hover:underline underline-offset-4">Dashboard</a>
                @else
                    <a href="{{ route('login') }}" class="text-sm font-medium hover:underline underline-offset-4">Log in</a>


                    @if (Route::has('register'))
                        <a href="{{ route('register') }}" class="text-sm font-medium hover:underline underline-offset-4">Register</a>
                    @endif
                @endauth
                
            @endif
            <!-- Dynamically generate navigation links -->
            {{-- <a class="text-sm font-medium hover:underline underline-offset-4" href="#">Features</a>
            <a class="text-sm font-medium hover:underline underline-offset-4" href="#">Pricing</a>
            <a class="text-sm font-medium hover:underline underline-offset-4" href="#">About</a>
            <a class="text-sm font-medium hover:underline underline-offset-4" href="#">Contact</a> --}}
            
        </nav>
    </header>


    <main class="flex-1">
        <section class="w-full pt-12 md:pt-24 lg:pt-32 border-y">
          <div class="px-4 md:px-6 space-y-10 xl:space-y-16">
            <div class="grid max-w-[1300px] mx-auto gap-4 px-4 sm:px-6 md:px-10 md:grid-cols-2 md:gap-16">
              <div>
                <h1 class="lg:leading-tighter text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl xl:text-[3.4rem] 2xl:text-[3.75rem]">
                  Simplify Your Event Management
                </h1>
                <p class="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400 mt-5">
                  {{ config('app.name') }} is your one-stop solution for seamless event planning and management. Get started today!
                </p>
                <div class="space-x-4 mt-5">
                  <a
                    class="inline-flex h-9 items-center justify-center rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-gray-50 shadow transition-colors hover:bg-gray-900/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:pointer-events-none disabled:opacity-50 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90 dark:focus-visible:ring-gray-300"
                    href="{{route('events.create')}}"
                  >
                    Get Started
                  </a>
                </div>
              </div>
              <div>
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="550" height="310" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                    <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                    <circle cx="9" cy="7" r="4"></circle>
                    <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                    <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                </svg>
                       
              </div>
            </div>
          </div>
        </section>
        <section class="w-full py-12 md:py-24 lg:py-32">
          <div class="container space-y-12 px-4 md:px-6">
            <div class="flex flex-col items-center justify-center space-y-4 text-center">
              <div class="space-y-2">
                <h2 class="text-3xl font-bold tracking-tighter sm:text-5xl">Discover Exciting Events Near You</h2>
                <p class="max-w-[900px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400">
                  Explore a variety of events hosted by our community, from music festivals to tech conferences.
                </p>
              </div>
            </div>
            <div class="mx-auto grid items-start gap-8 sm:max-w-4xl sm:grid-cols-2 md:gap-12 lg:max-w-5xl lg:grid-cols-3">
              <div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
                <div class="flex flex-col space-y-1.5 p-6">
                  <h3 class="text-2xl font-semibold whitespace-nowrap leading-none tracking-tight">Music Festival</h3>
                </div>
                <div class="p-6">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="200" height="200" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-music">
                        <path d="M9 18V5l12-2v13"></path>
                        <circle cx="6" cy="18" r="3"></circle>
                        <circle cx="18" cy="16" r="3"></circle>
                    </svg>
                </div>
              </div>
              <div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
                <div class="flex flex-col space-y-1.5 p-6">
                  <h3 class="text-2xl font-semibold whitespace-nowrap leading-none tracking-tight">Tech Conference</h3>
                </div>
                <div class="p-6">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="200" height="200" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cpu">
                        <rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect>
                        <rect x="9" y="9" width="6" height="6"></rect>
                        <line x1="9" y1="1" x2="9" y2="4"></line>
                        <line x1="15" y1="1" x2="15" y2="4"></line>
                        <line x1="9" y1="20" x2="9" y2="23"></line>
                        <line x1="15" y1="20" x2="15" y2="23"></line>
                        <line x1="20" y1="9" x2="23" y2="9"></line>
                        <line x1="20" y1="14" x2="23" y2="14"></line>
                        <line x1="1" y1="9" x2="4" y2="9"></line>
                        <line x1="1" y1="14" x2="4" y2="14"></line>
                    </svg>
                </div>
              </div>
              <div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
                <div class="flex flex-col space-y-1.5 p-6">
                  <h3 class="text-2xl font-semibold whitespace-nowrap leading-none tracking-tight">Art Exhibition</h3>
                </div>
                <div class="p-6">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="200" height="200" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-palette">
                        <path d="M2.459 18.534C3.969 21.858 6.053 24 12.001 24c8.837 0 12-7.163 12-12 0-7.732-7.045-13-12-13-6.627 0-9.545 4.732-11.542 8.466"></path>
                        <circle cx="6.5" cy="10.5" r="1"></circle>
                        <circle cx="10.5" cy="7.5" r="1"></circle>
                        <circle cx="13.5" cy="11.5" r="1"></circle>
                        <circle cx="17.5" cy="7.5" r="1"></circle>
                    </svg>
                </div>
              </div>
            </div>
          </div>
        </section>
        <section class="w-full py-12 md:py-24 lg:py-32 bg-gray-100 dark:bg-gray-800">
          <div class="container grid items-center justify-center gap-4 px-4 text-center md:px-6">
            <div class="space-y-3">
              <h2 class="text-3xl font-bold tracking-tighter md:text-4xl/tight">What Our Customers Say</h2>
              <p class="mx-auto max-w-[600px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400">
                Hear from our satisfied customers who have transformed their event management with {{ config('app.name') }}.
              </p>
            </div>
            <div class="mx-auto w-full max-w-sm space-y-2">
              <blockquote class="text-lg font-semibold leading-snug lg:text-xl lg:leading-normal xl:text-2xl">
                "{{ config('app.name') }} has revolutionized our event planning process. It's intuitive, easy to use, and has saved us
                countless hours.“
              </blockquote>
              <div>
                <div class="font-semibold">Jane Doe</div>
                <div class="text-sm text-gray-500 dark:text-gray-400">Event Manager, XYZ Corp</div>
              </div>
            </div>
          </div>
        </section>
    </main>


    <footer class="flex flex-col gap-2 sm:flex-row py-6 w-full shrink-0 items-center px-4 md:px-6 border-t">
        <p class="text-xs text-gray-500 dark:text-gray-400">
            © {{ now()->year }} {{ config('app.name') }}. All rights reserved.
        </p>
        <nav class="sm:ml-auto flex gap-4 sm:gap-6">
            <a class="text-xs hover:underline underline-offset-4" href="#">Terms of Service</a>
            <a class="text-xs hover:underline underline-offset-4" href="#">Privacy</a>
        </nav>
    </footer>
</div>
</body>
</html>

The main content section promotes the event management features of the application, including a call-to-action button for creating events, and showcases various event categories with icons.

Additional sections highlight customer testimonials and display contact information. The footer contains copyright information and links to terms of service and privacy policies, providing necessary legal and navigational links for users.

At the top, there is a header with a logo and navigation links that change based on user authentication status, offering links to login, register, or access the dashboard. Here is what the homepage will look like:

Homepage

Create the dashboard view

The dashboard showcases events created by the currently logged in user. Replace the content of dashboard.blade.php with the following:

<x-app-layout>
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                        My Events
                    </h2>
                    <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mt-4">
                        @forelse ($userEvents as $event)
                            <a href="{{ route('events.show', $event->id) }}" class="bg-white rounded-lg shadow-lg overflow-hidden block hover:bg-gray-50 transition duration-150 ease-in-out">
                                <div class="p-6">
                                    <h3 class="font-bold text-lg mb-2">{{ $event->title }}</h3>
                                    <p class="text-gray-600 text-sm mb-4">Date: {{ $event->date->format('F d, Y') }}</p>
                                    <p class="text-gray-600 text-sm">Location: {{ $event->location }}</p>
                                </div>
                            </a>
                        @empty
                            <p>You have not created any events yet.</p>
                        @endforelse
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

The dashboard displays the user's events within a clean, responsive layout using the <x-app-layout> component. It features a container with padding and a maximum width for centering. Inside, a card with a white background and shadow effect contains a header titled "My Events."

The user's events are rendered in a responsive grid, transitioning to two columns on medium screens, and three on large. Each event card links to its detailed view, showing the event's title, formatted date, and location. If no events exist, a fallback message is displayed, ensuring graceful degradation of the UI.

Next, you should create a view that lists the events the user is attending. Still in the resources\views directory, create a file named attending.blade.php and paste the following into the file:

<x-app-layout>
    <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        <div class="bg-white shadow overflow-hidden sm:rounded-lg">
            <div class="px-4 py-5 sm:px-6 border-b border-gray-200">
                <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                    Attending
                </h2>
            </div>
            <div class="mb-6 p-4">
                <div class="flex justify-center gap-4">
                    <x-primary-button id="upcomingBtn">Upcoming Events</x-primary-button>
                    <x-secondary-button id="pastBtn">Past Events</x-secondary-button>
                </div>
            </div>


            <div id="upcomingEvents">
                <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 p-4">
                    @forelse ($registeredUpcomingEvents as $event)
                        <a href="{{ route('events.show', $event->id) }}" class="bg-white rounded-lg shadow-lg overflow-hidden block hover:bg-blue-50 transition duration-150 ease-in-out">
                            <div class="p-6">
                                <h3 class="font-bold text-lg mb-2">{{ $event->title }}</h3>
                                <p class="text-gray-600 text-sm mb-4">Date: {{ $event->date->format('F d, Y') }}</p>
                                <p class="text-gray-600 text-sm mb-4">Time: {{ $event->time }}</p>
                                <p class="text-gray-600 text-sm">Location: {{ $event->location }}</p>
                                <span class="bg-green-300 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-900 dark:text-green-300">Going</span>
                            </div>
                        </a>
                    @empty
                        <p class="text-gray-600">No upcoming events found.</p>
                    @endforelse
                </div>
            </div>


            <div id="pastEvents" class="hidden">
                <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 p-4">
                    @forelse ($registeredPastEvents as $event)
                        <a href="{{ route('events.show', $event->id) }}" class="bg-white rounded-lg shadow-lg overflow-hidden block hover:bg-gray-50 transition duration-150 ease-in-out">
                            <div class="p-6">
                                <h3 class="font-bold text-lg mb-2">{{ $event->title }}</h3>
                                <p class="text-gray-600 text-sm mb-4">Date: {{ $event->date->format('F d, Y') }}</p>
                                <p class="text-gray-600 text-sm mb-4">Time: {{ $event->time }}</p>
                                <p class="text-gray-600 text-sm">Location: {{ $event->location }}</p>
                                <span class="bg-green-300 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-900 dark:text-green-300">Going</span>
                            </div>
                        </a>
                    @empty
                        <p class="text-gray-600">No past events found.</p>
                    @endforelse
                </div>
            </div>
        </div>
    </div>


    <script>
        document.getElementById('upcomingBtn').addEventListener('click', function() {
            document.getElementById('upcomingEvents').style.display = 'block';
            document.getElementById('pastEvents').style.display = 'none';
        });


        document.getElementById('pastBtn').addEventListener('click', function() {
            document.getElementById('upcomingEvents').style.display = 'none';
            document.getElementById('pastEvents').style.display = 'block';
        });
    </script>
</x-app-layout>

This view allows users to toggle between upcoming and past events the user registered for. It displays the events in a grid layout, showing details like the event title, date, time, and location. If there are no events to display, a message indicates that. JavaScript is used to control the visibility of the sections, allowing users to switch between upcoming and past events by clicking the respective buttons.

Create the event view

Now, you need to create views that handle creating an event, editing an event, showing an event and listing upcoming and past events. For creating an event, in the directory resources\views create a directory named events. In this directory, create a file named create.blade.php, and paste the following into the file:

<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <div class="text-left">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                {{ __('Create New Event') }}
            </h2>
        </div>


        <form method="POST" action="{{ route('events.store') }}">
            @csrf
            <!-- Title Field -->
            <input
                type="text"
                name="title"
                placeholder="{{ __('Event Title') }}"
                value="{{ old('title') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Description Field -->
            <textarea
                name="description"
                placeholder="{{ __('Event Description') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            >{{ old('description') }}</textarea>


            <!-- Date Field -->
            <input
                type="date"
                name="date"
                value="{{ old('date') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Time Field -->
            <input
                type="time"
                name="time"
                value="{{ old('time') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Location Field -->
            <input
                type="text"
                name="location"
                placeholder="{{ __('Event Location') }}"
                value="{{ old('location') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Error Messages -->
            @foreach(['title', 'description', 'date', 'time', 'location'] as $field)
                <x-input-error :messages="$errors->get($field)" class="mt-2" />
            @endforeach


            <!-- Submit Button -->
            <x-primary-button class="mt-4">{{ __('Create Event') }}</x-primary-button>
        </form>
    </div>
</x-app-layout>

This template renders a form that allows users to create a new event. The form includes input fields for the event's title, description, date, time, and location, each marked as required.

Upon submission, the form data is sent via a POST request to the events.store route. The template also checks for validation errors for each field and displays any error messages below the corresponding input.

Next, create a file edit.blade.php in the same directory, and enter the following:

<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <div class="text-left">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                {{ __('Edit Event') }}
            </h2>
        </div>


        <form method="POST" action="{{ route('events.update', $event->id) }}">
            @csrf
            @method('PUT')
            <!-- Title Field -->
            <input
                type="text"
                name="title"
                placeholder="{{ __('Event Title') }}"
                value="{{ old('title', $event->title) }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Description Field -->
            <textarea
                name="description"
                placeholder="{{ __('Event Description') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            >{{ old('description', $event->description) }}</textarea>


            <!-- Date Field -->
            <input
                type="date"
                name="date"
                value="{{ old('date', $event->date->format('Y-m-d')) }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Time Field -->
            <input
                type="time"
                name="time"
                value="{{ old('time', $event->time) }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Location Field -->
            <input
                type="text"
                name="location"
                placeholder="{{ __('Event Location') }}"
                value="{{ old('location', $event->location) }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm mt-4"
                required
            />


            <!-- Error Messages -->
            @foreach(['title', 'description', 'date', 'time', 'location'] as $field)
                <x-input-error :messages="$errors->get($field)" class="mt-2" />
            @endforeach


            <!-- Submit Button -->
            <x-primary-button class="mt-4">{{ __('Update Event') }}</x-primary-button>
        </form>
    </div>
</x-app-layout>

This Blade template renders a form for editing an existing event. It pre-fills the form fields with the current event data, allowing the user to update the event's title, description, date, time, and location. The form uses the HTTP PUT method and sends the updated data to the events.update route with the event's ID. Validation errors for each field are displayed below the respective input.

Next, create a file show.blade.php in the same directory and enter the following:

<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <div class="text-left">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                {{ $event->title }}
            </h2>
        </div>


        <div class="bg-white shadow overflow-hidden sm:rounded-lg mt-4">
            <div class="px-4 py-5 sm:px-6">
                <h3 class="text-lg leading-6 font-medium text-gray-900">
                    Event Details
                </h3>
            </div>
            <div class="border-t border-gray-200">
                <dl>
                    <!-- Description -->
                    <div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
                        <dt class="text-sm font-medium text-gray-500">
                            Description
                        </dt>
                        <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
                            {{ $event->description }}
                        </dd>
                    </div>
                    <!-- Date -->
                    <div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
                        <dt class="text-sm font-medium text-gray-500">
                            Date
                        </dt>
                        <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
                            {{ $event->date->format('F d, Y') }}
                        </dd>
                    </div>
                    <!-- Time -->
                    <div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
                        <dt class="text-sm font-medium text-gray-500">
                            Time
                        </dt>
                        <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
                            {{ $event->time }}
                        </dd>
                    </div>
                    <!-- Location -->
                    <div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
                        <dt class="text-sm font-medium text-gray-500">
                            Location
                        </dt>
                        <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
                            {{ $event->location }}
                        </dd>
                    </div>
                </dl>
            </div>
        </div>


        <!-- Registration and Edit Button -->
        <div class="mt-5 flex justify-start space-x-4">
            @if(auth()->id() === $event->user_id)
                <a href="{{ route('events.edit', $event->id) }}" class="inline-flex items-center px-4 py-2 inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150">
                    Edit
                </a>
            
            @endif


            @if($event->isUserRegistered(auth()->id()))
                @php
                    $registration = $event->registrations()->where('user_id', auth()->id())->first();
                @endphp
                <form action="{{ route('registrations.destroy', $registration->id) }}" method="POST" class="inline">
                    @csrf
                    @method('DELETE')
                    
                    <x-danger-button>
                        {{ __('Unregister') }}
                    </x-danger-button>
                </form>
            @else
                <form action="{{ route('registrations.store') }}" method="POST" class="inline">
                    @csrf
                    <input type="hidden" name="event_id" value="{{ $event->id }}">
                    {{-- <button type="submit" class="inline-flex items-center px-4 py-2 bg-green-500 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-green-700 active:bg-green-900 focus:outline-none focus:border-green-900 focus:ring focus:ring-green-300 disabled:opacity-25 transition">
                        Register
                    </button> --}}
                    <x-primary-button>{{ __('Register') }}</x-primary-button>
                </form>
            @endif
        </div>
    </div>
</x-app-layout>

This Blade template displays detailed information about an event, including its title, description, date, time, and location. It also provides conditional controls based on the authenticated user's relationship to the event.

If the authenticated user is the event's creator, an " Edit" button is displayed, allowing them to navigate to the event's edit page. If the user is registered for the event, an " Unregister" button appears, enabling them to cancel their registration. If the user is not registered, a " Register" button is shown, allowing them to sign up for the event.

Finally, create a file index.blade.php in the same directory and enter the following:

<x-app-layout>
    <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        @if (session('success')) 	<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">  	   <strong class="font-bold">Success!</strong> 
   <span class="block sm:inline">{{ session('success') }}</span> 
           </div>         @elseif (session('error'))            <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">  	   <strong class="font-bold">Error!</strong> <span class="block sm:inline">{{ session('error') }}
   </span> 
</div>
        @endif 
        <div class="bg-white shadow overflow-hidden sm:rounded-lg">
            <div class="px-4 py-5 sm:px-6 border-b border-gray-200">
                <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                    Events
                </h2>
            </div>
            <div class="mb-6 p-4">
                <div class="flex justify-center gap-4">
                    <x-primary-button id="upcomingBtn">Upcoming Events</x-primary-button>
                    <x-secondary-button id="pastBtn">Past Events</x-secondary-button>
                </div>
            </div>
            <div id="upcomingEvents">
                <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 p-4">
                    @forelse ($upcomingEvents as $event)
                        <a href="{{ route('events.show', $event->id) }}" class="bg-white rounded-lg shadow-lg overflow-hidden block hover:bg-blue-50 transition duration-150 ease-in-out">
                            <div class="p-6">
                                <h3 class="font-bold text-lg mb-2">{{ $event->title }}</h3>
                                <p class="text-gray-600 text-sm mb-4">Date: {{ $event->date->format('F d, Y') }}</p>
                                <p class="text-gray-600 text-sm mb-4">Time: {{ $event->time }}</p>
                                <p class="text-gray-600 text-sm">Location: {{ $event->location }}</p>
                            </div>
                        </a>
                    @empty
                        <p class="text-gray-600">No upcoming events found.</p>
                    @endforelse
                </div>
            </div>
            <div id="pastEvents" class="hidden">
                <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 p-4">
                    @forelse ($pastEvents as $event)
                        <a href="{{ route('events.show', $event->id) }}" class="bg-white rounded-lg shadow-lg overflow-hidden block hover:bg-gray-50 transition duration-150 ease-in-out">
                            <div class="p-6">
                                <h3 class="font-bold text-lg mb-2">{{ $event->title }}</h3>
                                <p class="text-gray-600 text-sm mb-4">Date: {{ $event->date->format('F d, Y') }}</p>
                                <p class="text-gray-600 text-sm mb-4">Time: {{ $event->time }}</p>
                                <p class="text-gray-600 text-sm">Location: {{ $event->location }}</p>
                            </div>
                        </a>
                    @empty
                        <p class="text-gray-600">No past events found.</p>
                    @endforelse
                </div>
            </div>
        </div>
    </div>
    <script>
        document.getElementById('upcomingBtn').addEventListener('click', function() {
            document.getElementById('upcomingEvents').style.display = 'block';
            document.getElementById('pastEvents').style.display = 'none';
        });
        document.getElementById('pastBtn').addEventListener('click', function() {
            document.getElementById('upcomingEvents').style.display = 'none';
            document.getElementById('pastEvents').style.display = 'block';
        });
    </script>
</x-app-layout>

This template presents a dynamic event listing page with flash messaging for user feedback. At the top, a header labeled "Events" introduces the page, accompanied by two buttons that allow users to toggle between viewing upcoming and past events. Below, any flash messages from event creation attempts, whether successful or failed, are displayed prominently to provide immediate feedback to the user.

The event data is rendered in a responsive grid format, where each event's title, date, time, and location are displayed, and each event links to its detail page. The @forelse directive is used to handle cases where there are no events to display.

The JavaScript code attached to the buttons enables the toggling functionality between upcoming and past event sections, ensuring only the relevant events are shown based on the user's selection.

Add view navigation

Next, add links to the navigation menu provided by Laravel Breeze by updating the navigation section in resources\views\layouts\navigation.blade.php file:

<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
   <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
       {{ __('Dashboard') }}
   </x-nav-link>
   <x-nav-link :href="route('attending')" :active="request()->routeIs('attending')">
       {{ __('Attending') }}
   </x-nav-link>
   <x-nav-link :href="route('events.index')" :active="request()->routeIs('events.index')">
        {{ __('Explore') }}
    </x-nav-link>
</div>

For mobile screens:

<!-- Responsive Navigation Menu -->
<div class="pt-2 pb-3 space-y-1">
   <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
       {{ __('Dashboard') }}
   </x-responsive-nav-link>
   <x-responsive-nav-link :href="route('attending')" :active="request()->routeIs('attending')">
       {{ __('Attending') }}
   </x-responsive-nav-link>
   <x-responsive-nav-link :href="route('events.index')" :active="request()->routeIs('events.index')"> 
       {{ __('Explore') }} 
   </x-responsive-nav-link>
</div>

Now, update the navigation items for creating an event:

<!-- Settings Dropdown →
<div class="hidden sm:flex sm:items-center sm:ms-6">
   <x-nav-link :href="route('events.create')" :active="request()->routeIs('events.create')" class="mr-4"> {{ __('Create Event') }} 
   </x-nav-link>
   …
</div>

For mobile screens:

<!-- Responsive Settings Options --> 
<div class="pt-4 pb-1 border-t border-gray-200"> 
  <div class="px-4"> 
    <div class="font-medium text-base text-gray-800">
      {{ Auth::user()->name }}
   </div> 
   <div class="font-medium text-sm text-gray-500">
     {{ Auth::user()->email }}
  </div> 
  </div> 
  <div class="mt-3 space-y-1">
   <x-responsive-nav-link :href="route('events.create')" :active="request()->routeIs('events.create')" class="mr-4"> 
    {{ __('Create Event') }} 
  </x-responsive-nav-link>
  // The rest of the code
</div>

An authenticated user would now see the following links on the navigation pane:

Navigation

Create the notification mails template

When you generated the mailable classes, the corresponding views were also created. You can find these at the resources\views\mail\events directory. You should update these files with information relevant to their purpose. Replace the content of the created.blade.php file with the following:

<x-mail::message>

# {{ $title }} has been created!

**Congratulations!** Now, it's time to share your beautiful new event page and invite your guests.

**Date:** {{ $date }}

**Location:** {{ $location }}

<x-mail::button :url="config('app.url') . '/events/' . $eventID ">
Event Page
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

This Blade template is used to generate an email notification informing the recipient that a new event has been created. It dynamically inserts the event's title, date, and location into the email body. The template also includes a button that links to the event's page, using the event ID to construct the URL. The email is concluded with a signature that includes the application's name.

Next, replace the content of the registered.blade.php file with the following:

<x-mail::message>

# You have registered for {{ $title }}

**Date:** {{ $date }}

**Location:** {{ $location }}

<x-mail::button :url="config('app.url') . '/events/' . $eventID ">
Event Page
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

This Blade template generates an email notification confirming the recipient's registration for an event. It displays the event's title, date, and location, and includes a button linking to the event's page, constructed using the event ID. The email ends with a signature featuring the application's name.

Finally, replace the content of the updated.blade.php file with the following:

<x-mail::message>

# {{ $title }} has been updated!

Click on the Event Page button to see the updated details of this event

**Date:** {{ $date }}

**Location:** {{ $location }}

<x-mail::button :url="config('app.url') . '/events/' . $eventID ">
Event Page
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

This Blade template is used to create an email notification informing the recipient that an event has been updated. It displays the event's updated title, date, and location, and includes a button linking to the event's page, allowing the recipient to view the updated details. The email concludes with a signature that includes the application's name.

Congratulations, you’ve finished coding the app!

Test your work

It's time to test that the app works properly. In your browser, visit http://localhost. Click on Register on the navigation bar and create an account. You will be redirected to the dashboard, which is currently empty. Click Create Event on the navigation bar, where you will see the form for creating a new event:

New event form

Enter the details of an event and click the Create Event button to submit the form. This redirects you to the events page which will look similar to this after creating several events:

Events page

Click on an event to view details of that event. Here you can register for the event or edit the event if you are the creator:

Event details for a chess meetup with friends including date, time, location, and registration button.

Click on Register. You should receive an email confirming your registration for the event:

Screenshot of an email confirming registration for a chess event in Toronto, Canada on August 12, 2024.

You would also receive similar emails when you create or update an event.

That's how to build an event management app with Laravel and Twilio SendGrid

In this tutorial, you successfully built a simple event management application with Laravel, integrating Twilio SendGrid for seamless email delivery. By following the steps, you’ve gained hands-on experience with setting up email notifications and managing events efficiently. To continue improving your app, consider exploring Twilio SendGrid’s advanced features like email analytics and templates.

You can find the full source code of this app here.

Caleb Oki is a Senior Software Developer with extensive experience in building robust applications using Laravel, TypeScript, and Vue. He has a passion for teaching and has created backend development courses, sharing his expertise with students and professionals alike. Connect with him on Linkedin or via email at caleboki@gmail.com.

Event icons created by Aron_940 - Flaticon.