#5CRSE Custom Event Creation Implementation Plan
#Overview
The custom event creation flow is a key differentiator for 5CRSE, allowing users to create personalized events with premium transportation, venue selection, and guest management. This document outlines the implementation plan for this feature, focusing on a premium user experience that showcases our luxury transportation options.
#User Journey & Flow
flowchart TD
A[Dashboard] -->|"Click 'Create Custom Event'"| B[Event Type Selection]
B -->|"Select category"| C[Date & Time Selection]
C -->|"Select date/time"| D[Location Selection]
D -->|"Choose venue"| E[Transportation Planning]
E -->|"Select vehicle & logistics"| F[Guest Management]
F -->|"Invite guests"| G[Add-on Services]
G -->|"Select add-ons"| H[Review & Confirm]
H -->|"Complete booking"| I[Confirmation]
subgraph "Restaurant Preferences Form"
D1[Restaurant Type] --> D2[Party Size]
D2 --> D3[Special Requirements]
end
D -.->|"If dining event"| D1
subgraph "Premium Transportation"
E1[Premium Vehicle Selection] --> E2[Pickup Details]
E2 --> E3[Dropoff Details]
end
E -.-> E1
subgraph "Guest System"
F1[Add Guest Details] --> F2[Send Invitations]
F2 --> F3[Track RSVPs]
end
F -.-> F1
#Implementation Phases
gantt
title Custom Event Creation Implementation Plan
dateFormat YYYY-MM-DD
section Phase 1: Core Experience
Event Type Selection :2025-04-01, 5d
Date & Time Selection :2025-04-06, 4d
Premium Transportation UI :2025-04-10, 7d
section Phase 2: Venue & Location
Basic Venue Selection :2025-04-17, 5d
Restaurant Preferences Form :2025-04-22, 4d
Custom Location Input :2025-04-26, 3d
section Phase 3: Guest Experience
Guest Management Interface :2025-04-29, 5d
Email Template System :2025-05-04, 4d
Basic RSVP Tracking :2025-05-08, 3d
section Phase 4: Finalization
Add-on Services Selection :2025-05-11, 4d
Review & Summary UI :2025-05-15, 3d
Basic Payment Integration :2025-05-18, 5d
section Phase 5: Enhancements
OpenTable API Integration :future, 0d
Advanced Guest Management :future, 0d
Advanced Payment Options :future, 0d
#Detailed Component Implementations
#1. Event Type Selection
This is the entry point for the custom event creation flow, where users select the type of event they want to create.
User Interface
- Large, visually appealing cards for each event type
- Gold accents to highlight selected options
- Clear descriptions of each event type
- Subtle animations on hover/selection
Component Structure
// src/components/CreateEvent/EventTypeSelection.tsx
import React from 'react';
import Image from 'next/image';
const eventTypes = [
{
id: 'birthday',
title: 'Birthday Party',
description: 'Celebrate with luxury transportation',
icon: '/images/event-types/birthday.svg'
},
{
id: 'corporate',
title: 'Corporate Event',
description: 'Impress clients and colleagues',
icon: '/images/event-types/corporate.svg'
},
{
id: 'dining',
title: 'Fine Dining',
description: 'Premium restaurant experience',
icon: '/images/event-types/dining.svg'
},
{
id: 'concert',
title: 'Concert or Show',
description: 'Arrive in style to performances',
icon: '/images/event-types/concert.svg'
},
{
id: 'sports',
title: 'Sporting Event',
description: 'Transportation to games and matches',
icon: '/images/event-types/sports.svg'
},
{
id: 'other',
title: 'Custom Event',
description: 'Create your own unique experience',
icon: '/images/event-types/custom.svg'
}
];
interface EventTypeSelectionProps {
selectedType: string | null;
onSelect: (type: string) => void;
}
export const EventTypeSelection: React.FC<EventTypeSelectionProps> = ({
selectedType,
onSelect
}) => {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white">What type of event are you planning?</h2>
<p className="text-gray-300">Select an event type to begin customizing your experience</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{eventTypes.map((type) => (
<div
key={type.id}
className={`p-4 rounded-lg cursor-pointer transition-all ${
selectedType === type.id
? 'bg-gold bg-opacity-20 border-2 border-gold'
: 'bg-gray-900 hover:bg-gray-800 border-2 border-transparent'
}`}
onClick={() => onSelect(type.id)}
>
<div className="flex items-center space-x-4">
<div className="w-12 h-12 relative">
<Image
src={type.icon}
alt={type.title}
fill
className="object-contain"
/>
</div>
<div>
<h3 className="font-bold text-white">{type.title}</h3>
<p className="text-sm text-gray-300">{type.description}</p>
</div>
</div>
</div>
))}
</div>
</div>
);
};
Data Flow
- User selects event type
- Selection is stored in event creation context/state
- Event type determines available options in subsequent steps
- Backend validates event type against supported categories
#2. Premium Transportation Selection
This component is the centerpiece of our custom event creation flow, highlighting our luxury vehicles with rich visuals and detailed features.
User Interface
- High-quality images of Cadillac Escalade and Tesla vehicles
- Prominent brand logos and model specifications
- Gold accents to highlight premium nature
- Elegant animations for vehicle selection
- Clear capacity and feature listings
- Luxurious copy emphasizing the premium experience
Component Structure
// src/components/CreateEvent/PremiumTransportation.tsx
import React, { useState } from 'react';
import Image from 'next/image';
import { CheckIcon } from '@heroicons/react/24/outline';
interface PremiumTransportationProps {
onSelect: (transportationDetails: any) => void;
eventDateTime?: Date;
eventLocation?: {
address: string;
coordinates: [number, number];
};
}
export const PremiumTransportation: React.FC<PremiumTransportationProps> = ({
onSelect,
eventDateTime,
eventLocation
}) => {
const [vehicleType, setVehicleType] = useState<'escalade' | 'tesla'>('escalade');
const [passengerCount, setPassengerCount] = useState(2);
const [pickupDetails, setPickupDetails] = useState({
location: '',
time: eventDateTime ? new Date(eventDateTime.getTime() - 2 * 60 * 60 * 1000) : null
});
const [dropoffDetails, setDropoffDetails] = useState({
location: eventLocation?.address || '',
time: eventDateTime || null
});
const [specialInstructions, setSpecialInstructions] = useState('');
// Vehicle feature lists
const escaladeFeatures = [
'Premium leather interior',
'Seating for up to 7 passengers',
'Complimentary chilled beverages',
'Climate-controlled cabin',
'Wi-Fi connectivity',
'Premium sound system',
'Privacy partition',
'Professional chauffeur'
];
const teslaFeatures = [
'Modern luxury interior',
'Seating for up to 5 passengers',
'Eco-friendly all-electric vehicle',
'Panoramic glass roof',
'Wi-Fi connectivity',
'Premium sound system',
'Cutting-edge technology',
'Professional chauffeur'
];
const handleSubmit = () => {
// Validation and submission logic
onSelect({
vehicleType,
capacity: vehicleType === 'escalade' ? 7 : 5,
passengerCount,
pickupDetails,
dropoffDetails,
specialInstructions
});
};
return (
<div className="space-y-8">
<div className="text-center">
<h2 className="text-3xl font-bold text-white mb-2">Premium Transportation</h2>
<p className="text-xl text-gold">The 5CRSE Experience</p>
<p className="text-gray-300 mt-2 max-w-2xl mx-auto">
Select your preferred luxury vehicle for an unforgettable transportation experience.
Our professional chauffeurs ensure a seamless journey in ultimate comfort.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Cadillac Escalade Card */}
<div
className={`rounded-lg overflow-hidden transition-all ${
vehicleType === 'escalade'
? 'ring-4 ring-gold'
: 'ring-1 ring-gray-700 hover:ring-gray-500'
}`}
>
<div className="relative h-64">
<Image
src="/images/vehicles/escalade-premium.jpg"
alt="Cadillac Escalade"
fill
className="object-cover"
/>
<div className="absolute top-4 left-4 bg-black bg-opacity-70 rounded-full px-4 py-1">
<Image
src="/images/logos/cadillac-logo.svg"
alt="Cadillac"
width={80}
height={20}
/>
</div>
</div>
<div className="p-6 bg-black">
<div className="flex justify-between items-center mb-4">
<h3 className="text-2xl font-bold text-white">Cadillac Escalade</h3>
<span className="text-gold text-lg font-medium">Premium SUV</span>
</div>
<div className="mb-6">
<p className="text-gray-300">
Experience the epitome of luxury with our Cadillac Escalade, combining
elegant styling with spacious comfort for up to 7 passengers.
</p>
</div>
<div className="mb-6">
<h4 className="text-lg font-medium text-white mb-2">Vehicle Features</h4>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{escaladeFeatures.map((feature) => (
<li key={feature} className="flex items-center text-gray-300">
<CheckIcon className="w-5 h-5 text-gold mr-2" />
<span>{feature}</span>
</li>
))}
</ul>
</div>
<button
onClick={() => setVehicleType('escalade')}
className={`w-full py-3 rounded-md font-medium transition-colors ${
vehicleType === 'escalade'
? 'bg-gold text-black'
: 'bg-gray-800 text-white hover:bg-gray-700'
}`}
>
{vehicleType === 'escalade' ? 'Selected' : 'Select Escalade'}
</button>
</div>
</div>
{/* Tesla Card */}
<div
className={`rounded-lg overflow-hidden transition-all ${
vehicleType === 'tesla'
? 'ring-4 ring-gold'
: 'ring-1 ring-gray-700 hover:ring-gray-500'
}`}
>
<div className="relative h-64">
<Image
src="/images/vehicles/tesla-premium.jpg"
alt="Tesla Model X"
fill
className="object-cover"
/>
<div className="absolute top-4 left-4 bg-black bg-opacity-70 rounded-full px-4 py-1">
<Image
src="/images/logos/tesla-logo.svg"
alt="Tesla"
width={80}
height={20}
/>
</div>
</div>
<div className="p-6 bg-black">
<div className="flex justify-between items-center mb-4">
<h3 className="text-2xl font-bold text-white">Tesla Model X</h3>
<span className="text-gold text-lg font-medium">Electric Luxury</span>
</div>
<div className="mb-6">
<p className="text-gray-300">
Choose our Tesla Model X for a sophisticated, eco-friendly luxury experience
with cutting-edge technology and seating for up to 5 passengers.
</p>
</div>
<div className="mb-6">
<h4 className="text-lg font-medium text-white mb-2">Vehicle Features</h4>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{teslaFeatures.map((feature) => (
<li key={feature} className="flex items-center text-gray-300">
<CheckIcon className="w-5 h-5 text-gold mr-2" />
<span>{feature}</span>
</li>
))}
</ul>
</div>
<button
onClick={() => setVehicleType('tesla')}
className={`w-full py-3 rounded-md font-medium transition-colors ${
vehicleType === 'tesla'
? 'bg-gold text-black'
: 'bg-gray-800 text-white hover:bg-gray-700'
}`}
>
{vehicleType === 'tesla' ? 'Selected' : 'Select Tesla'}
</button>
</div>
</div>
</div>
{/* Passenger Count Section */}
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Passenger Details</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Number of Passengers
</label>
<select
value={passengerCount}
onChange={(e) => setPassengerCount(parseInt(e.target.value))}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
>
{[...Array(vehicleType === 'escalade' ? 7 : 5)].map((_, i) => (
<option key={i} value={i + 1}>
{i + 1} {i === 0 ? 'Passenger' : 'Passengers'}
</option>
))}
</select>
<p className="mt-2 text-sm text-gray-400">
{vehicleType === 'escalade'
? 'Escalade comfortably seats up to 7 passengers'
: 'Tesla Model X comfortably seats up to 5 passengers'}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Special Requirements
</label>
<textarea
value={specialInstructions}
onChange={(e) => setSpecialInstructions(e.target.value)}
placeholder="Any special requests or accommodations?"
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white h-24"
/>
</div>
</div>
</div>
{/* Pickup & Dropoff Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Pickup details */}
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Pickup Details</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Pickup Location
</label>
<input
type="text"
value={pickupDetails.location}
onChange={(e) => setPickupDetails({...pickupDetails, location: e.target.value})}
placeholder="Enter pickup address"
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Pickup Time
</label>
<input
type="datetime-local"
value={pickupDetails.time ? pickupDetails.time.toISOString().slice(0, 16) : ''}
onChange={(e) => setPickupDetails({
...pickupDetails,
time: e.target.value ? new Date(e.target.value) : null
})}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
</div>
</div>
{/* Dropoff details */}
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Dropoff Details</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Dropoff Location
</label>
<input
type="text"
value={dropoffDetails.location}
onChange={(e) => setDropoffDetails({...dropoffDetails, location: e.target.value})}
placeholder="Enter dropoff address"
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Dropoff Time
</label>
<input
type="datetime-local"
value={dropoffDetails.time ? dropoffDetails.time.toISOString().slice(0, 16) : ''}
onChange={(e) => setDropoffDetails({
...dropoffDetails,
time: e.target.value ? new Date(e.target.value) : null
})}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
</div>
</div>
</div>
<div className="flex justify-end mt-8">
<button
onClick={handleSubmit}
className="px-8 py-3 bg-gold text-black rounded-md font-medium text-lg"
>
Continue to Next Step
</button>
</div>
</div>
);
};
#3. Restaurant Preferences Form (Simplified Approach)
Instead of direct OpenTable API integration, we'll start with a form that collects restaurant preferences for dining events.
User Interface
- Restaurant type/cuisine selection
- Party size input
- Special dietary requirements
- Preferred date/time
- Price range selection
- Special occasion notification
Component Structure
// src/components/CreateEvent/RestaurantPreferences.tsx
import React, { useState } from 'react';
interface RestaurantPreferencesProps {
onComplete: (preferences: any) => void;
}
const cuisineTypes = [
'American', 'Italian', 'French', 'Japanese', 'Chinese',
'Indian', 'Mexican', 'Mediterranean', 'Steakhouse', 'Seafood',
'Vegetarian', 'Vegan', 'Fusion', 'Other'
];
const priceRanges = [
{ id: '$', label: 'Moderate ($30-50 per person)' },
{ id: '$$', label: 'Upscale ($50-100 per person)' },
{ id: '$$$', label: 'Fine Dining ($100-200 per person)' },
{ id: '$$$$', label: 'Premium Experience ($200+ per person)' }
];
export const RestaurantPreferences: React.FC<RestaurantPreferencesProps> = ({
onComplete
}) => {
const [preferences, setPreferences] = useState({
cuisineType: '',
partySize: 2,
date: '',
time: '19:00',
priceRange: '$$',
specialOccasion: false,
occasionType: '',
dietaryRequirements: '',
additionalRequests: ''
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setPreferences({
...preferences,
[name]: type === 'checkbox' ? checked : value
});
};
const handleSubmit = (e) => {
e.preventDefault();
onComplete(preferences);
};
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-3xl font-bold text-white mb-2">Dining Preferences</h2>
<p className="text-gray-300 mt-2 max-w-2xl mx-auto">
Tell us about your dining preferences and we'll arrange the perfect restaurant experience.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Restaurant Type</h3>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{cuisineTypes.map((cuisine) => (
<label
key={cuisine}
className={`flex items-center p-3 rounded-md cursor-pointer ${
preferences.cuisineType === cuisine
? 'bg-gold bg-opacity-20 border border-gold'
: 'bg-gray-800 border border-gray-700 hover:border-gray-500'
}`}
>
<input
type="radio"
name="cuisineType"
value={cuisine}
checked={preferences.cuisineType === cuisine}
onChange={handleChange}
className="sr-only"
/>
<span className={`${
preferences.cuisineType === cuisine ? 'text-gold' : 'text-white'
}`}>
{cuisine}
</span>
</label>
))}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Party Details</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Party Size
</label>
<select
name="partySize"
value={preferences.partySize}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
>
{[...Array(20)].map((_, i) => (
<option key={i} value={i + 1}>
{i + 1} {i === 0 ? 'Person' : 'People'}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Price Range
</label>
<div className="space-y-2">
{priceRanges.map((range) => (
<label
key={range.id}
className="flex items-center cursor-pointer"
>
<input
type="radio"
name="priceRange"
value={range.id}
checked={preferences.priceRange === range.id}
onChange={handleChange}
className="mr-2 text-gold"
/>
<span className="text-white">{range.label}</span>
</label>
))}
</div>
</div>
</div>
</div>
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Date & Time</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Preferred Date
</label>
<input
type="date"
name="date"
value={preferences.date}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Preferred Time
</label>
<input
type="time"
name="time"
value={preferences.time}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
/>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="specialOccasion"
name="specialOccasion"
checked={preferences.specialOccasion}
onChange={handleChange}
className="mr-2 text-gold"
/>
<label htmlFor="specialOccasion" className="text-white">
This is a special occasion
</label>
</div>
{preferences.specialOccasion && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Occasion Type
</label>
<select
name="occasionType"
value={preferences.occasionType}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white"
>
<option value="">Select Occasion</option>
<option value="birthday">Birthday</option>
<option value="anniversary">Anniversary</option>
<option value="business">Business Meeting</option>
<option value="engagement">Engagement</option>
<option value="other">Other</option>
</select>
</div>
)}
</div>
</div>
</div>
<div className="bg-gray-900 p-6 rounded-lg">
<h3 className="text-xl font-medium text-white mb-4">Special Requirements</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Dietary Requirements
</label>
<textarea
name="dietaryRequirements"
value={preferences.dietaryRequirements}
onChange={handleChange}
placeholder="Vegetarian, vegan, gluten-free, allergies, etc."
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white h-20"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Additional Requests
</label>
<textarea
name="additionalRequests"
value={preferences.additionalRequests}
onChange={handleChange}
placeholder="Private dining, specific table requests, etc."
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-md text-white h-20"
/>
</div>
</div>
</div>
<div className="flex justify-end">
<button
type="submit"
className="px-8 py-3 bg-gold text-black rounded-md font-medium text-lg"
>
Submit Dining Preferences
</button>
</div>
</form>
</div>
);
};
#4. Guest Management & Invitation System
The guest management system allows event creators to invite guests to their custom events.
User Interface
- Clean guest list interface
- Easy-to-use form for adding guests
- Customizable invitation message
- Real-time validation of email addresses
- Elegant invitation templates
Backend Integration
For the initial phase, we'll implement a simplified email invitation system:
// src/pages/api/send-invitations.ts
import { NextApiRequest, NextApiResponse } from 'next';
import nodemailer from 'nodemailer';
import { getPayloadClient } from '../../payload/payloadClient';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { eventId, guests, message } = req.body;
if (!eventId || !guests || !Array.isArray(guests) || guests.length === 0) {
return res.status(400).json({ error: 'Invalid request data' });
}
try {
// Get event details from Payload
const payload = await getPayloadClient();
const event = await payload.findByID({
collection: 'events',
id: eventId
});
if (!event) {
return res.status(404).json({ error: 'Event not found' });
}
// Configure email transporter
const transporter = nodemailer.createTransport({
// Email configuration
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
// Update event with invited guests
await payload.update({
collection: 'events',
id: eventId,
data: {
invitedGuests: guests.map(guest => ({
name: guest.name,
email: guest.email,
status: 'pending',
invitedAt: new Date().toISOString()
}))
}
});
// Send invitations to all guests
const emailPromises = guests.map(async (guest) => {
const emailContent = generateInvitationEmail(event, guest, message);
await transporter.sendMail({
from: `"5CRSE Events" <${process.env.SMTP_FROM_EMAIL}>`,
to: guest.email,
subject: `You're Invited: ${event.event_name}`,
html: emailContent
});
});
await Promise.all(emailPromises);
return res.status(200).json({
success: true,
message: `Invitations sent to ${guests.length} guests`
});
} catch (error) {
console.error('Error sending invitations:', error);
return res.status(500).json({ error: 'Failed to send invitations' });
}
}
function generateInvitationEmail(event, guest, customMessage) {
// Generate HTML email template with event details, custom message, etc.
return `
<!DOCTYPE html>
<html>
<head>
<style>
/* Email styles */
body { font-family: Arial, sans-serif; color: #333; }
.container { max-width: 600px; margin: 0 auto; }
.header { background-color: #000; padding: 20px; text-align: center; }
.logo { color: #D4AF37; font-size: 24px; font-weight: bold; }
.content { padding: 20px; }
.event-details { background-color: #f5f5f5; padding: 15px; margin: 20px 0; }
.footer { font-size: 12px; text-align: center; margin-top: 30px; color: #999; }
.button { display: inline-block; background-color: #D4AF37; color: #000; text-decoration: none; padding: 12px 30px; border-radius: 4px; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">5CRSE</div>
</div>
<div class="content">
<h2>You're Invited</h2>
<p>Hello ${guest.name},</p>
<p>${customMessage}</p>
<div class="event-details">
<h3>${event.event_name}</h3>
<p><strong>Date:</strong> ${new Date(event.event_date).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}</p>
<p><strong>Time:</strong> ${new Date(event.event_date).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}</p>
<p><strong>Location:</strong> ${event.location.venue}, ${event.location.address}</p>
</div>
<p>Luxury transportation will be provided by 5CRSE.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${process.env.NEXT_PUBLIC_SITE_URL}/rsvp/${event.id}/${Buffer.from(guest.email).toString('base64')}" class="button">Respond to Invitation</a>
</div>
</div>
<div class="footer">
<p>This invitation was sent by 5CRSE Events on behalf of the event organizer.</p>
<p>© 2025 5CRSE. All rights reserved.</p>
</div>
</div>
</body>
</html>
`;
}
#Database Schema Updates
We'll need to update our Payload CMS collections to support custom events:
#Events Collection Extension
// Additional fields for the Events collection
{
name: 'isCustomEvent',
type: 'checkbox',
label: 'Custom Event',
defaultValue: false,
},
{
name: 'creator',
type: 'relationship',
relationTo: 'users',
label: 'Event Creator',
admin: {
condition: (data) => data?.isCustomEvent === true,
},
},
{
name: 'customEventDetails',
type: 'group',
label: 'Custom Event Details',
admin: {
condition: (data) => data?.isCustomEvent === true,
},
fields: [
{
name: 'transportation',
type: 'group',
fields: [
{
name: 'vehicleType',
type: 'select',
options: [
{ label: 'Cadillac Escalade', value: 'escalade' },
{ label: 'Tesla Model X', value: 'tesla' },
],
},
{
name: 'passengerCount',
type: 'number',
min: 1,
max: 7,
},
{
name: 'pickupDetails',
type: 'group',
fields: [
{
name: 'location',
type: 'text',
},
{
name: 'time',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayAndTime',
},
},
},
],
},
{
name: 'dropoffDetails',
type: 'group',
fields: [
{
name: 'location',
type: 'text',
},
{
name: 'time',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayAndTime',
},
},
},
],
},
],
},
{
name: 'restaurantPreferences',
type: 'group',
admin: {
condition: (data) => data?.event_type === 'dining',
},
fields: [
{
name: 'cuisineType',
type: 'text',
},
{
name: 'priceRange',
type: 'select',
options: [
{ label: 'Moderate', value: '$' },
{ label: 'Upscale', value: '$$' },
{ label: 'Fine Dining', value: '$$$' },
{ label: 'Premium', value: '$$$$' },
],
},
{
name: 'dietaryRequirements',
type: 'textarea',
},
{
name: 'specialOccasion',
type: 'checkbox',
},
{
name: 'occasionType',
type: 'text',
admin: {
condition: (data) => data?.specialOccasion === true,
},
},
],
},
{
name: 'invitedGuests',
type: 'array',
fields: [
{
name: 'name',
type: 'text',
},
{
name: 'email',
type: 'email',
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Pending', value: 'pending' },
{ label: 'Confirmed', value: 'confirmed' },
{ label: 'Declined', value: 'declined' },
],
},
{
name: 'invitedAt',
type: 'date',
},
{
name: 'respondedAt',
type: 'date',
},
],
},
],
}
#Implementation Priorities & Timeline
-
Phase 1: Core Experience (4 weeks)
- Event Type Selection
- Date & Time Components
- Premium Transportation UI
- Basic state management
-
Phase 2: Location & Venue (3 weeks)
- Basic venue selection interface
- Restaurant preferences form (simplified)
- Custom location input with map picker
-
Phase 3: Guest System (3 weeks)
- Guest list management
- Email template system
- Basic RSVP tracking
-
Phase 4: Finalization (3 weeks)
- Add-on services selection
- Review & summary screen
- Basic payment integration
- Confirmation experience
-
Future Enhancements
- OpenTable direct API integration
- Advanced guest management with dynamic updates
- Payment plan options
- Custom event templates
#Questions to Consider
- Do we have existing venue partnerships to feature in the location selection step?
- What is the pricing structure for different vehicle types and additional services?
- What payment processor will we be using for the initial implementation?
- How will the 5CRSE Ambassador service be priced and what information should we collect?
- What email service will we use for sending invitations?
#Next Steps
- Set up the custom event creation routes and page structure
- Implement the wizard component framework with state management
- Start building the event type selection component
- Create transportation selection UI with vehicle details
- Develop the state management for the multi-step process
