279 lines
11 KiB
PHP
279 lines
11 KiB
PHP
<?php
|
|
session_start();
|
|
require_once 'db/config.php';
|
|
|
|
// Allow guest access with a token
|
|
$order_id = $_GET['order_id'] ?? null;
|
|
$token = $_GET['token'] ?? null;
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
|
|
if (!$order_id) {
|
|
header("Location: index.php");
|
|
exit();
|
|
}
|
|
|
|
$pdo = db();
|
|
$order = null;
|
|
|
|
if ($user_id) {
|
|
// User is logged in, verify order belongs to them
|
|
$stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?");
|
|
$stmt->execute([$order_id, $user_id]);
|
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
} elseif ($token) {
|
|
// Guest access, verify token
|
|
$stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.guest_token = ?");
|
|
$stmt->execute([$order_id, $token]);
|
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
if (!$order) {
|
|
include 'header.php';
|
|
echo "<div class='container mt-5'><div class='alert alert-danger'>Order not found or you do not have permission to view it.</div></div>";
|
|
include 'footer.php';
|
|
exit();
|
|
}
|
|
|
|
// Fetch order items
|
|
$stmt = $pdo->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?");
|
|
$stmt->execute([$order_id]);
|
|
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
include 'header.php';
|
|
?>
|
|
|
|
<div class="container mt-5">
|
|
<div class="row">
|
|
<div class="col-lg-8 offset-lg-2">
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body text-center">
|
|
<h1 class="card-title fw-bold">Thank You For Your Order!</h1>
|
|
<p class="text-muted">Order #<?php echo $order['id']; ?></p>
|
|
<p><strong>Restaurant:</strong> <?php echo htmlspecialchars($order['restaurant_name']); ?></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<h4 class="card-title text-center mb-4">Live Order Tracking</h4>
|
|
<div id="map" style="height: 400px; border-radius: .25rem;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h4 class="card-title text-center mb-4">Order Status</h4>
|
|
<div id="order-status-timeline">
|
|
<!-- Timeline will be dynamically generated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mt-4">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Order Summary</h5>
|
|
<ul class="list-group list-group-flush">
|
|
<?php foreach ($items as $item): ?>
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
<?php echo htmlspecialchars($item['item_name']); ?> (x<?php echo $item['quantity']; ?>)
|
|
<span>$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
<li class="list-group-item d-flex justify-content-between align-items-center fw-bold">
|
|
Total
|
|
<span>$<?php echo number_format($order['total_price'], 2); ?></span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="text-center mt-4">
|
|
<a href="index.php" class="btn btn-primary">Back to Home</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const orderId = <?php echo $order_id; ?>;
|
|
const token = '<?php echo $token; ?>';
|
|
const timelineContainer = document.getElementById('order-status-timeline');
|
|
const currentStatus = '<?php echo $order['status']; ?>';
|
|
|
|
const statuses = [
|
|
{ name: 'Pending', desc: 'Your order has been placed and is waiting for the restaurant to accept it.' },
|
|
{ name: 'Preparing', desc: 'The restaurant is preparing your food.' },
|
|
{ name: 'Out For Delivery', desc: 'A driver is on their way to you.' },
|
|
{ name: 'Delivered', desc: 'Your order has been delivered. Enjoy!' }
|
|
];
|
|
|
|
const cancelledStatus = { name: 'Cancelled', desc: 'This order has been cancelled.' };
|
|
|
|
function renderTimeline(status) {
|
|
timelineContainer.innerHTML = '';
|
|
let activeIndex = statuses.findIndex(s => s.name.toLowerCase() === status.toLowerCase());
|
|
|
|
if (status.toLowerCase() === 'cancelled') {
|
|
const item = document.createElement('div');
|
|
item.className = 'timeline-item timeline-cancelled';
|
|
item.innerHTML = `
|
|
<div class="timeline-icon"><i class="fas fa-times-circle"></i></div>
|
|
<div class="timeline-content">
|
|
<h5 class="fw-bold">${cancelledStatus.name}</h5>
|
|
<p class="text-muted">${cancelledStatus.desc}</p>
|
|
</div>
|
|
`;
|
|
timelineContainer.appendChild(item);
|
|
return;
|
|
}
|
|
|
|
|
|
statuses.forEach((s, index) => {
|
|
const item = document.createElement('div');
|
|
let itemClass = 'timeline-item';
|
|
let icon = '<i class="far fa-circle"></i>';
|
|
|
|
if (index < activeIndex) {
|
|
itemClass += ' timeline-complete';
|
|
icon = '<i class="fas fa-check-circle"></i>';
|
|
} else if (index === activeIndex) {
|
|
itemClass += ' timeline-active';
|
|
icon = '<i class="fas fa-dot-circle"></i>';
|
|
}
|
|
|
|
item.className = itemClass;
|
|
item.innerHTML = `
|
|
<div class="timeline-icon">${icon}</div>
|
|
<div class="timeline-content">
|
|
<h5 class="fw-bold">${s.name}</h5>
|
|
<p class="text-muted">${s.desc}</p>
|
|
</div>
|
|
`;
|
|
timelineContainer.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function fetchStatus() {
|
|
let url = `api/get_order_status.php?order_id=${orderId}`;
|
|
if (token) {
|
|
url += `&token=${token}`;
|
|
}
|
|
fetch(url)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status) {
|
|
renderTimeline(data.status);
|
|
} else if(data.error) {
|
|
console.error('Error fetching status:', data.error);
|
|
timelineContainer.innerHTML = '<div class="alert alert-warning">Could not retrieve order status. Please try again later.</div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fetch error:', error);
|
|
timelineContainer.innerHTML = '<div class="alert alert-danger">An error occurred while trying to update the order status.</div>';
|
|
});
|
|
}
|
|
|
|
renderTimeline(currentStatus);
|
|
setInterval(fetchStatus, 10000); // Poll every 10 seconds
|
|
|
|
// --- Map Logic ---
|
|
let map = null;
|
|
let driverMarker = null;
|
|
let restaurantMarker = null;
|
|
let homeMarker = null;
|
|
|
|
// Define custom icons
|
|
const driverIcon = L.icon({
|
|
iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png', // A different color or style
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
|
|
const restaurantIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
|
|
const homeIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
|
|
|
|
function initializeMap(restaurantLocation) {
|
|
if (map) return;
|
|
map = L.map('map').setView([restaurantLocation.lat, restaurantLocation.lng], 13);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
}).addTo(map);
|
|
|
|
restaurantMarker = L.marker([restaurantLocation.lat, restaurantLocation.lng], {icon: restaurantIcon}).addTo(map)
|
|
.bindPopup(`<b>${restaurantLocation.name}</b>`).openPopup();
|
|
}
|
|
|
|
function updateMap(data) {
|
|
if (!data.restaurant_location || !data.restaurant_location.lat) return;
|
|
|
|
if (!map) {
|
|
initializeMap(data.restaurant_location);
|
|
}
|
|
|
|
if (data.driver_location && data.driver_location.lat) {
|
|
const { lat, lng } = data.driver_location;
|
|
if (driverMarker) {
|
|
driverMarker.setLatLng([lat, lng]);
|
|
} else {
|
|
driverMarker = L.marker([lat, lng], {icon: driverIcon}).addTo(map)
|
|
.bindPopup('<b>Your Driver</b>');
|
|
}
|
|
// Center map between driver and restaurant
|
|
if (restaurantMarker) {
|
|
const bounds = L.latLngBounds(driverMarker.getLatLng(), restaurantMarker.getLatLng());
|
|
map.fitBounds(bounds.pad(0.3));
|
|
}
|
|
}
|
|
|
|
// Geocode delivery address to show home marker
|
|
// This requires a geocoding service. For now, we will skip this.
|
|
// Example using a hypothetical geocoding service:
|
|
// if (!homeMarker && data.delivery_address) {
|
|
// geocode(data.delivery_address).then(coords => {
|
|
// homeMarker = L.marker(coords, {icon: homeIcon}).addTo(map).bindPopup('<b>Your Address</b>');
|
|
// });
|
|
// }
|
|
}
|
|
|
|
function fetchMapData() {
|
|
let url = `api/get_order_status.php?order_id=${orderId}`;
|
|
if (token) {
|
|
url += `&token=${token}`;
|
|
}
|
|
fetch(url)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
console.error('Error fetching map data:', data.error);
|
|
} else {
|
|
updateMap(data);
|
|
}
|
|
})
|
|
.catch(error => console.error('Fetch error for map:', error));
|
|
}
|
|
|
|
fetchMapData();
|
|
setInterval(fetchMapData, 5000); // Poll every 5 seconds for map updates
|
|
});
|
|
</script>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
|
|
|
<?php include 'footer.php'; ?>
|