updated booking form to multistep form
This commit is contained in:
@@ -68,7 +68,7 @@
|
||||
{{if .Authenticated}}
|
||||
<a href="/profile" {{if eq .ActivePage "profile"}}class="active"{{end}}>Profile</a>
|
||||
{{else}}
|
||||
<a href="{{.OryLoginURL}}" class="btn btn-primary" style="padding: 0.5rem 1rem; text-decoration: none;">Login</a>
|
||||
<a href="{{.OryLoginURL}}" class="btn btn-secondary" style="padding: 0.5rem 1rem; text-decoration: none;">Login</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
<button id="themeToggle" class="btn btn-outline" aria-label="Toggle theme">
|
||||
|
||||
@@ -15,45 +15,267 @@
|
||||
<script src="/static/js/alpine.js" defer></script>
|
||||
<script src="/static/htmx/htmx.min.js" defer></script>
|
||||
<script src="/static/js/drawer.js" defer></script>
|
||||
<style>
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.step-circle {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.step-circle.active {
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
.step-circle.completed {
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
.step-line {
|
||||
width: 3rem;
|
||||
height: 2px;
|
||||
background: hsl(var(--muted));
|
||||
}
|
||||
.step-line.completed {
|
||||
background: hsl(var(--primary));
|
||||
}
|
||||
.option-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.option-card {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 2px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
background: hsl(var(--card));
|
||||
}
|
||||
.option-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
|
||||
}
|
||||
.option-card.selected {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 3px hsla(var(--primary), 0.2);
|
||||
}
|
||||
.option-card img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.option-card-content {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.option-card-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.option-checkbox {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
border: 2px solid hsl(var(--border));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.option-card.selected .option-checkbox {
|
||||
background: hsl(var(--primary));
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
.option-checkbox svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: white;
|
||||
display: none;
|
||||
}
|
||||
.option-card.selected .option-checkbox svg {
|
||||
display: block;
|
||||
}
|
||||
[data-theme="dark"] .option-card.selected .option-checkbox svg {
|
||||
color: #22c55e;
|
||||
}
|
||||
.step-content {
|
||||
display: none;
|
||||
}
|
||||
.step-content.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "header" .}}
|
||||
|
||||
<main>
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
<div style="margin-bottom: 2rem; position: relative;">
|
||||
<img src="/assets/party-icons/birthday-cake.png" alt="Birthday cake" style="position: absolute; top: -20px; right: -40px; width: 90px; height: 90px; opacity: 0.85;">
|
||||
<h1 style="font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem;">Book a Service</h1>
|
||||
<p style="color: hsl(var(--muted-foreground)); font-size: 1.125rem;">Fill out the form below to request a booking</p>
|
||||
<p style="color: hsl(var(--muted-foreground)); font-size: 1.125rem;">Select your options and complete your booking</p>
|
||||
</div>
|
||||
|
||||
<div class="step-indicator">
|
||||
<div class="step">
|
||||
<div class="step-circle active" id="step-circle-1">1</div>
|
||||
<span style="font-weight: 500;">Service Options</span>
|
||||
</div>
|
||||
<div class="step-line" id="step-line-1"></div>
|
||||
<div class="step">
|
||||
<div class="step-circle" id="step-circle-2">2</div>
|
||||
<span style="font-weight: 500;">Event Type</span>
|
||||
</div>
|
||||
<div class="step-line" id="step-line-2"></div>
|
||||
<div class="step">
|
||||
<div class="step-circle" id="step-circle-3">3</div>
|
||||
<span style="font-weight: 500;">Details</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form id="bookingForm">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="service_option">Service Option</label>
|
||||
<select class="form-input" id="service_option" name="service_option" required>
|
||||
<option value="">Select a service option...</option>
|
||||
<option value="Small Bundle">Small Bundle</option>
|
||||
<option value="Medium Bundle">Medium Bundle</option>
|
||||
<option value="Large Bundle">Large Bundle</option>
|
||||
<option value="Balloons Only">Balloons Only</option>
|
||||
</select>
|
||||
<div class="step-content active" id="step-1">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem;">Select Service Options</h2>
|
||||
<p style="color: hsl(var(--muted-foreground)); margin-bottom: 1.5rem;">Choose one or more decoration options for your event</p>
|
||||
<div class="option-grid">
|
||||
<div class="option-card" data-option="Balloons" onclick="toggleOption(this)">
|
||||
<img src="/assets/options-photos/balloons.jpg" alt="Balloons">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Balloons</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-option="Centrepiece" onclick="toggleOption(this)">
|
||||
<img src="/assets/options-photos/centrepiece.jpg" alt="Centrepiece">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Centrepiece</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-option="Signs & Streamers" onclick="toggleOption(this)">
|
||||
<img src="/assets/options-photos/Signs-Streamers.jpg" alt="Signs & Streamers">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Signs & Streamers</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-option="Table Decorations" onclick="toggleOption(this)">
|
||||
<img src="/assets/options-photos/table-decorations.jpeg" alt="Table Decorations">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Table Decorations</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-option="Custom Plaque" onclick="toggleOption(this)">
|
||||
<img src="/assets/options-photos/custom-plaque.jpg" alt="Custom Plaque">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Custom Plaque</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
|
||||
<button type="button" class="btn btn-primary" onclick="nextStep(1)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="event_type">Event Type</label>
|
||||
<select class="form-input" id="event_type" name="event_type" required>
|
||||
<option value="">Select an event type...</option>
|
||||
<option value="Birthday">Birthday</option>
|
||||
<option value="Wedding">Wedding</option>
|
||||
<option value="Anniversary">Anniversary</option>
|
||||
<option value="Baby Shower">Baby Shower</option>
|
||||
<option value="Graduation">Graduation</option>
|
||||
<option value="Corporate Event">Corporate Event</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
<div class="step-content" id="step-2">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem;">Select Event Type</h2>
|
||||
<p style="color: hsl(var(--muted-foreground)); margin-bottom: 1.5rem;">What type of event are you planning?</p>
|
||||
<div class="option-grid">
|
||||
<div class="option-card" data-event="Birthday" onclick="selectEventType(this)">
|
||||
<img src="/assets/event-type-photos/birthday-party.jpg" alt="Birthday">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Birthday</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-event="Wedding" onclick="selectEventType(this)">
|
||||
<img src="/assets/event-type-photos/weddings.jpg" alt="Wedding">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Wedding</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-event="Anniversary" onclick="selectEventType(this)">
|
||||
<img src="/assets/event-type-photos/anniversary.jpg" alt="Anniversary">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Anniversary</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-card" data-event="Graduation" onclick="selectEventType(this)">
|
||||
<img src="/assets/event-type-photos/graduation.jpg" alt="Graduation">
|
||||
<div class="option-checkbox">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="option-card-content">
|
||||
<div class="option-card-title">Graduation</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem; justify-content: space-between;">
|
||||
<button type="button" class="btn btn-outline" onclick="prevStep(2)">Back</button>
|
||||
<button type="button" class="btn btn-primary" onclick="nextStep(2)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-content" id="step-3">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem;">Event Details</h2>
|
||||
<p style="color: hsl(var(--muted-foreground)); margin-bottom: 1.5rem;">Provide information about your event</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="event_date">Event Date & Time</label>
|
||||
@@ -71,18 +293,88 @@
|
||||
<textarea class="form-textarea" id="notes" name="notes" placeholder="Any special requests or details..."></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
||||
<button type="submit" class="btn btn-primary" style="flex: 1;">Request Booking</button>
|
||||
<a href="/catalog" class="btn btn-outline">Back to Catalog</a>
|
||||
<div style="display: flex; gap: 0.5rem; justify-content: space-between;">
|
||||
<button type="button" class="btn btn-outline" onclick="prevStep(3)">Back</button>
|
||||
<button type="submit" class="btn btn-primary">Request Booking</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{template "footer"}}
|
||||
{{template "theme-script"}}
|
||||
<script>
|
||||
let currentStep = 1;
|
||||
let selectedOptions = new Set();
|
||||
let selectedEventType = '';
|
||||
|
||||
function toggleOption(card) {
|
||||
const option = card.dataset.option;
|
||||
if (selectedOptions.has(option)) {
|
||||
selectedOptions.delete(option);
|
||||
card.classList.remove('selected');
|
||||
} else {
|
||||
selectedOptions.add(option);
|
||||
card.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function selectEventType(card) {
|
||||
document.querySelectorAll('[data-event]').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
selectedEventType = card.dataset.event;
|
||||
}
|
||||
|
||||
function updateStepIndicator() {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const circle = document.getElementById(`step-circle-${i}`);
|
||||
const line = document.getElementById(`step-line-${i}`);
|
||||
|
||||
if (i < currentStep) {
|
||||
circle.classList.add('completed');
|
||||
circle.classList.remove('active');
|
||||
if (line) line.classList.add('completed');
|
||||
} else if (i === currentStep) {
|
||||
circle.classList.add('active');
|
||||
circle.classList.remove('completed');
|
||||
} else {
|
||||
circle.classList.remove('active', 'completed');
|
||||
if (line) line.classList.remove('completed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep(step) {
|
||||
if (step === 1) {
|
||||
if (selectedOptions.size === 0) {
|
||||
alert('Please select at least one service option');
|
||||
return;
|
||||
}
|
||||
} else if (step === 2) {
|
||||
if (!selectedEventType) {
|
||||
alert('Please select an event type');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById(`step-${step}`).classList.remove('active');
|
||||
currentStep = step + 1;
|
||||
document.getElementById(`step-${currentStep}`).classList.add('active');
|
||||
updateStepIndicator();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function prevStep(step) {
|
||||
document.getElementById(`step-${step}`).classList.remove('active');
|
||||
currentStep = step - 1;
|
||||
document.getElementById(`step-${currentStep}`).classList.add('active');
|
||||
updateStepIndicator();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function validateAustralianAddress(address) {
|
||||
if (!address || address.trim().length < 10) {
|
||||
return { valid: false, message: 'Address is too short. Please provide a complete address.' };
|
||||
@@ -136,10 +428,12 @@ document.getElementById('bookingForm').addEventListener('submit', async function
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Submitting...';
|
||||
|
||||
const serviceOptionsArray = Array.from(selectedOptions);
|
||||
|
||||
const body = {
|
||||
user_id: "",
|
||||
service_option: f.service_option.value,
|
||||
event_type: f.event_type.value,
|
||||
service_option: serviceOptionsArray.join(', '),
|
||||
event_type: selectedEventType,
|
||||
event_date: new Date(f.event_date.value).toISOString(),
|
||||
address: f.address.value,
|
||||
notes: f.notes.value
|
||||
@@ -155,6 +449,13 @@ document.getElementById('bookingForm').addEventListener('submit', async function
|
||||
if (res.ok) {
|
||||
alert('Booking requested successfully! We will contact you soon.');
|
||||
f.reset();
|
||||
selectedOptions.clear();
|
||||
selectedEventType = '';
|
||||
currentStep = 1;
|
||||
document.querySelectorAll('.step-content').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById('step-1').classList.add('active');
|
||||
document.querySelectorAll('.option-card').forEach(c => c.classList.remove('selected'));
|
||||
updateStepIndicator();
|
||||
} else {
|
||||
const error = await res.text();
|
||||
alert('Error creating booking: ' + error);
|
||||
|
||||
Reference in New Issue
Block a user