mirror of https://github.com/kortix-ai/suna.git
1029 lines
35 KiB
JavaScript
1029 lines
35 KiB
JavaScript
// Mapbox access token
|
|
const mapboxAccessToken = 'pk.eyJ1IjoibWFya29rcmFlbWVyIiwiYSI6ImNscGRrMWY5bTEzMDMyaHJsM3Nmc2VmYzUifQ.CxA8xsQ5iSMxUhP86oYUmg';
|
|
|
|
// DOM Elements
|
|
const menuToggle = document.getElementById('menuToggle');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const closeSidebar = document.getElementById('closeSidebar');
|
|
const pickupInput = document.getElementById('pickupLocation');
|
|
const destinationInput = document.getElementById('destinationLocation');
|
|
const searchLocationResults = document.getElementById('searchLocationResults');
|
|
const rideOptions = document.getElementById('rideOptions');
|
|
const driverPanel = document.getElementById('driverPanel');
|
|
const requestRideBtn = document.querySelector('.request-ride-btn');
|
|
const cancelTripBtn = document.querySelector('.cancel-trip-btn');
|
|
const rideTypes = document.querySelectorAll('.ride-type');
|
|
const tripHistoryBtn = document.getElementById('tripHistoryBtn');
|
|
const tripHistoryPanel = document.getElementById('tripHistoryPanel');
|
|
const backToRideBtn = document.getElementById('backToRide');
|
|
const scheduleRideBtn = document.getElementById('scheduleRideBtn');
|
|
const schedulePanel = document.getElementById('schedulePanel');
|
|
const paymentMethodBtn = document.getElementById('paymentMethodBtn');
|
|
const paymentMethodsPanel = document.getElementById('paymentMethodsPanel');
|
|
const closePaymentPanel = document.getElementById('closePaymentPanel');
|
|
const ratingPanel = document.getElementById('ratingPanel');
|
|
const ratingStars = document.querySelectorAll('.rating-star');
|
|
const submitRatingBtn = document.getElementById('submitRating');
|
|
const promoCodeInput = document.getElementById('promoCodeInput');
|
|
const applyPromoBtn = document.getElementById('applyPromoBtn');
|
|
const promoContainer = document.getElementById('promoContainer');
|
|
const selectedPaymentMethod = document.getElementById('selectedPaymentMethod');
|
|
|
|
// Global variables
|
|
let map;
|
|
let userMarker;
|
|
let destinationMarker;
|
|
let routeLine;
|
|
let driverMarker;
|
|
let currentRating = 0;
|
|
let currentTrip = null;
|
|
let liveTracking = null;
|
|
let currentRouteData = null;
|
|
let tripHistory = [
|
|
{
|
|
id: 'trip-001',
|
|
date: new Date(Date.now() - 86400000), // Yesterday
|
|
pickup: 'Times Square',
|
|
destination: 'Central Park',
|
|
driver: 'Sarah Johnson',
|
|
car: 'Honda Civic · White',
|
|
licensePlate: 'XYZ 789',
|
|
price: 18.75,
|
|
rideType: 'UberX',
|
|
rating: 5
|
|
},
|
|
{
|
|
id: 'trip-002',
|
|
date: new Date(Date.now() - 172800000), // 2 days ago
|
|
pickup: 'Grand Central Terminal',
|
|
destination: 'Brooklyn Bridge',
|
|
driver: 'Robert Smith',
|
|
car: 'Toyota Prius · Silver',
|
|
licensePlate: 'ABC 456',
|
|
price: 24.50,
|
|
rideType: 'Comfort',
|
|
rating: 4
|
|
}
|
|
];
|
|
|
|
// Initialize map
|
|
function initMap() {
|
|
mapboxgl.accessToken = mapboxAccessToken;
|
|
|
|
map = new mapboxgl.Map({
|
|
container: 'map',
|
|
style: 'mapbox://styles/mapbox/streets-v11',
|
|
center: [-74.5, 40], // Default location (New York)
|
|
zoom: 12
|
|
});
|
|
|
|
// Add time of day control to switch between day and night mode
|
|
const timeOfDayControl = document.createElement('div');
|
|
timeOfDayControl.className = 'mapboxgl-ctrl mapboxgl-ctrl-group time-of-day-control';
|
|
timeOfDayControl.innerHTML = `
|
|
<button type="button" title="Toggle day/night mode">
|
|
<i class="fas fa-moon"></i>
|
|
</button>
|
|
`;
|
|
timeOfDayControl.style.margin = '10px';
|
|
|
|
// Add click event to toggle between day and night map styles
|
|
timeOfDayControl.querySelector('button').addEventListener('click', () => {
|
|
const currentStyle = map.getStyle().name;
|
|
const icon = timeOfDayControl.querySelector('i');
|
|
|
|
if (currentStyle.includes('Dark') || currentStyle.includes('Night')) {
|
|
map.setStyle('mapbox://styles/mapbox/streets-v11');
|
|
icon.className = 'fas fa-moon';
|
|
} else {
|
|
map.setStyle('mapbox://styles/mapbox/navigation-night-v1');
|
|
icon.className = 'fas fa-sun';
|
|
}
|
|
});
|
|
|
|
document.querySelector('.map-container').appendChild(timeOfDayControl);
|
|
|
|
// Add navigation controls
|
|
map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
|
|
|
|
// Add geolocation control
|
|
map.addControl(new mapboxgl.GeolocateControl({
|
|
positionOptions: {
|
|
enableHighAccuracy: true
|
|
},
|
|
trackUserLocation: true,
|
|
showUserHeading: true
|
|
}), 'bottom-right');
|
|
|
|
// Add loading indicator
|
|
const loadingEl = document.createElement('div');
|
|
loadingEl.className = 'map-loading';
|
|
loadingEl.innerHTML = '<div class="spinner"></div><p>Loading map...</p>';
|
|
loadingEl.style.position = 'absolute';
|
|
loadingEl.style.top = '0';
|
|
loadingEl.style.left = '0';
|
|
loadingEl.style.right = '0';
|
|
loadingEl.style.bottom = '0';
|
|
loadingEl.style.backgroundColor = 'rgba(255,255,255,0.8)';
|
|
loadingEl.style.display = 'flex';
|
|
loadingEl.style.flexDirection = 'column';
|
|
loadingEl.style.alignItems = 'center';
|
|
loadingEl.style.justifyContent = 'center';
|
|
loadingEl.style.zIndex = '100';
|
|
|
|
const spinner = loadingEl.querySelector('.spinner');
|
|
spinner.style.width = '40px';
|
|
spinner.style.height = '40px';
|
|
spinner.style.border = '4px solid #f3f3f3';
|
|
spinner.style.borderTop = '4px solid #276EF1';
|
|
spinner.style.borderRadius = '50%';
|
|
spinner.style.animation = 'spin 1s linear infinite';
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
|
|
document.head.appendChild(style);
|
|
|
|
document.getElementById('map').appendChild(loadingEl);
|
|
|
|
map.on('load', () => {
|
|
// Remove loading indicator when map is loaded
|
|
setTimeout(() => {
|
|
loadingEl.style.opacity = '0';
|
|
loadingEl.style.transition = 'opacity 0.5s';
|
|
setTimeout(() => {
|
|
loadingEl.remove();
|
|
}, 500);
|
|
}, 500);
|
|
|
|
// Add custom user location marker
|
|
setTimeout(() => {
|
|
// This would normally come from the browser's geolocation API
|
|
const userLocation = [-74.006, 40.7128]; // New York City coordinates
|
|
|
|
// Create a custom marker element for user location
|
|
const el = document.createElement('div');
|
|
el.className = 'user-marker';
|
|
el.style.width = '22px';
|
|
el.style.height = '22px';
|
|
el.style.borderRadius = '50%';
|
|
el.style.backgroundColor = '#276EF1';
|
|
el.style.border = '3px solid white';
|
|
el.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
|
|
|
|
// Add pulse effect
|
|
const pulseEl = document.createElement('div');
|
|
pulseEl.className = 'marker-pulse';
|
|
el.appendChild(pulseEl);
|
|
|
|
// Add marker for user's location
|
|
userMarker = new mapboxgl.Marker(el)
|
|
.setLngLat(userLocation)
|
|
.addTo(map);
|
|
|
|
// Center map on user's location
|
|
map.flyTo({
|
|
center: userLocation,
|
|
zoom: 14,
|
|
essential: true
|
|
});
|
|
|
|
// Simulate setting pickup location to current location
|
|
pickupInput.value = "Current Location";
|
|
|
|
// Add weather widget
|
|
const weatherService = new WeatherService();
|
|
weatherService.getWeather(userLocation[1], userLocation[0])
|
|
.then(weatherData => {
|
|
const weatherWidget = weatherService.createWeatherWidget(weatherData);
|
|
document.querySelector('.map-container').appendChild(weatherWidget);
|
|
});
|
|
}, 1000);
|
|
});
|
|
|
|
return map;
|
|
}
|
|
|
|
// Event Listeners
|
|
function setupEventListeners() {
|
|
// Sidebar toggle
|
|
menuToggle.addEventListener('click', () => {
|
|
sidebar.classList.add('active');
|
|
});
|
|
|
|
closeSidebar.addEventListener('click', () => {
|
|
sidebar.classList.remove('active');
|
|
});
|
|
|
|
// Close sidebar when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (sidebar.classList.contains('active') &&
|
|
!sidebar.contains(e.target) &&
|
|
e.target !== menuToggle) {
|
|
sidebar.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// Location search
|
|
destinationInput.addEventListener('focus', () => {
|
|
showLocationSearchResults();
|
|
});
|
|
|
|
destinationInput.addEventListener('input', () => {
|
|
if (destinationInput.value.trim() !== '') {
|
|
showLocationSearchResults();
|
|
} else {
|
|
searchLocationResults.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Hide search results when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target !== destinationInput && !searchLocationResults.contains(e.target)) {
|
|
searchLocationResults.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Select ride type
|
|
rideTypes.forEach(ride => {
|
|
ride.addEventListener('click', () => {
|
|
// Remove selected class from all ride types
|
|
rideTypes.forEach(r => r.classList.remove('selected'));
|
|
|
|
// Add selected class to clicked ride type
|
|
ride.classList.add('selected');
|
|
|
|
// Update request button text
|
|
const rideTypeName = ride.querySelector('h4').textContent;
|
|
requestRideBtn.textContent = `Request ${rideTypeName}`;
|
|
});
|
|
});
|
|
|
|
// Request ride button
|
|
requestRideBtn.addEventListener('click', () => {
|
|
rideOptions.classList.add('hidden');
|
|
driverPanel.classList.remove('hidden');
|
|
|
|
// Create current trip object
|
|
currentTrip = {
|
|
id: generateTripId(),
|
|
date: new Date(),
|
|
pickup: pickupInput.value,
|
|
destination: destinationInput.value,
|
|
driver: "Michael Johnson",
|
|
car: "Toyota Camry · Black",
|
|
licensePlate: "ABC 123",
|
|
price: getSelectedRidePrice(),
|
|
rideType: getSelectedRideType(),
|
|
rating: 0
|
|
};
|
|
|
|
// Simulate driver approaching
|
|
simulateDriverApproaching();
|
|
});
|
|
|
|
// Cancel trip button
|
|
cancelTripBtn.addEventListener('click', () => {
|
|
showToast("Trip cancelled");
|
|
driverPanel.classList.add('hidden');
|
|
rideOptions.classList.remove('hidden');
|
|
|
|
// Remove driver marker and route
|
|
if (driverMarker) {
|
|
driverMarker.remove();
|
|
driverMarker = null;
|
|
}
|
|
|
|
if (routeLine && map.getSource('route')) {
|
|
map.removeLayer('route');
|
|
map.removeSource('route');
|
|
routeLine = null;
|
|
}
|
|
|
|
currentTrip = null;
|
|
});
|
|
|
|
// Saved places
|
|
const savedPlaces = document.querySelectorAll('.saved-place');
|
|
savedPlaces.forEach(place => {
|
|
place.addEventListener('click', () => {
|
|
const placeType = place.querySelector('span').textContent;
|
|
destinationInput.value = placeType;
|
|
|
|
// Show ride options
|
|
searchLocationResults.classList.add('hidden');
|
|
rideOptions.classList.remove('hidden');
|
|
|
|
// Simulate route
|
|
simulateRoute();
|
|
});
|
|
});
|
|
|
|
// Trip history button
|
|
tripHistoryBtn.addEventListener('click', () => {
|
|
sidebar.classList.remove('active');
|
|
showTripHistory();
|
|
});
|
|
|
|
// Back to ride button
|
|
if (backToRideBtn) {
|
|
backToRideBtn.addEventListener('click', () => {
|
|
tripHistoryPanel.classList.add('hidden');
|
|
schedulePanel.classList.add('hidden');
|
|
});
|
|
}
|
|
|
|
// Schedule ride button
|
|
scheduleRideBtn.addEventListener('click', () => {
|
|
schedulePanel.classList.remove('hidden');
|
|
document.getElementById('schedulePickup').textContent = pickupInput.value;
|
|
document.getElementById('scheduleDestination').textContent = destinationInput.value || "Enter destination";
|
|
});
|
|
|
|
// Confirm schedule button
|
|
document.getElementById('confirmSchedule').addEventListener('click', () => {
|
|
const scheduleDate = document.getElementById('scheduleDate').value;
|
|
const scheduleTime = document.getElementById('scheduleTime').value;
|
|
|
|
if (scheduleDate && scheduleTime) {
|
|
showToast("Ride scheduled successfully");
|
|
schedulePanel.classList.add('hidden');
|
|
|
|
// In a real app, you would save this scheduled ride
|
|
console.log(`Ride scheduled for ${scheduleDate} at ${scheduleTime}`);
|
|
} else {
|
|
showToast("Please select date and time");
|
|
}
|
|
});
|
|
|
|
// Payment method button
|
|
paymentMethodBtn.addEventListener('click', () => {
|
|
showPaymentMethods();
|
|
});
|
|
|
|
// Close payment panel
|
|
closePaymentPanel.addEventListener('click', () => {
|
|
paymentMethodsPanel.classList.add('hidden');
|
|
});
|
|
|
|
// Rating stars
|
|
ratingStars.forEach((star, index) => {
|
|
star.addEventListener('click', () => {
|
|
currentRating = index + 1;
|
|
updateRatingStars();
|
|
});
|
|
|
|
star.addEventListener('mouseover', () => {
|
|
// Temporarily show stars up to the hovered one
|
|
ratingStars.forEach((s, i) => {
|
|
if (i <= index) {
|
|
s.classList.add('active');
|
|
} else {
|
|
s.classList.remove('active');
|
|
}
|
|
});
|
|
});
|
|
|
|
star.addEventListener('mouseout', () => {
|
|
// Restore the actual rating
|
|
updateRatingStars();
|
|
});
|
|
});
|
|
|
|
// Submit rating
|
|
submitRatingBtn.addEventListener('click', () => {
|
|
if (currentRating > 0 && currentTrip) {
|
|
currentTrip.rating = currentRating;
|
|
tripHistory.unshift(currentTrip); // Add to beginning of history
|
|
|
|
// Show confetti for 5-star ratings
|
|
if (currentRating === 5) {
|
|
const confetti = new Confetti();
|
|
confetti.start();
|
|
}
|
|
|
|
showToast(`Thank you for your ${currentRating}-star rating!`);
|
|
ratingPanel.classList.add('hidden');
|
|
currentTrip = null;
|
|
currentRating = 0;
|
|
} else {
|
|
showToast("Please select a rating");
|
|
}
|
|
});
|
|
|
|
// Apply promo code
|
|
if (applyPromoBtn) {
|
|
applyPromoBtn.addEventListener('click', () => {
|
|
const promoCode = promoCodeInput.value.trim();
|
|
if (promoCode) {
|
|
applyPromoCode(promoCode);
|
|
promoCodeInput.value = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add payment method
|
|
document.getElementById('addPaymentMethod').addEventListener('click', () => {
|
|
// In a real app, this would open a form to add a new payment method
|
|
showToast("Payment method form would open here");
|
|
});
|
|
}
|
|
|
|
// Show location search results
|
|
function showLocationSearchResults() {
|
|
// Clear previous results
|
|
searchLocationResults.innerHTML = '';
|
|
searchLocationResults.classList.remove('hidden');
|
|
|
|
const searchTerm = destinationInput.value.trim().toLowerCase();
|
|
|
|
// Sample locations
|
|
const locations = [
|
|
{ name: "Times Square", address: "Manhattan, NY 10036", icon: "fa-map-marker-alt" },
|
|
{ name: "Central Park", address: "Manhattan, NY 10024", icon: "fa-tree" },
|
|
{ name: "Empire State Building", address: "20 W 34th St, NY 10001", icon: "fa-building" },
|
|
{ name: "Brooklyn Bridge", address: "Brooklyn Bridge, NY 10038", icon: "fa-archway" },
|
|
{ name: "Grand Central Terminal", address: "89 E 42nd St, NY 10017", icon: "fa-train" }
|
|
];
|
|
|
|
// Filter locations based on search term
|
|
const filteredLocations = locations.filter(location =>
|
|
location.name.toLowerCase().includes(searchTerm) ||
|
|
location.address.toLowerCase().includes(searchTerm) ||
|
|
searchTerm === ''
|
|
);
|
|
|
|
if (filteredLocations.length === 0) {
|
|
searchLocationResults.innerHTML = `
|
|
<div class="no-results">
|
|
No locations found. Try a different search.
|
|
</div>
|
|
`;
|
|
} else {
|
|
filteredLocations.forEach(location => {
|
|
const locationElement = document.createElement('div');
|
|
locationElement.className = 'location-result';
|
|
locationElement.innerHTML = `
|
|
<div class="location-icon">
|
|
<i class="fas ${location.icon}"></i>
|
|
</div>
|
|
<div class="location-details">
|
|
<div class="location-name">${location.name}</div>
|
|
<div class="location-address">${location.address}</div>
|
|
</div>
|
|
`;
|
|
|
|
locationElement.addEventListener('click', () => {
|
|
destinationInput.value = location.name;
|
|
searchLocationResults.classList.add('hidden');
|
|
rideOptions.classList.remove('hidden');
|
|
|
|
// Simulate route
|
|
simulateRoute(location.name);
|
|
});
|
|
|
|
searchLocationResults.appendChild(locationElement);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Simulate route on map
|
|
async function simulateRoute(destination = null) {
|
|
// In a real app, this would call a routing API to get directions
|
|
console.log('Simulating route from pickup to destination');
|
|
|
|
const destinationName = destination || destinationInput.value;
|
|
|
|
// Sample destination coordinates based on name
|
|
let destinationCoords;
|
|
switch(destinationName.toLowerCase()) {
|
|
case 'times square':
|
|
destinationCoords = [-73.9855, 40.7580];
|
|
break;
|
|
case 'central park':
|
|
destinationCoords = [-73.9665, 40.7812];
|
|
break;
|
|
case 'empire state building':
|
|
destinationCoords = [-73.9857, 40.7484];
|
|
break;
|
|
case 'brooklyn bridge':
|
|
destinationCoords = [-73.9969, 40.7061];
|
|
break;
|
|
case 'grand central terminal':
|
|
destinationCoords = [-73.9772, 40.7527];
|
|
break;
|
|
case 'work':
|
|
destinationCoords = [-73.9787, 40.7500];
|
|
break;
|
|
case 'home':
|
|
destinationCoords = [-73.9664, 40.7605];
|
|
break;
|
|
default:
|
|
// Random location near user
|
|
const userCoords = userMarker.getLngLat();
|
|
destinationCoords = [
|
|
userCoords.lng + (Math.random() * 0.02 - 0.01),
|
|
userCoords.lat + (Math.random() * 0.02 - 0.01)
|
|
];
|
|
}
|
|
|
|
// Add destination marker
|
|
if (destinationMarker) {
|
|
destinationMarker.remove();
|
|
}
|
|
|
|
// Create a custom marker element
|
|
const el = document.createElement('div');
|
|
el.className = 'destination-marker';
|
|
el.innerHTML = '<i class="fas fa-map-marker-alt" style="color: #000; font-size: 24px;"></i>';
|
|
el.style.marginTop = '-24px'; // Offset to position the marker correctly
|
|
|
|
destinationMarker = new mapboxgl.Marker(el)
|
|
.setLngLat(destinationCoords)
|
|
.addTo(map);
|
|
|
|
// Create a simple route using our directions helper
|
|
const userCoords = userMarker.getLngLat();
|
|
|
|
// Remove existing route if any
|
|
if (routeLine && map.getSource('route')) {
|
|
map.removeLayer('route');
|
|
map.removeSource('route');
|
|
}
|
|
|
|
try {
|
|
// Use our directions helper to get a more realistic route
|
|
const directions = new MapboxDirections(mapboxAccessToken);
|
|
const routeData = await directions.getRoute(
|
|
[userCoords.lng, userCoords.lat],
|
|
destinationCoords
|
|
);
|
|
|
|
// Add the route line
|
|
map.addSource('route', {
|
|
'type': 'geojson',
|
|
'data': {
|
|
'type': 'Feature',
|
|
'properties': {},
|
|
'geometry': routeData.routes[0].geometry
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
'id': 'route',
|
|
'type': 'line',
|
|
'source': 'route',
|
|
'layout': {
|
|
'line-join': 'round',
|
|
'line-cap': 'round'
|
|
},
|
|
'paint': {
|
|
'line-color': '#276EF1',
|
|
'line-width': 4
|
|
}
|
|
});
|
|
|
|
routeLine = true;
|
|
|
|
// Store route data for later use
|
|
currentRouteData = routeData;
|
|
|
|
// Calculate and display estimated time and distance
|
|
const distance = (routeData.routes[0].distance / 1000).toFixed(1); // km
|
|
const duration = Math.round(routeData.routes[0].duration / 60); // minutes
|
|
|
|
// Update ride options with ETA
|
|
document.querySelectorAll('.ride-type p').forEach(el => {
|
|
const baseText = el.textContent.split('·')[1] || '';
|
|
el.textContent = `${duration} min away · ${baseText.trim()}`;
|
|
});
|
|
|
|
// Show distance and ETA in a tooltip
|
|
const tooltipEl = document.createElement('div');
|
|
tooltipEl.className = 'route-tooltip';
|
|
tooltipEl.innerHTML = `
|
|
<div style="background-color: white; padding: 8px 12px; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); font-size: 14px;">
|
|
<div style="font-weight: 500;">${duration} min (${distance} km)</div>
|
|
<div style="color: #757575; font-size: 12px;">Estimated trip time</div>
|
|
</div>
|
|
`;
|
|
tooltipEl.style.position = 'absolute';
|
|
tooltipEl.style.top = '50%';
|
|
tooltipEl.style.left = '50%';
|
|
tooltipEl.style.transform = 'translate(-50%, -50%)';
|
|
tooltipEl.style.zIndex = '100';
|
|
tooltipEl.style.pointerEvents = 'none';
|
|
tooltipEl.style.opacity = '0';
|
|
tooltipEl.style.transition = 'opacity 0.3s ease';
|
|
|
|
document.querySelector('.map-container').appendChild(tooltipEl);
|
|
|
|
setTimeout(() => {
|
|
tooltipEl.style.opacity = '1';
|
|
|
|
setTimeout(() => {
|
|
tooltipEl.style.opacity = '0';
|
|
setTimeout(() => tooltipEl.remove(), 300);
|
|
}, 3000);
|
|
}, 300);
|
|
|
|
} catch (error) {
|
|
console.error('Error generating route:', error);
|
|
|
|
// Fallback to simple straight line if directions fail
|
|
map.addSource('route', {
|
|
'type': 'geojson',
|
|
'data': {
|
|
'type': 'Feature',
|
|
'properties': {},
|
|
'geometry': {
|
|
'type': 'LineString',
|
|
'coordinates': [
|
|
[userCoords.lng, userCoords.lat],
|
|
destinationCoords
|
|
]
|
|
}
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
'id': 'route',
|
|
'type': 'line',
|
|
'source': 'route',
|
|
'layout': {
|
|
'line-join': 'round',
|
|
'line-cap': 'round'
|
|
},
|
|
'paint': {
|
|
'line-color': '#276EF1',
|
|
'line-width': 4
|
|
}
|
|
});
|
|
|
|
routeLine = true;
|
|
}
|
|
|
|
// Fit map to show both markers
|
|
const bounds = new mapboxgl.LngLatBounds()
|
|
.extend([userCoords.lng, userCoords.lat])
|
|
.extend(destinationCoords);
|
|
|
|
map.fitBounds(bounds, {
|
|
padding: 100,
|
|
duration: 1000
|
|
});
|
|
}
|
|
|
|
// Simulate driver approaching
|
|
function simulateDriverApproaching() {
|
|
// In a real app, this would show real-time updates of the driver's location
|
|
console.log('Driver is approaching pickup location');
|
|
|
|
// Create driver location (slightly away from pickup)
|
|
const userCoords = userMarker.getLngLat();
|
|
const driverCoords = [
|
|
userCoords.lng + 0.008,
|
|
userCoords.lat - 0.005
|
|
];
|
|
|
|
// Add driver marker
|
|
if (driverMarker) {
|
|
driverMarker.remove();
|
|
}
|
|
|
|
// Create a car element for the driver marker
|
|
const el = document.createElement('div');
|
|
el.className = 'driver-marker';
|
|
el.innerHTML = '<i class="fas fa-car" style="color: #000; font-size: 20px;"></i>';
|
|
el.style.width = '30px';
|
|
el.style.height = '30px';
|
|
el.style.borderRadius = '50%';
|
|
el.style.backgroundColor = '#fff';
|
|
el.style.display = 'flex';
|
|
el.style.justifyContent = 'center';
|
|
el.style.alignItems = 'center';
|
|
el.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
|
|
|
|
// Add pulse effect behind the driver marker
|
|
const pulseEl = document.createElement('div');
|
|
pulseEl.className = 'marker-pulse';
|
|
el.appendChild(pulseEl);
|
|
|
|
driverMarker = new mapboxgl.Marker(el)
|
|
.setLngLat(driverCoords)
|
|
.addTo(map);
|
|
|
|
// Update driver ETA text with animation
|
|
const etaElement = document.querySelector('.driver-eta h3');
|
|
etaElement.classList.add('driver-approaching');
|
|
|
|
// Simulate driver movement
|
|
let progress = 0;
|
|
const totalSteps = 60; // 60 steps for 3 minutes (3 seconds per step in this demo)
|
|
|
|
const etaElement = document.querySelector('.driver-eta h3');
|
|
let minutes = 3;
|
|
let seconds = 0;
|
|
|
|
// Fit map to show both markers
|
|
const bounds = new mapboxgl.LngLatBounds()
|
|
.extend([userCoords.lng, userCoords.lat])
|
|
.extend(driverCoords);
|
|
|
|
map.fitBounds(bounds, {
|
|
padding: 100,
|
|
duration: 1000
|
|
});
|
|
|
|
const driverInterval = setInterval(() => {
|
|
progress++;
|
|
seconds--;
|
|
|
|
if (seconds < 0) {
|
|
minutes--;
|
|
seconds = 59;
|
|
}
|
|
|
|
// Update driver position
|
|
if (progress <= totalSteps) {
|
|
const newLng = driverCoords[0] - (progress * (driverCoords[0] - userCoords.lng) / totalSteps);
|
|
const newLat = driverCoords[1] - (progress * (driverCoords[1] - userCoords.lat) / totalSteps);
|
|
|
|
driverMarker.setLngLat([newLng, newLat]);
|
|
|
|
// Rotate the car icon to face the direction of travel
|
|
if (progress > 1) {
|
|
const angle = Math.atan2(
|
|
newLat - driverMarker.getLngLat().lat,
|
|
newLng - driverMarker.getLngLat().lng
|
|
) * 180 / Math.PI;
|
|
el.style.transform = `rotate(${90 + angle}deg)`;
|
|
}
|
|
}
|
|
|
|
// Update ETA text
|
|
if (minutes === 0 && seconds === 0) {
|
|
clearInterval(driverInterval);
|
|
etaElement.textContent = 'Your driver has arrived';
|
|
|
|
// Start live tracking
|
|
if (currentRouteData) {
|
|
liveTracking = new LiveTracking(map, currentRouteData);
|
|
liveTracking.start(currentRouteData);
|
|
}
|
|
|
|
// Show rating panel after a delay (simulating end of trip)
|
|
setTimeout(() => {
|
|
if (liveTracking) {
|
|
liveTracking.stop();
|
|
}
|
|
driverPanel.classList.add('hidden');
|
|
ratingPanel.classList.remove('hidden');
|
|
document.getElementById('ratingDriverName').textContent = currentTrip.driver;
|
|
}, 15000);
|
|
} else {
|
|
const minuteText = minutes === 1 ? 'min' : 'mins';
|
|
if (minutes > 0) {
|
|
etaElement.textContent = `Your driver is arriving in ${minutes}:${seconds.toString().padStart(2, '0')} ${minuteText}`;
|
|
} else {
|
|
etaElement.textContent = `Your driver is arriving in ${seconds} seconds`;
|
|
}
|
|
}
|
|
}, 50); // Speed up for demo (50ms instead of 1000ms)
|
|
}
|
|
|
|
// Show trip history
|
|
function showTripHistory() {
|
|
tripHistoryPanel.classList.remove('hidden');
|
|
const tripHistoryList = document.getElementById('tripHistoryList');
|
|
tripHistoryList.innerHTML = '';
|
|
|
|
if (tripHistory.length === 0) {
|
|
tripHistoryList.innerHTML = `
|
|
<div class="no-results">
|
|
You haven't taken any trips yet.
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
tripHistory.forEach(trip => {
|
|
const tripDate = formatDate(trip.date);
|
|
const tripElement = document.createElement('div');
|
|
tripElement.className = 'trip-item';
|
|
tripElement.innerHTML = `
|
|
<div class="trip-header">
|
|
<div class="trip-date">${tripDate}</div>
|
|
<div class="trip-price">$${trip.price.toFixed(2)}</div>
|
|
</div>
|
|
<div class="trip-route">
|
|
<div class="trip-from">${trip.pickup}</div>
|
|
<div class="trip-to">${trip.destination}</div>
|
|
</div>
|
|
<div class="trip-footer">
|
|
<div class="trip-car">${trip.car}</div>
|
|
<div class="trip-rating">
|
|
${generateRatingStars(trip.rating)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
tripHistoryList.appendChild(tripElement);
|
|
});
|
|
}
|
|
|
|
// Show payment methods
|
|
function showPaymentMethods() {
|
|
paymentMethodsPanel.classList.remove('hidden');
|
|
const paymentMethodsList = document.getElementById('paymentMethods');
|
|
paymentMethodsList.innerHTML = '';
|
|
|
|
// Sample payment methods
|
|
const paymentMethods = [
|
|
{ id: 'card-1', name: 'Personal Card', info: '•••• 4242', icon: 'fa-credit-card', selected: true },
|
|
{ id: 'card-2', name: 'Business Card', info: '•••• 5678', icon: 'fa-credit-card', selected: false },
|
|
{ id: 'paypal', name: 'PayPal', info: 'john.doe@example.com', icon: 'fa-paypal', selected: false },
|
|
{ id: 'apple-pay', name: 'Apple Pay', info: 'iPhone', icon: 'fa-apple-pay', selected: false }
|
|
];
|
|
|
|
paymentMethods.forEach(method => {
|
|
const methodElement = document.createElement('div');
|
|
methodElement.className = `payment-method ${method.selected ? 'selected' : ''}`;
|
|
methodElement.innerHTML = `
|
|
<div class="payment-icon">
|
|
<i class="fab ${method.icon}"></i>
|
|
</div>
|
|
<div class="payment-details">
|
|
<div class="payment-name">${method.name}</div>
|
|
<div class="payment-info">${method.info}</div>
|
|
</div>
|
|
`;
|
|
|
|
methodElement.addEventListener('click', () => {
|
|
// Update selected payment method
|
|
document.querySelectorAll('.payment-method').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
methodElement.classList.add('selected');
|
|
|
|
// Update displayed payment method
|
|
selectedPaymentMethod.textContent = `${method.name} ${method.info}`;
|
|
|
|
// Close panel after selection
|
|
setTimeout(() => {
|
|
paymentMethodsPanel.classList.add('hidden');
|
|
}, 300);
|
|
});
|
|
|
|
paymentMethodsList.appendChild(methodElement);
|
|
});
|
|
}
|
|
|
|
// Apply promo code
|
|
function applyPromoCode(code) {
|
|
// Sample promo codes
|
|
const promoCodes = {
|
|
'WELCOME10': { discount: '10% off', valid: true },
|
|
'SUMMER20': { discount: '20% off', valid: true },
|
|
'RIDE5': { discount: '$5 off', valid: true }
|
|
};
|
|
|
|
if (promoCodes[code] && promoCodes[code].valid) {
|
|
showToast(`Promo code ${code} applied!`);
|
|
|
|
// Add promo to the list
|
|
const promoElement = document.createElement('div');
|
|
promoElement.className = 'applied-promo';
|
|
promoElement.innerHTML = `
|
|
<div class="promo-info">
|
|
<i class="fas fa-tag"></i>
|
|
<span>${code} - ${promoCodes[code].discount}</span>
|
|
</div>
|
|
<button class="remove-promo">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
// Add remove functionality
|
|
promoElement.querySelector('.remove-promo').addEventListener('click', () => {
|
|
promoElement.remove();
|
|
showToast('Promo code removed');
|
|
});
|
|
|
|
if (promoContainer) {
|
|
promoContainer.appendChild(promoElement);
|
|
}
|
|
} else {
|
|
showToast('Invalid promo code');
|
|
}
|
|
}
|
|
|
|
// Update rating stars
|
|
function updateRatingStars() {
|
|
ratingStars.forEach((star, i) => {
|
|
if (i < currentRating) {
|
|
star.classList.add('active');
|
|
} else {
|
|
star.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Generate rating stars HTML
|
|
function generateRatingStars(rating) {
|
|
let starsHtml = '';
|
|
for (let i = 1; i <= 5; i++) {
|
|
if (i <= rating) {
|
|
starsHtml += '<i class="fas fa-star active"></i>';
|
|
} else {
|
|
starsHtml += '<i class="fas fa-star"></i>';
|
|
}
|
|
}
|
|
return starsHtml;
|
|
}
|
|
|
|
// Format date
|
|
function formatDate(date) {
|
|
const now = new Date();
|
|
const yesterday = new Date(now);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
if (date.toDateString() === now.toDateString()) {
|
|
return 'Today';
|
|
} else if (date.toDateString() === yesterday.toDateString()) {
|
|
return 'Yesterday';
|
|
} else {
|
|
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
}
|
|
}
|
|
|
|
// Get selected ride price
|
|
function getSelectedRidePrice() {
|
|
const selectedRide = document.querySelector('.ride-type.selected');
|
|
if (selectedRide) {
|
|
const priceText = selectedRide.querySelector('.ride-price').textContent;
|
|
return parseFloat(priceText.replace('$', ''));
|
|
}
|
|
return 15.49; // Default price
|
|
}
|
|
|
|
// Get selected ride type
|
|
function getSelectedRideType() {
|
|
const selectedRide = document.querySelector('.ride-type.selected');
|
|
if (selectedRide) {
|
|
return selectedRide.querySelector('h4').textContent;
|
|
}
|
|
return 'UberX'; // Default ride type
|
|
}
|
|
|
|
// Generate trip ID
|
|
function generateTripId() {
|
|
return 'trip-' + Math.random().toString(36).substr(2, 9);
|
|
}
|
|
|
|
// Show toast message
|
|
function showToast(message) {
|
|
// Create toast if it doesn't exist
|
|
let toast = document.getElementById('toast');
|
|
if (!toast) {
|
|
toast = document.createElement('div');
|
|
toast.id = 'toast';
|
|
document.body.appendChild(toast);
|
|
}
|
|
|
|
toast.textContent = message;
|
|
toast.classList.add('show');
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
// Initialize the app
|
|
function initApp() {
|
|
map = initMap();
|
|
setupEventListeners();
|
|
|
|
// Initialize payment method display
|
|
if (selectedPaymentMethod) {
|
|
selectedPaymentMethod.textContent = "Personal Card •••• 4242";
|
|
}
|
|
|
|
// Add animation classes to elements
|
|
document.querySelectorAll('.ride-type').forEach(el => {
|
|
el.classList.add('fade-in');
|
|
});
|
|
|
|
document.querySelector('.request-ride-btn').classList.add('slide-up');
|
|
|
|
// Add pulse animation to the pickup dot
|
|
const pickupDot = document.querySelector('.input-icon.pickup .dot');
|
|
if (pickupDot) {
|
|
pickupDot.classList.add('pulse');
|
|
}
|
|
|
|
// Initialize enhanced UI elements
|
|
window.initEnhancedUI();
|
|
}
|
|
|
|
// Start the app when the DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', initApp); |