Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here
Premium Members Only
Join to unlock this tutorial and all of our courses.
Tutorial Premium Tutorial

Timeslot Checkout Page with Reservation Timer: Livewire and Alpine

August 23, 2023
16 min read

Tutorial last revisioned on March 17, 2024 with Laravel 11 and LIvewire 3

If you want your user to reserve an item for X minutes before confirming the purchase, this tutorial will show you how to do it, with a project of timeslot booking and reservation timer, built with TALL stack - Livewire and Alpine.


Why Reserve For 15 Minutes?

It's a typical behavior in ticket booking systems, or anywhere where the supply of items is strictly limited. You wouldn't want to sell 100 plane tickets when a plane seats only 30 people, would you?

We have two typical scenarios here:

Scenario one - User A buys the product:

  • User A adds a product to their cart
  • User B sees that the product is no longer available
  • User A buys the product within 15 minutes
  • User B was never shown the product

Scenario two - User A abandons the page:

  • User A adds a product to their cart
  • User B sees that the product is no longer available
  • User A waits 15 minutes and doesn't buy the product
  • User B can now buy the product
  • User B buys the product

We will implement exactly that: a one-page checkout solution with Livewire and Alpine, including 15:00 countdown timer and automatic Laravel command freeing up the item after those 15 minutes are over without the purchase.

In our case, it will be an appointment system with timeslots: the timeslots will be reserved for 15 minutes and then become available again for other users, in case the current user doesn't confirm the reservation.

This tutorial will be a step-by-step one, explaining the Livewire component for picking the dates along the way.


Database Structure - Basic Setup

Our implementation of the reservation system will be based on the following database structure:

Key fields from the database are:

  • confirmed - A boolean field indicating the reservation has confirmation. This field is set to true when the user completes the checkout process.
  • reserved_at - A datetime field that indicates when the reservation was created. This field is set to the current datetime when the user adds a product to their cart.

Any other field can be added based on the application requirements. We didn't want to focus on them in this tutorial.


Project Setup

For this tutorial, we will be using Livewire 3.0 with Laravel Breeze. You can install it by following the guide if you haven't already.


Creating an Appointment Reservation System

Let's start by creating a Livewire component that will be used to make a reservation, showing available timeslots for certain dates. We'll call it DateTimeAvailability:

php artisan make:livewire DateTimeAvailability

Next, for our tutorial, we will load the component in the dashboard.blade.php file:

resources/views/dashboard.blade.php

<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Date time availability') }}
</h2>
</x-slot>
 
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="font-sans text-gray-900 antialiased">
<div class="w-full sm:max-w-5xl mt-6 mb-6 px-6 py-8 bg-white shadow-md overflow-hidden sm:rounded-lg">
<livewire:date-time-availability></livewire:date-time-availability>
</div>
</div>
</div>
</div>
</x-app-layout>

Before we dive into our Livewire component - we need to prepare our Layout to accept stackable Scripts:

resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
{{-- ... --}}
</head>
<body class="font-sans antialiased">
 
{{-- ... --}}
 
@stack('scripts')
</body>
</html>

Now that this is done, we can customize our Livewire...

Premium Members Only

This advanced tutorial is available exclusively to Laravel Daily Premium members.

Premium membership includes:

Access to all premium tutorials
Video and Text Courses
Private Discord Channel

Comments & Discussion

LA
Luis Antonio Parrado ✓ Link copied!

Pikaday styles missing in this tutorial!

M
Modestas ✓ Link copied!

Oh! You are right, please add:

<link href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css" rel="stylesheet">

To your app.blade.php. I'm updating the tutorial. Thank you!

RA
Richard A. Hoyle ✓ Link copied!

"livewire/livewire": "^3.0" v3.0.0 Version 3 was just released. 8/24/2023 at 5:00 PM America/Chicago Time

All is installing just fine.

RA
Richard A. Hoyle ✓ Link copied!

Hello again almost everything is working just fine; hoverer for some reason when you try to confirm the appointment it is not getting sent to the database; the start time is but the confirmed is not.

Not sour what is missing hear!

The public function confirmAppointment(): void section is in the DateTimeAvailability.php file but for some reason it in not getting sent to the database. It is getting redirected back and the table has it taken it is just not in the database.

M
Modestas ✓ Link copied!

If it is redirecting you to the page with the time slot selection - that means that this code was triggered:

        $appointment = Appointment::find($this->appointmentID);
        if (!$appointment || Carbon::parse($appointment->reserved_at)->diffInMinutes(now()) > config('app.appointmentReservationTime')) {
            $this->redirectRoute('dashboard');
            return;
        }

If that's the case, I would check fi the appointment was not deleted from the database and timer still had time remaining. Can you try to debug what's going on with this part?

RA
Richard A. Hoyle ✓ Link copied!

This is taken from the database as you can see the confirmed has 0 and not 1 or am I reading this wrong ?


id  start_time            confirmed            reserved_at              created_at 
2  2023-08-24 08:30:00       0               2023-08-24 23:48:11  2023-08-24 23:48:11
updated_at
2023-08-24 23:48:11



 public function confirmAppointment(): void
    {
        $appointment = Appointment::find($this->appointmentID);
        if (!$appointment || Carbon::parse($appointment->reserved_at)->diffInMinutes(now()) > config('app.appointmentReservationTime')) {
            $this->redirectRoute('dashboard');
            return;
        }

        $appointment->confirmed = true;
        $appointment->save();

        $this->redirectRoute('appointment-confirmed', $this->appointmentID);
    }


the page has the time slot taken It appears that nothing is being sent to the database the reserved_at and the confirmed are not being changed.

RA
Richard A. Hoyle ✓ Link copied!

fixed the problem I had to move the

$appointment->confirmed = true; $appointment->save();

to above the

return;

However the reserved_at time is not changed it is still the same as the created_at time; However the updated_at time is changed.

M
Modestas ✓ Link copied!

you have removed safety checks by doing so :)

RA
Richard A. Hoyle ✓ Link copied!

Ok so how do we git this to work?

M
Modestas ✓ Link copied!

Dump both if conditions to see which gets teiggered. that way you will know what's causing your issue

also, have you set the expiration timer correctly? It impacts what happens when you click confirm

RA
Richard A. Hoyle ✓ Link copied!

I downloaded the GitHub repository and set it up however the reserved_at is still the same as the created_at; that is to say that the time on both of them are the same I know that the day would be but I would think that the time would be at least a secant or two different or be matching the updated_at wouldn’t it?

M
Modestas ✓ Link copied!

We are checking the time difference from the reservation time, so it's the same as creation time.

in a way, we look at how much time passed since the reservation, and not how much time is still left there

DV
Daniela Venegas ✓ Link copied!

I wonder if it would be really more expensive to load the Appointment as: public Appointment $appointment within the mount() method and then use it on any function then having two different Appointment::find for the two functions?

M
Modestas ✓ Link copied!

The problem with this - then our Appointment model becomes publicly visible. For example, it can be seen with all of it's parameters sent to the user.

This could be a security issue. We would also need to limit which fields we load there, to not leak information.

So having it in two functions - seems more logical. Especially since they only run if you click on them.

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.