#5CRSE Event Discovery & Booking Implementation Plan

#Overview

The event discovery and booking flow is a core part of the 5CRSE platform, allowing users to find events (both from Ticketmaster and custom/partner events), view details, and book luxury transportation. This document outlines the implementation plan for creating a seamless, visually appealing event discovery and booking experience.

#User Journey & Flow

flowchart TD
    A[Homepage] -->|"Browse Events"| B[Event Discovery]
    B -->|"Apply Filters"| B
    B -->|"Select Event"| C[Event Details]
    C -->|"Book Event"| D[Transportation Selection]
    D -->|"Select Vehicle"| E[Booking Details]
    E -->|"Add Guests"| F[Review & Checkout]
    F -->|"Complete Payment"| G[Booking Confirmation]
    
    subgraph "Event Discovery"
    B1[Filter Panel] --> B2[Search]
    B2 --> B3[Event Grid]
    B3 --> B4[Pagination]
    end
    
    subgraph "Event Sources"
    S1[Ticketmaster Events]
    S2[Custom Partner Events]
    S3[User-Created Events]
    end
    
    S1 --> B
    S2 --> B
    S3 --> B
    
    subgraph "Booking Flow"
    D1[Vehicle Options] --> E1[Guest Management]
    E1 --> F1[Price Breakdown]
    F1 --> F2[Payment Processing]
    end
    
    D --> D1
    E --> E1
    F --> F1
    F1 --> F2

#Implementation Phases

gantt
    title Event Discovery & Booking Implementation Plan
    dateFormat  YYYY-MM-DD
    section Phase 1: Core Discovery
    Event Grid UI                 :2025-04-01, 5d
    Basic Filtering               :2025-04-06, 4d
    Ticketmaster Integration      :2025-04-10, 7d
    
    section Phase 2: Advanced Discovery
    Advanced Filtering            :2025-04-17, 4d
    Search Functionality          :2025-04-21, 3d
    Event Detail Pages            :2025-04-24, 5d
    
    section Phase 3: Booking Flow
    Transportation Selection      :2025-04-29, 4d
    Booking Details Form          :2025-05-03, 3d
    Guest Information             :2025-05-06, 3d
    
    section Phase 4: Checkout & Completion
    Order Summary                 :2025-05-09, 3d
    Payment Integration           :2025-05-12, 5d
    Confirmation Pages            :2025-05-17, 3d
    
    section Phase 5: Enhancements
    Map View Integration          :future, 0d
    Personalized Recommendations  :future, 0d
    Enhanced UI Animations        :future, 0d

#Detailed Component Implementations

#1. Event Discovery Grid

The event discovery grid is the main interface for users to browse and find events of interest.

User Interface

  • Responsive grid of event cards
  • Visually appealing cards with event images
  • Clear event information display
  • Hover effects and interactive elements
  • Quick-access booking buttons

Component Structure

// src/components/EventDiscovery/EventGrid.tsx
import React from 'react';
import { EventCard } from './EventCard';
import { Pagination } from '../Pagination';
import { Spinner } from '../ui/Spinner';

interface EventGridProps {
  events: any[];
  loading: boolean;
  totalEvents: number;
  currentPage: number;
  eventsPerPage: number;
  onPageChange: (page: number) => void;
}

export const EventGrid: React.FC<EventGridProps> = ({
  events,
  loading,
  totalEvents,
  currentPage,
  eventsPerPage,
  onPageChange
}) => {
  if (loading) {
    return (
      <div className="flex justify-center items-center py-20">
        <Spinner size="lg" />
      </div>
    );
  }
  
  if (events.length === 0) {
    return (
      <div className="text-center py-16">
        <h3 className="text-2xl font-bold text-white mb-2">No Events Found</h3>
        <p className="text-gray-400">
          Try adjusting your filters or search for different events.
        </p>
      </div>
    );
  }
  
  return (
    <div className="space-y-8">
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {events.map((event) => (
          <EventCard key={event.id} event={event} />
        ))}
      </div>
      
      <Pagination
        currentPage={currentPage}
        totalPages={Math.ceil(totalEvents / eventsPerPage)}
        onPageChange={onPageChange}
      />
    </div>
  );
};

#2. Event Card Component

Each event is displayed in a visually appealing card that showcases key information.

// src/components/EventDiscovery/EventCard.tsx
import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { formatDateTime } from '@/utilities/formatDateTime';
import { CalendarIcon, MapPinIcon, TicketIcon } from '@heroicons/react/24/outline';

interface EventCardProps {
  event: any;
}

export const EventCard: React.FC<EventCardProps> = ({ event }) => {
  // Generate the event image URL or use a fallback
  const imageUrl = event.images && event.images.length > 0
    ? event.images[0].image?.url || '/images/event-placeholder.jpg'
    : '/images/event-placeholder.jpg';
  
  // Determine if event is from Ticketmaster or custom
  const isTicketmasterEvent = Boolean(event.ticketmaster_id);
  const eventSource = isTicketmasterEvent ? 'Ticketmaster' : 'Partner Event';
  
  return (
    <div className="bg-gray-900 rounded-lg overflow-hidden group hover:ring-2 hover:ring-gold transition-all">
      <div className="relative aspect-[16/9]">
        <Image
          src={imageUrl}
          alt={event.event_name}
          fill
          className="object-cover transition-transform group-hover:scale-105"
        />
        
        {/* Event source badge */}
        <div className="absolute top-4 left-4 bg-black bg-opacity-70 rounded-full px-3 py-1 text-sm">
          <span className={isTicketmasterEvent ? 'text-blue-400' : 'text-gold'}>
            {eventSource}
          </span>
        </div>
        
        {/* Price badge */}
        {event.event_price && (
          <div className="absolute top-4 right-4 bg-gold text-black font-bold rounded-full px-3 py-1 text-sm">
            ${typeof event.event_price === 'number' ? event.event_price.toFixed(0) : event.event_price}
          </div>
        )}
      </div>
      
      <div className="p-5">
        <h3 className="text-xl font-bold text-white mb-2 line-clamp-2">
          {event.event_name}
        </h3>
        
        <div className="space-y-2 mb-4">
          {/* Date & Time */}
          <div className="flex items-start">
            <CalendarIcon className="w-5 h-5 text-gold mr-2 mt-0.5" />
            <span className="text-gray-300">
              {formatDateTime(event.event_date)}
            </span>
          </div>
          
          {/* Location */}
          <div className="flex items-start">
            <MapPinIcon className="w-5 h-5 text-gold mr-2 mt-0.5" />
            <span className="text-gray-300 line-clamp-1">
              {event.location?.venue || 'Location TBD'}
            </span>
          </div>
          
          {/* Category */}
          {event.categories && event.categories.length > 0 && (
            <div className="flex items-start">
              <TicketIcon className="w-5 h-5 text-gold mr-2 mt-0.5" />
              <div className="flex flex-wrap gap-1">
                {event.categories.map((category: any) => (
                  <span 
                    key={category.id} 
                    className="text-xs bg-gray-800 text-gray-300 px-2 py-1 rounded"
                  >
                    {category.name}
                  </span>
                ))}
              </div>
            </div>
          )}
        </div>
        
        <div className="flex items-center justify-between pt-2 border-t border-gray-800">
          <Link
            href={`/events/${event.id}`}
            className="text-gold font-medium hover:underline"
          >
            View Details
          </Link>
          
          <Link
            href={`/book/${event.id}`}
            className="bg-gold text-black px-4 py-2 rounded font-medium hover:bg-yellow-500 transition-colors"
          >
            Book Now
          </Link>
        </div>
      </div>
    </div>
  );
};

#3. Event Filtering System

A comprehensive filtering system that allows users to narrow down events based on various criteria.

// src/components/EventDiscovery/EventFilters.tsx
import React, { useState } from 'react';
import { DateRangePicker } from '@/components/DateRangePicker';
import { FilterChip } from '@/components/ui/FilterChip';
import { Slider } from '@/components/ui/Slider';
import { FilterIcon, XMarkIcon } from '@heroicons/react/24/outline';

interface EventFiltersProps {
  filters: {
    categories: string[];
    dateRange: { start: Date | null; end: Date | null };
    location: string;
    distance: number;
    priceRange: { min: number; max: number };
  };
  categories: { id: string; name: string }[];
  maxPrice: number;
  onChange: (filters: any) => void;
  onReset: () => void;
}

export const EventFilters: React.FC<EventFiltersProps> = ({
  filters,
  categories,
  maxPrice,
  onChange,
  onReset
}) => {
  const [isOpen, setIsOpen] = useState(false);
  
  const handleCategoryToggle = (categoryId: string) => {
    const newCategories = filters.categories.includes(categoryId)
      ? filters.categories.filter(id => id !== categoryId)
      : [...filters.categories, categoryId];
    
    onChange({ ...filters, categories: newCategories });
  };
  
  const handleDateRangeChange = (dateRange: { start: Date | null; end: Date | null }) => {
    onChange({ ...filters, dateRange });
  };
  
  const handlePriceRangeChange = (priceRange: { min: number; max: number }) => {
    onChange({ ...filters, priceRange });
  };
  
  const handleLocationChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    onChange({ ...filters, location: e.target.value });
  };
  
  const handleDistanceChange = (distance: number) => {
    onChange({ ...filters, distance });
  };
  
  return (
    <div className="bg-gray-900 rounded-lg overflow-hidden">
      {/* Mobile filter toggle */}
      <div className="md:hidden p-4 border-b border-gray-800">
        <button
          onClick={() => setIsOpen(!isOpen)}
          className="flex items-center justify-between w-full"
        >
          <span className="text-lg font-medium text-white">Filters</span>
          <FilterIcon className="w-5 h-5 text-gold" />
        </button>
      </div>
      
      {/* Filter content - responsive */}
      <div className={`${isOpen ? 'block' : 'hidden'} md:block p-5 space-y-6`}>
        {/* Header with reset */}
        <div className="flex justify-between items-center">
          <h3 className="text-xl font-bold text-white">Filter Events</h3>
          <button
            onClick={onReset}
            className="text-sm text-gold hover:underline flex items-center"
          >
            <XMarkIcon className="w-4 h-4 mr-1" />
            Reset Filters
          </button>
        </div>
        
        {/* Event Categories */}
        <div>
          <h4 className="text-lg font-medium text-white mb-3">Event Type</h4>
          <div className="flex flex-wrap gap-2">
            {categories.map((category) => (
              <FilterChip
                key={category.id}
                label={category.name}
                selected={filters.categories.includes(category.id)}
                onClick={() => handleCategoryToggle(category.id)}
              />
            ))}
          </div>
        </div>
        
        {/* Date Range */}
        <div>
          <h4 className="text-lg font-medium text-white mb-3">Date Range</h4>
          <DateRangePicker
            startDate={filters.dateRange.start}
            endDate={filters.dateRange.end}
            onChange={handleDateRangeChange}
          />
        </div>
        
        {/* Location */}
        <div>
          <h4 className="text-lg font-medium text-white mb-3">Location</h4>
          <input
            type="text"
            value={filters.location}
            onChange={handleLocationChange}
            placeholder="City or zip code"
            className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
          />
          
          <div className="mt-3">
            <div className="flex justify-between items-center mb-2">
              <span className="text-sm text-gray-400">Distance</span>
              <span className="text-sm text-gray-400">{filters.distance} miles</span>
            </div>
            <Slider
              min={5}
              max={100}
              step={5}
              value={filters.distance}
              onChange={handleDistanceChange}
            />
          </div>
        </div>
        
        {/* Price Range */}
        <div>
          <h4 className="text-lg font-medium text-white mb-3">Price Range</h4>
          <div className="flex justify-between items-center mb-2">
            <span className="text-sm text-gray-400">${filters.priceRange.min}</span>
            <span className="text-sm text-gray-400">${filters.priceRange.max === maxPrice ? filters.priceRange.max + '+' : filters.priceRange.max}</span>
          </div>
          <Slider
            min={0}
            max={maxPrice}
            step={10}
            range
            value={[filters.priceRange.min, filters.priceRange.max]}
            onChange={([min, max]) => handlePriceRangeChange({ min, max })}
          />
        </div>
        
        {/* Apply button - mobile only */}
        <div className="md:hidden">
          <button
            onClick={() => setIsOpen(false)}
            className="w-full py-3 bg-gold text-black rounded-md font-medium"
          >
            Apply Filters
          </button>
        </div>
      </div>
    </div>
  );
};

#4. Event Details Page

This page displays comprehensive information about an event along with booking options.

// src/app/(frontend)/events/[id]/page.tsx
import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { getEventById } from '@/lib/events';
import { EventGallery } from '@/components/EventDetails/EventGallery';
import { EventInfo } from '@/components/EventDetails/EventInfo';
import { EventLocation } from '@/components/EventDetails/EventLocation';
import { RelatedEvents } from '@/components/EventDetails/RelatedEvents';
import { TransportationOptions } from '@/components/EventDetails/TransportationOptions';

export default async function EventDetailsPage({ params }: { params: { id: string } }) {
  const event = await getEventById(params.id);
  
  if (!event) {
    return (
      <div className="container mx-auto px-4 py-20 text-center">
        <h1 className="text-3xl font-bold text-white mb-4">Event Not Found</h1>
        <p className="text-gray-400 mb-8">
          The event you're looking for doesn't exist or has been removed.
        </p>
        <Link
          href="/events"
          className="inline-block px-6 py-3 bg-gold text-black rounded-md font-medium"
        >
          Browse Events
        </Link>
      </div>
    );
  }
  
  return (
    <div className="bg-black min-h-screen">
      {/* Hero section with main image */}
      <div className="relative h-[50vh] lg:h-[60vh]">
        <Image
          src={event.images?.[0]?.image?.url || '/images/event-placeholder.jpg'}
          alt={event.event_name}
          fill
          className="object-cover"
          priority
        />
        <div className="absolute inset-0 bg-gradient-to-t from-black to-transparent" />
        
        <div className="absolute bottom-0 left-0 w-full p-8">
          <div className="container mx-auto">
            <h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
              {event.event_name}
            </h1>
            <div className="flex flex-wrap items-center gap-4 text-gray-300">
              <span className="bg-gold text-black px-3 py-1 rounded-full text-sm font-medium">
                ${typeof event.event_price === 'number' ? event.event_price.toFixed(0) : event.event_price}
              </span>
              <span>{new Date(event.event_date).toLocaleDateString('en-US', { 
                weekday: 'long',
                month: 'long',
                day: 'numeric',
                year: 'numeric'
              })}</span>
              <span>{event.location?.venue}</span>
            </div>
          </div>
        </div>
      </div>
      
      <div className="container mx-auto px-4 py-12">
        <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
          {/* Main content */}
          <div className="lg:col-span-2 space-y-8">
            {/* Event description */}
            <div className="bg-gray-900 rounded-lg p-6">
              <h2 className="text-2xl font-bold text-white mb-4">About This Event</h2>
              <div className="prose prose-invert max-w-none">
                {event.description}
              </div>
            </div>
            
            {/* Event gallery */}
            {event.images && event.images.length > 1 && (
              <EventGallery images={event.images} />
            )}
            
            {/* Event information */}
            <EventInfo event={event} />
            
            {/* Event location */}
            <EventLocation location={event.location} />
          </div>
          
          {/* Sidebar */}
          <div className="space-y-6">
            {/* Transportation booking */}
            <div className="bg-gray-900 rounded-lg p-6 sticky top-24">
              <h2 className="text-2xl font-bold text-white mb-4">Book Transportation</h2>
              <p className="text-gray-300 mb-6">
                Arrive in style with our premium transportation service. Select your preferred vehicle option below.
              </p>
              
              <TransportationOptions eventId={event.id} />
              
              <div className="mt-6 text-sm text-gray-400">
                <p>* All transportation includes professional chauffeur service</p>
                <p>* Prices include 3-hour minimum booking</p>
              </div>
            </div>
          </div>
        </div>
        
        {/* Related events */}
        <div className="mt-16">
          <h2 className="text-2xl font-bold text-white mb-6">Similar Events You Might Like</h2>
          <RelatedEvents 
            currentEventId={event.id} 
            categories={event.categories?.map(cat => cat.id) || []}
          />
        </div>
      </div>
    </div>
  );
}

#5. Transportation Options Component

This component displays available vehicles for booking with an event.

// src/components/EventDetails/TransportationOptions.tsx
import React, { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { CheckIcon } from '@heroicons/react/24/outline';

interface TransportationOptionsProps {
  eventId: string;
}

const vehicles = [
  {
    id: 'escalade',
    name: 'Cadillac Escalade',
    image: '/images/vehicles/escalade.jpg',
    capacity: 7,
    pricePerHour: 150,
    features: [
      'Premium leather interior',
      'Professional chauffeur',
      'Complimentary beverages',
      'WiFi & device charging'
    ]
  },
  {
    id: 'tesla',
    name: 'Tesla Model X',
    image: '/images/vehicles/tesla.jpg',
    capacity: 5,
    pricePerHour: 120,
    features: [
      'Eco-friendly electric vehicle',
      'Professional chauffeur',
      'Panoramic glass roof',
      'WiFi & device charging'
    ]
  }
];

export const TransportationOptions: React.FC<TransportationOptionsProps> = ({ eventId }) => {
  const [selectedVehicle, setSelectedVehicle] = useState(vehicles[0].id);
  
  const handleVehicleChange = (vehicleId: string) => {
    setSelectedVehicle(vehicleId);
  };
  
  const selectedVehicleData = vehicles.find(v => v.id === selectedVehicle);
  
  return (
    <div className="space-y-6">
      {/* Vehicle selection tabs */}
      <div className="grid grid-cols-2 gap-2">
        {vehicles.map((vehicle) => (
          <button
            key={vehicle.id}
            className={`py-3 rounded-md font-medium transition-colors text-center ${
              selectedVehicle === vehicle.id
                ? 'bg-gold text-black'
                : 'bg-gray-800 text-white hover:bg-gray-700'
            }`}
            onClick={() => handleVehicleChange(vehicle.id)}
          >
            {vehicle.name}
          </button>
        ))}
      </div>
      
      {/* Selected vehicle details */}
      {selectedVehicleData && (
        <div className="space-y-4">
          <div className="relative aspect-[16/9] rounded-md overflow-hidden">
            <Image
              src={selectedVehicleData.image}
              alt={selectedVehicleData.name}
              fill
              className="object-cover"
            />
          </div>
          
          <div className="flex justify-between items-center">
            <div>
              <h3 className="text-xl font-bold text-white">{selectedVehicleData.name}</h3>
              <p className="text-gray-400">Up to {selectedVehicleData.capacity} passengers</p>
            </div>
            <div className="text-right">
              <span className="text-xl text-gold font-bold">${selectedVehicleData.pricePerHour}</span>
              <p className="text-gray-400">per hour</p>
            </div>
          </div>
          
          <ul className="space-y-2">
            {selectedVehicleData.features.map((feature, index) => (
              <li key={index} className="flex items-center text-gray-300">
                <CheckIcon className="w-5 h-5 text-gold mr-2" />
                <span>{feature}</span>
              </li>
            ))}
          </ul>
          
          <Link
            href={`/book/${eventId}?vehicle=${selectedVehicleData.id}`}
            className="block w-full py-3 bg-gold text-black text-center rounded-md font-medium hover:bg-yellow-500 transition-colors"
          >
            Book This Vehicle
          </Link>
        </div>
      )}
    </div>
  );
};

#6. Booking Form

This component allows users to enter booking details.

// src/app/(frontend)/book/[eventId]/BookingForm.tsx
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { DateTimePicker } from '@/components/DateTimePicker';
import { AddressInput } from '@/components/AddressInput';
import { PassengerCounter } from '@/components/PassengerCounter';
import { CheckIcon } from '@heroicons/react/24/outline';

interface BookingFormProps {
  event: any;
  vehicle: {
    id: string;
    name: string;
    capacity: number;
    pricePerHour: number;
  };
}

export const BookingForm: React.FC<BookingFormProps> = ({ event, vehicle }) => {
  const router = useRouter();
  
  const [bookingData, setBookingData] = useState({
    passengers: 1,
    pickupLocation: '',
    pickupTime: event.event_date ? new Date(new Date(event.event_date).getTime() - 2 * 60 * 60 * 1000) : new Date(),
    dropoffLocation: event.location?.venue ? `${event.location.venue}, ${event.location.address}` : '',
    dropoffTime: event.event_date ? new Date(event.event_date) : new Date(),
    specialRequests: '',
    addons: {
      ambassador: false,
      refreshments: false,
      redCarpet: false
    }
  });
  
  const calculateDuration = () => {
    if (!bookingData.pickupTime || !bookingData.dropoffTime) return 3; // Minimum 3 hours
    
    const diffMs = bookingData.dropoffTime.getTime() - bookingData.pickupTime.getTime();
    const diffHours = diffMs / (1000 * 60 * 60);
    
    return Math.max(3, Math.ceil(diffHours)); // Minimum 3 hours, rounded up
  };
  
  const calculatePrice = () => {
    const hours = calculateDuration();
    let total = hours * vehicle.pricePerHour;
    
    // Add-ons
    if (bookingData.addons.ambassador) total += 150;
    if (bookingData.addons.refreshments) total += 75;
    if (bookingData.addons.redCarpet) total += 100;
    
    return total;
  };
  
  const handleChange = (field, value) => {
    setBookingData({
      ...bookingData,
      [field]: value
    });
  };
  
  const handleAddonChange = (addon) => {
    setBookingData({
      ...bookingData,
      addons: {
        ...bookingData.addons,
        [addon]: !bookingData.addons[addon]
      }
    });
  };
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    // Validate form
    if (!bookingData.pickupLocation || !bookingData.dropoffLocation) {
      alert('Please provide both pickup and dropoff locations');
      return;
    }
    
    // Prepare booking data
    const bookingPayload = {
      event: event.id,
      vehicle: vehicle.id,
      passengers: bookingData.passengers,
      pickupLocation: bookingData.pickupLocation,
      pickupTime: bookingData.pickupTime.toISOString(),
      dropoffLocation: bookingData.dropoffLocation,
      dropoffTime: bookingData.dropoffTime.toISOString(),
      specialRequests: bookingData.specialRequests,
      addons: bookingData.addons,
      totalPrice: calculatePrice()
    };
    
    try {
      // Create draft booking
      const response = await fetch('/api/bookings', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(bookingPayload)
      });
      
      const data = await response.json();
      
      if (data.success) {
        // Redirect to checkout
        router.push(`/checkout/${data.bookingId}`);
      } else {
        alert('Error creating booking: ' + data.message);
      }
    } catch (error) {
      console.error('Error creating booking:', error);
      alert('Failed to create booking. Please try again.');
    }
  };
  
  return (
    <form onSubmit={handleSubmit} className="space-y-8">
      <div className="bg-gray-900 rounded-lg p-6">
        <h2 className="text-2xl font-bold text-white mb-4">Transportation Details</h2>
        
        <div className="space-y-5">
          {/* Passenger count */}
          <div>
            <label className="block text-lg font-medium text-white mb-2">
              Number of Passengers
            </label>
            <PassengerCounter
              value={bookingData.passengers}
              max={vehicle.capacity}
              onChange={(value) => handleChange('passengers', value)}
            />
            <p className="mt-1 text-sm text-gray-400">
              Maximum capacity: {vehicle.capacity} passengers
            </p>
          </div>
          
          {/* Pickup details */}
          <div>
            <label className="block text-lg font-medium text-white mb-2">
              Pickup Details
            </label>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <AddressInput
                value={bookingData.pickupLocation}
                onChange={(value) => handleChange('pickupLocation', value)}
                placeholder="Enter pickup address"
                className="w-full"
              />
              <DateTimePicker
                label="Pickup Time"
                value={bookingData.pickupTime}
                onChange={(value) => handleChange('pickupTime', value)}
                className="w-full"
              />
            </div>
          </div>
          
          {/* Dropoff details */}
          <div>
            <label className="block text-lg font-medium text-white mb-2">
              Dropoff Details
            </label>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <AddressInput
                value={bookingData.dropoffLocation}
                onChange={(value) => handleChange('dropoffLocation', value)}
                placeholder="Enter dropoff address"
                className="w-full"
              />
              <DateTimePicker
                label="Dropoff Time"
                value={bookingData.dropoffTime}
                onChange={(value) => handleChange('dropoffTime', value)}
                className="w-full"
              />
            </div>
          </div>
          
          {/* Special requests */}
          <div>
            <label className="block text-lg font-medium text-white mb-2">
              Special Requests
            </label>
            <textarea
              value={bookingData.specialRequests}
              onChange={(e) => handleChange('specialRequests', e.target.value)}
              placeholder="Any special requests or instructions for your driver..."
              className="w-full px-4 py-3 bg-gray-800 border border-gray-700 rounded-md text-white h-32"
            />
          </div>
        </div>
      </div>
      
      {/* Premium add-ons */}
      <div className="bg-gray-900 rounded-lg p-6">
        <h2 className="text-2xl font-bold text-white mb-4">Premium Add-ons</h2>
        
        <div className="space-y-4">
          <div className="flex items-start">
            <input
              type="checkbox"
              id="ambassador"
              checked={bookingData.addons.ambassador}
              onChange={() => handleAddonChange('ambassador')}
              className="mt-1 mr-3"
            />
            <div>
              <label htmlFor="ambassador" className="text-lg font-medium text-white block">
                5CRSE Ambassador ($150)
              </label>
              <p className="text-gray-400">
                A personal host who will ensure your event experience is flawless from start to finish.
              </p>
            </div>
          </div>
          
          <div className="flex items-start">
            <input
              type="checkbox"
              id="refreshments"
              checked={bookingData.addons.refreshments}
              onChange={() => handleAddonChange('refreshments')}
              className="mt-1 mr-3"
            />
            <div>
              <label htmlFor="refreshments" className="text-lg font-medium text-white block">
                Premium Refreshments ($75)
              </label>
              <p className="text-gray-400">
                Complimentary champagne and gourmet snacks for your journey.
              </p>
            </div>
          </div>
          
          <div className="flex items-start">
            <input
              type="checkbox"
              id="redCarpet"
              checked={bookingData.addons.redCarpet}
              onChange={() => handleAddonChange('redCarpet')}
              className="mt-1 mr-3"
            />
            <div>
              <label htmlFor="redCarpet" className="text-lg font-medium text-white block">
                Red Carpet Arrival ($100)
              </label>
              <p className="text-gray-400">
                Make a grand entrance with our exclusive red carpet service.
              </p>
            </div>
          </div>
        </div>
      </div>
      
      {/* Price summary */}
      <div className="bg-gray-900 rounded-lg p-6">
        <div className="flex justify-between items-center mb-4">
          <h2 className="text-2xl font-bold text-white">Price Summary</h2>
          <span className="text-2xl font-bold text-gold">${calculatePrice()}</span>
        </div>
        
        <div className="space-y-2 border-t border-gray-800 pt-4">
          <div className="flex justify-between">
            <span className="text-gray-300">{vehicle.name} ({calculateDuration()} hours)</span>
            <span className="text-white">${vehicle.pricePerHour * calculateDuration()}</span>
          </div>
          
          {bookingData.addons.ambassador && (
            <div className="flex justify-between">
              <span className="text-gray-300">5CRSE Ambassador</span>
              <span className="text-white">$150</span>
            </div>
          )}
          
          {bookingData.addons.refreshments && (
            <div className="flex justify-between">
              <span className="text-gray-300">Premium Refreshments</span>
              <span className="text-white">$75</span>
            </div>
          )}
          
          {bookingData.addons.redCarpet && (
            <div className="flex justify-between">
              <span className="text-gray-300">Red Carpet Arrival</span>
              <span className="text-white">$100</span>
            </div>
          )}
        </div>
        
        <div className="mt-6">
          <p className="text-gray-400 text-sm mb-4">
            * Transportation charges include a 3-hour minimum booking.
            * Payment will be securely processed on the next page.
          </p>
          
          <button
            type="submit"
            className="w-full py-4 bg-gold text-black rounded-md font-medium text-lg hover:bg-yellow-500 transition-colors"
          >
            Proceed to Checkout
          </button>
        </div>
      </div>
    </form>
  );
};

#7. Ticketmaster API Integration

This service handles fetching and normalizing data from the Ticketmaster API.

// src/services/TicketmasterService.ts
import axios from 'axios';
import { getPayloadClient } from '@/payload/payloadClient';

const API_KEY = process.env.TICKETMASTER_API_KEY;
const API_BASE_URL = 'https://app.ticketmaster.com/discovery/v2';

export class TicketmasterService {
  /**
   * Search for events from Ticketmaster API
   */
  static async searchEvents(params = {}) {
    try {
      const response = await axios.get(`${API_BASE_URL}/events.json`, {
        params: {
          apikey: API_KEY,
          ...params
        }
      });
      
      // Handle empty results
      if (!response.data._embedded?.events) {
        return {
          events: [],
          page: {
            totalElements: 0,
            totalPages: 0,
            number: 0,
            size: 0
          }
        };
      }
      
      // Extract events and pagination data
      const events = response.data._embedded.events.map(this.normalizeEvent);
      const page = response.data.page;
      
      return {
        events,
        page
      };
    } catch (error) {
      console.error('Error fetching events from Ticketmaster:', error);
      throw error;
    }
  }
  
  /**
   * Get event details by Ticketmaster ID
   */
  static async getEventById(id) {
    try {
      const response = await axios.get(`${API_BASE_URL}/events/${id}`, {
        params: {
          apikey: API_KEY
        }
      });
      
      return this.normalizeEvent(response.data);
    } catch (error) {
      console.error(`Error fetching event details for ID ${id}:`, error);
      throw error;
    }
  }
  
  /**
   * Sync an event from Ticketmaster to Payload CMS
   */
  static async syncEvent(ticketmasterId) {
    try {
      // Get event from Ticketmaster
      const ticketmasterEvent = await this.getEventById(ticketmasterId);
      
      // Check if event already exists in Payload
      const payload = await getPayloadClient();
      const existingEvents = await payload.find({
        collection: 'events',
        where: {
          ticketmaster_id: {
            equals: ticketmasterId
          }
        }
      });
      
      // If event exists, update it
      if (existingEvents.docs.length > 0) {
        const updatedEvent = await payload.update({
          collection: 'events',
          id: existingEvents.docs[0].id,
          data: this.mapToPayloadEvent(ticketmasterEvent)
        });
        
        return {
          success: true,
          message: 'Event updated successfully',
          event: updatedEvent
        };
      }
      
      // Otherwise, create a new event
      const newEvent = await payload.create({
        collection: 'events',
        data: this.mapToPayloadEvent(ticketmasterEvent)
      });
      
      return {
        success: true,
        message: 'Event created successfully',
        event: newEvent
      };
    } catch (error) {
      console.error(`Error syncing event ${ticketmasterId}:`, error);
      return {
        success: false,
        message: `Error syncing event: ${error.message}`,
        error
      };
    }
  }
  
  /**
   * Normalize a Ticketmaster event to a standard format
   */
  static normalizeEvent(event) {
    // Handle venue and location
    const venue = event._embedded?.venues?.[0] || {};
    const location = venue ? {
      name: venue.name,
      address: venue.address?.line1,
      city: venue.city?.name,
      state: venue.state?.stateCode,
      country: venue.country?.countryCode,
      postalCode: venue.postalCode,
      location: venue.location ? {
        latitude: parseFloat(venue.location.latitude),
        longitude: parseFloat(venue.location.longitude)
      } : null
    } : null;
    
    // Handle images
    const images = event.images?.map(img => ({
      url: img.url,
      width: img.width,
      height: img.height,
      ratio: img.ratio
    })) || [];
    
    // Handle dates
    let eventDate = null;
    if (event.dates?.start) {
      const { localDate, localTime } = event.dates.start;
      if (localDate) {
        eventDate = localTime 
          ? new Date(`${localDate}T${localTime}`) 
          : new Date(`${localDate}T00:00:00`);
      }
    }
    
    // Handle pricing
    let priceRange = null;
    if (event.priceRanges && event.priceRanges.length > 0) {
      const range = event.priceRanges[0];
      priceRange = {
        min: range.min,
        max: range.max,
        currency: range.currency
      };
    }
    
    // Handle classifications/categories
    const categories = event.classifications?.map(classification => ({
      segment: classification.segment?.name,
      genre: classification.genre?.name,
      subGenre: classification.subGenre?.name
    })) || [];
    
    return {
      id: event.id,
      name: event.name,
      description: event.info || event.description || '',
      ticketmasterUrl: event.url,
      eventDate,
      onsaleStartDate: event.sales?.public?.startDateTime 
        ? new Date(event.sales.public.startDateTime) 
        : null,
      status: event.dates?.status?.code,
      images,
      venue: location,
      priceRange,
      categories,
      source: 'ticketmaster'
    };
  }
  
  /**
   * Map a normalized event to Payload CMS format
   */
  static mapToPayloadEvent(event) {
    return {
      event_name: event.name,
      event_date: event.eventDate,
      event_price: event.priceRange?.min || 0,
      description: event.description,
      location: {
        venue: event.venue?.name || '',
        address: [
          event.venue?.address,
          event.venue?.city,
          event.venue?.state
        ].filter(Boolean).join(', '),
        city: event.venue?.city || '',
        state: event.venue?.state || '',
        zip: event.venue?.postalCode || '',
        country: event.venue?.country || 'US',
        coordinates: event.venue?.location ? [
          event.venue.location.longitude,
          event.venue.location.latitude
        ] : undefined
      },
      ticketmaster_id: event.id,
      ticketmaster_url: event.ticketmasterUrl,
      images: event.images.slice(0, 5).map(img => ({
        image: {
          url: img.url
        }
      })),
      capacity: 0, // Not provided by Ticketmaster
      status: 'published',
      source: 'ticketmaster'
    };
  }
}

#8. Checkout & Payment Flow

The checkout component handles payment processing and booking confirmation.

// src/app/(frontend)/checkout/[bookingId]/page.tsx
import React from 'react';
import { redirect } from 'next/navigation';
import { getBookingById } from '@/lib/bookings';
import { getEventById } from '@/lib/events';
import { getVehicleById } from '@/lib/vehicles';
import { CheckoutSummary } from '@/components/Checkout/CheckoutSummary';
import { PaymentForm } from '@/components/Checkout/PaymentForm';

export default async function CheckoutPage({ params }: { params: { bookingId: string } }) {
  // Get booking details
  const booking = await getBookingById(params.bookingId);
  
  if (!booking) {
    redirect('/events');
  }
  
  // Get event and vehicle details
  const event = booking.event ? await getEventById(booking.event.toString()) : null;
  const vehicle = booking.vehicle ? await getVehicleById(booking.vehicle.toString()) : null;
  
  if (!event || !vehicle) {
    redirect('/events');
  }
  
  return (
    <div className="bg-black min-h-screen py-12">
      <div className="container mx-auto px-4">
        <h1 className="text-4xl font-bold text-white mb-8">Complete Your Booking</h1>
        
        <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
          {/* Payment form */}
          <div className="lg:col-span-2">
            <div className="bg-gray-900 rounded-lg p-6">
              <h2 className="text-2xl font-bold text-white mb-6">Payment Details</h2>
              <PaymentForm booking={booking} />
            </div>
          </div>
          
          {/* Order summary */}
          <div className="lg:col-span-1">
            <CheckoutSummary 
              booking={booking} 
              event={event} 
              vehicle={vehicle} 
            />
          </div>
        </div>
      </div>
    </div>
  );
}

#Data Flow Architecture

sequenceDiagram
    participant User
    participant Frontend
    participant PayloadAPI
    participant TicketmasterAPI
    participant Payments
    
    User->>Frontend: Visit Event Discovery
    Frontend->>PayloadAPI: Request Events
    Frontend->>TicketmasterAPI: Request External Events
    TicketmasterAPI-->>Frontend: Return Events
    PayloadAPI-->>Frontend: Return Events
    Frontend->>Frontend: Merge & Normalize Data
    Frontend-->>User: Display Events
    
    User->>Frontend: Select Event
    Frontend->>PayloadAPI: Get Event Details
    PayloadAPI-->>Frontend: Return Event Data
    Frontend-->>User: Display Event Details
    
    User->>Frontend: Book Transportation
    Frontend->>PayloadAPI: Create Booking (Draft)
    PayloadAPI-->>Frontend: Return Booking ID
    Frontend-->>User: Show Checkout
    
    User->>Frontend: Enter Payment Details
    Frontend->>Payments: Process Payment
    Payments-->>Frontend: Payment Confirmation
    Frontend->>PayloadAPI: Update Booking Status
    PayloadAPI-->>Frontend: Return Updated Booking
    Frontend-->>User: Show Confirmation

#Filter & Search Architecture

flowchart LR
    Client[Client Browser] -->|Request Events| API[API Layer]
    API -->|Find Events| QueryBuilder[Query Builder]
    
    subgraph Query Builder
        TM[Ticketmaster Query] -->|Combine Results| Normalizer[Data Normalizer]
        Payload[Payload Query] -->|Combine Results| Normalizer
    end
    
    QueryBuilder -->|Execute Queries| DataSources
    
    subgraph DataSources
        Ticketmaster[Ticketmaster API]
        PayloadCMS[Payload CMS]
    end
    
    Ticketmaster --> TM
    PayloadCMS --> Payload
    
    Normalizer -->|Return Normalized Results| API
    API -->|Return Events| Client
    
    subgraph Client Filters
        Category[Event Category]
        Date[Date Range]
        Location[Geographic Location]
        Price[Price Range]
    end
    
    Category --> Client
    Date --> Client
    Location --> Client
    Price --> Client

#Implementation Priorities & Timeline

  1. Phase 1: Core Discovery (3 weeks)

    • Implement basic event grid
    • Develop event card component
    • Create initial filter UI
    • Set up Ticketmaster API integration
  2. Phase 2: Advanced Discovery (3 weeks)

    • Enhance filtering with more options
    • Add search functionality
    • Create detailed event pages
    • Implement event recommendations
  3. Phase 3: Booking Flow (2 weeks)

    • Develop transportation selection UI
    • Create booking details form
    • Implement guest management features
    • Design booking summary component
  4. Phase 4: Checkout & Completion (2 weeks)

    • Build payment integration
    • Create order summary component
    • Implement booking confirmation emails
    • Develop booking management interface
  5. Future Enhancements

    • Map view for geographic exploration
    • Personalized event recommendations
    • Enhanced animations and transitions
    • Additional payment methods

#Questions to Consider

  1. How should we handle the merging and normalization of events from multiple sources (Ticketmaster + custom)?
  2. What are the most important filter criteria for our target user base?
  3. Should the transportation booking be fully integrated into the event booking process, or kept as separate steps?
  4. What payment processor integrations should we prioritize?
  5. How will bookings be managed after they're created (modifications, cancellations, etc.)?

#Next Steps

  1. Set up the Ticketmaster API integration
  2. Create the event card and grid components
  3. Implement the filtering system
  4. Develop the event detail page template
  5. Design the booking form and transportation selection components