// Initialize Google Maps services
let map, placesService, distanceMatrixService;
let pickupAutocomplete, destinationAutocomplete;
let currentFormStep = 1;
let bookingHistory = [];
// Uber NYC rates (in USD)
const uberRates = {
uberX: {
base: 2.50,
perMile: 1.75,
perMinute: 0.35,
name: “UberX”
},
uberXL: {
base: 5.00,
perMile: 2.85,
perMinute: 0.50,
name: “UberXL”
},
black: {
base: 8.00,
perMile: 4.10,
perMinute: 0.65,
name: “Uber Black”
}
};
// Peak hours (7-9am and 4-7pm)
const isPeakHour = (dateTime) => {
const hour = new Date(dateTime).getHours();
return (hour >= 7 && hour < 9) || (hour >= 16 && hour < 19);
};
// Initialize form when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Generate CSRF token
document.getElementById('csrfToken').value = generateCsrfToken();
// Load booking history from session
loadBookingHistory();
// Initialize form validation
initFormValidation();
// Set up event listeners
setupEventListeners();
// Set minimum datetime for pickup (current time + 30 minutes)
const now = new Date();
now.setMinutes(now.getMinutes() + 30);
document.getElementById('pickupTime').min = now.toISOString().slice(0, 16);
});
// Initialize Google Maps Autocomplete
function initAutocomplete() {
distanceMatrixService = new google.maps.DistanceMatrixService();
placesService = new google.maps.places.PlacesService(document.createElement('div'));
// Initialize autocomplete for pickup and destination
pickupAutocomplete = new google.maps.places.Autocomplete(
document.getElementById('pickup'),
{ types: ['geocode'] }
);
destinationAutocomplete = new google.maps.places.Autocomplete(
document.getElementById('destination'),
{ types: ['geocode'] }
);
// Add listeners for place changes
pickupAutocomplete.addListener('place_changed', calculatePrice);
destinationAutocomplete.addListener('place_changed', calculatePrice);
}
// Generate CSRF token
function generateCsrfToken() {
return 'csrf-' + Math.random().toString(36).substr(2, 16) + '-' + Date.now().toString(36);
}
// Initialize form validation
function initFormValidation() {
const validateName = (value) => value.length >= 2;
const validatePhone = (value) => /^[\d\s\-()+]{7,}$/.test(value);
const validateEmail = (value) => value === ” || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const validateRequired = (value) => value.trim() !== ”;
// Add input event listeners for validation
document.getElementById(‘name’).addEventListener(‘input’, function() {
validateField(this, validateName, ‘Please enter a valid name’);
});
document.getElementById(‘phone’).addEventListener(‘input’, function() {
validateField(this, validatePhone, ‘Please enter a valid phone number’);
});
document.getElementById(’email’).addEventListener(‘input’, function() {
validateField(this, validateEmail, ‘Please enter a valid email’);
});
document.getElementById(‘pickup’).addEventListener(‘input’, function() {
validateField(this, validateRequired, ‘Pickup location is required’);
});
document.getElementById(‘destination’).addEventListener(‘input’, function() {
validateField(this, validateRequired, ‘Destination is required’);
});
document.getElementById(‘pickupTime’).addEventListener(‘change’, function() {
validateField(this, validateRequired, ‘Pickup time is required’);
});
}
// Validate a form field
function validateField(field, validator, errorMessage) {
const isValid = validator(field.value);
const validationMsg = field.parentElement.querySelector(‘.validation-message’);
if (isValid) {
field.classList.remove(‘error’);
field.classList.add(‘valid’);
validationMsg.textContent = ”;
} else {
field.classList.remove(‘valid’);
field.classList.add(‘error’);
validationMsg.textContent = errorMessage;
}
return isValid;
}
// Set up event listeners
function setupEventListeners() {
// Navigation buttons
document.querySelectorAll(‘.next-btn’).forEach(btn => {
btn.addEventListener(‘click’, goToNextStep);
});
document.querySelectorAll(‘.back-btn’).forEach(btn => {
btn.addEventListener(‘click’, goToPrevStep);
});
// Return trip toggle
document.getElementById(‘returnTrip’).addEventListener(‘change’, function() {
const returnFields = document.getElementById(‘returnFields’);
returnFields.style.display = this.checked ? ‘block’ : ‘none’;
calculatePrice();
});
// Vehicle type change
document.getElementById(‘vehicleType’).addEventListener(‘change’, calculatePrice);
// Tip buttons
document.querySelectorAll(‘.tip-btn’).forEach(btn => {
btn.addEventListener(‘click’, function() {
document.querySelectorAll(‘.tip-btn’).forEach(b => b.classList.remove(‘active’));
this.classList.add(‘active’);
document.getElementById(‘selectedTip’).value = this.dataset.tip;
calculatePrice();
});
});
// Custom tip input
document.getElementById(‘customTip’).addEventListener(‘input’, function() {
document.querySelectorAll(‘.tip-btn’).forEach(b => b.classList.remove(‘active’));
document.getElementById(‘selectedTip’).value = this.value;
calculatePrice();
});
// Current location button
document.getElementById(‘currentLocation’).addEventListener(‘click’, getCurrentLocation);
// Theme toggle
document.getElementById(‘themeToggle’).addEventListener(‘click’, toggleDarkMode);
// New booking button
document.getElementById(‘newBooking’).addEventListener(‘click’, resetForm);
// Form submission
document.getElementById(‘taxiForm’).addEventListener(‘submit’, submitForm);
}
// Calculate price using Distance Matrix API
function calculatePrice() {
const pickup = document.getElementById(‘pickup’).value;
const destination = document.getElementById(‘destination’).value;
const vehicleType = document.getElementById(‘vehicleType’).value;
const returnTrip = document.getElementById(‘returnTrip’).checked;
const pickupTime = document.getElementById(‘pickupTime’).value;
const tipPercentage = parseFloat(document.getElementById(‘selectedTip’).value) || 0;
if (!pickup || !destination || !pickupTime) return;
const pickupPlace = pickupAutocomplete.getPlace();
const destinationPlace = destinationAutocomplete.getPlace();
if (!pickupPlace || !destinationPlace || !pickupPlace.geometry || !destinationPlace.geometry) {
return;
}
const rate = uberRates[vehicleType];
const origin = pickupPlace.geometry.location;
const destinationLoc = destinationPlace.geometry.location;
// Show loading state
const priceDisplay = document.getElementById(‘estimatedPrice’);
priceDisplay.innerHTML = ‘ Calculating…’;
distanceMatrixService.getDistanceMatrix({
origins: [origin],
destinations: [destinationLoc],
travelMode: ‘DRIVING’,
unitSystem: google.maps.UnitSystem.IMPERIAL
}, function(response, status) {
if (status === ‘OK’) {
const element = response.rows[0].elements[0];
if (element.status === ‘OK’) {
const distanceMiles = element.distance.value / 1609.34; // meters to miles
const durationMinutes = element.duration.value / 60; // seconds to minutes
// Check if peak hour
const isPeak = isPeakHour(pickupTime);
const peakMultiplier = isPeak ? 1.2 : 1;
// Calculate fare components
const baseFare = rate.base;
const distanceFare = distanceMiles * rate.perMile;
const timeFare = durationMinutes * rate.perMinute;
// Calculate one-way price
let price = (baseFare + distanceFare + timeFare) * peakMultiplier;
// Double the price for return trip
if (returnTrip) {
price *= 2;
}
// Add tip if specified
let tipAmount = 0;
if (tipPercentage > 0) {
tipAmount = price * (tipPercentage / 100);
price += tipAmount;
}
// Update fare breakdown
document.getElementById(‘baseFare’).textContent = `$${baseFare.toFixed(2)}`;
document.getElementById(‘distanceFare’).textContent = `$${distanceFare.toFixed(2)}`;
document.getElementById(‘timeFare’).textContent = `$${timeFare.toFixed(2)}`;
// Show/hide peak fee
const peakFeeElement = document.querySelector(‘.peak-fee’);
if (isPeak) {
const peakFee = (baseFare + distanceFare + timeFare) * 0.2;
document.getElementById(‘peakFee’).textContent = `$${peakFee.toFixed(2)}`;
peakFeeElement.classList.remove(‘hidden’);
} else {
peakFeeElement.classList.add(‘hidden’);
}
// Show/hide tip
const tipElement = document.querySelector(‘.tip-row’);
if (tipAmount > 0) {
document.getElementById(‘tipAmount’).textContent = `$${tipAmount.toFixed(2)}`;
tipElement.classList.remove(‘hidden’);
} else {
tipElement.classList.add(‘hidden’);
}
// Update distance info
document.getElementById(‘distanceInfo’).style.display = ‘block’;
document.getElementById(‘distanceInfo’).innerHTML = `
${distanceMiles.toFixed(1)} miles • ${Math.ceil(durationMinutes)} minutes
${isPeak ? ‘• Peak hours apply’ : ”}
`;
// Display the price
document.getElementById(‘estimatedPrice’).textContent = `$${price.toFixed(2)}`;
// Save to session storage
saveFormProgress();
}
}
});
}
// Get current location
function getCurrentLocation() {
const pickupField = document.getElementById(‘pickup’);
pickupField.disabled = true;
pickupField.value = ‘Detecting your location…’;
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const geocoder = new google.maps.Geocoder();
const latlng = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
geocoder.geocode({ location: latlng }, (results, status) => {
if (status === ‘OK’ && results[0]) {
pickupField.value = results[0].formatted_address;
pickupField.dispatchEvent(new Event(‘input’));
calculatePrice();
} else {
pickupField.value = ”;
alert(‘Could not determine your address. Please enter it manually.’);
}
pickupField.disabled = false;
});
},
(error) => {
pickupField.value = ”;
pickupField.disabled = false;
alert(‘Geolocation error: ‘ + error.message);
}
);
} else {
pickupField.value = ”;
pickupField.disabled = false;
alert(‘Geolocation is not supported by your browser.’);
}
}
// Navigate to next step
function goToNextStep(e) {
const currentStep = currentFormStep;
const nextStep = parseInt(e.target.dataset.next);
// Validate current step before proceeding
if (currentStep === 1) {
const nameValid = validateField(document.getElementById(‘name’), (v) => v.length >= 2, ‘Please enter a valid name’);
const phoneValid = validateField(document.getElementById(‘phone’), (v) => /^[\d\s\-()+]{7,}$/.test(v), ‘Please enter a valid phone number’);
const emailValid = validateField(document.getElementById(’email’), (v) => v === ” || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), ‘Please enter a valid email’);
if (!nameValid || !phoneValid || !emailValid) return;
} else if (currentStep === 2) {
const pickupValid = validateField(document.getElementById(‘pickup’), (v) => v.trim() !== ”, ‘Pickup location is required’);
const destValid = validateField(document.getElementById(‘destination’), (v) => v.trim() !== ”, ‘Destination is required’);
const timeValid = validateField(document.getElementById(‘pickupTime’), (v) => v.trim() !== ”, ‘Pickup time is required’);
if (!pickupValid || !destValid || !timeValid) return;
// Calculate price when moving to confirmation step
calculatePrice();
}
// Switch steps
document.querySelector(`.form-step[data-step=”${currentStep}”]`).classList.remove(‘active’);
document.querySelector(`.form-step[data-step=”${nextStep}”]`).classList.add(‘active’);
document.querySelector(`.progress-steps .step[data-step=”${currentStep}”]`).classList.remove(‘active’);
document.querySelector(`.progress-steps .step[data-step=”${nextStep}”]`).classList.add(‘active’);
currentFormStep = nextStep;
saveFormProgress();
}
// Navigate to previous step
function goToPrevStep(e) {
const currentStep = currentFormStep;
const prevStep = parseInt(e.target.dataset.prev);
document.querySelector(`.form-step[data-step=”${currentStep}”]`).classList.remove(‘active’);
document.querySelector(`.form-step[data-step=”${prevStep}”]`).classList.add(‘active’);
document.querySelector(`.progress-steps .step[data-step=”${currentStep}”]`).classList.remove(‘active’);
document.querySelector(`.progress-steps .step[data-step=”${prevStep}”]`).classList.add(‘active’);
currentFormStep = prevStep;
}
// Toggle dark mode
function toggleDarkMode() {
document.body.classList.toggle(‘dark-mode’);
localStorage.setItem(‘darkMode’, document.body.classList.contains(‘dark-mode’));
}
// Save form progress to session storage
function saveFormProgress() {
const formData = {
step: currentFormStep,
name: document.getElementById(‘name’).value,
phone: document.getElementById(‘phone’).value,
email: document.getElementById(’email’).value,
pickup: document.getElementById(‘pickup’).value,
destination: document.getElementById(‘destination’).value,
pickupTime: document.getElementById(‘pickupTime’).value,
returnTrip: document.getElementById(‘returnTrip’).checked,
returnTime: document.getElementById(‘returnTime’).value,
vehicleType: document.getElementById(‘vehicleType’).value,
additionalInfo: document.getElementById(‘additionalInfo’).value,
tip: document.getElementById(‘selectedTip’).value
};
sessionStorage.setItem(‘taxiBookingForm’, JSON.stringify(formData));
}
// Load form progress from session storage
function loadFormProgress() {
const savedData = sessionStorage.getItem(‘taxiBookingForm’);
if (savedData)