V22
This commit is contained in:
parent
2be0d2d4c4
commit
8795a633f6
62
admin/export_events.php
Normal file
62
admin/export_events.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
require_once '../db/config.php';
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Filters
|
||||
$event_type = $_GET['event_type'] ?? '';
|
||||
$severity = $_GET['severity'] ?? '';
|
||||
$date_range = $_GET['date_range'] ?? '';
|
||||
|
||||
$where_clauses = [];
|
||||
$params = [];
|
||||
|
||||
if ($event_type) {
|
||||
$where_clauses[] = 'event_type = ?';
|
||||
$params[] = $event_type;
|
||||
}
|
||||
|
||||
if ($severity) {
|
||||
$where_clauses[] = 'severity = ?';
|
||||
$params[] = $severity;
|
||||
}
|
||||
|
||||
if ($date_range) {
|
||||
list($start_date, $end_date) = explode(' - ', $date_range);
|
||||
$where_clauses[] = 'timestamp BETWEEN ? AND ?';
|
||||
$params[] = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$params[] = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM system_events";
|
||||
if (!empty($where_clauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $where_clauses);
|
||||
}
|
||||
$sql .= " ORDER BY timestamp DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
header('Content-Type: text/csv');
|
||||
header('Content-Disposition: attachment; filename="system_events_' . date('Y-m-d') . '.csv"');
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// Add BOM to fix UTF-8 in Excel
|
||||
fputs($output, "\xEF\xBB\xBF");
|
||||
|
||||
fputcsv($output, array_keys($events[0] ?? []));
|
||||
|
||||
foreach ($events as $event) {
|
||||
fputcsv($output, $event);
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
@ -1,3 +1,17 @@
|
||||
</div>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
$('input[name="date_range"]').daterangepicker({
|
||||
opens: 'left'
|
||||
}, function(start, end, label) {
|
||||
console.log("A new date selection was made: " + start.format('YYYY-MM-DD') + ' to ' + end.format('YYYY-MM-DD'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -11,7 +11,9 @@ if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== tru
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
@ -43,6 +45,9 @@ if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== tru
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="notification_emails.php">Notification Emails</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="system_events.php">System Events</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
include 'header.php';
|
||||
require_once '../db/config.php';
|
||||
require_once '../includes/weather_service.php';
|
||||
|
||||
// Check if the user is logged in as an admin
|
||||
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
||||
@ -10,6 +11,14 @@ if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== tru
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Update weather data if it's older than 15 minutes
|
||||
$stmt_weather_time = $pdo->query("SELECT timestamp FROM weather_status ORDER BY timestamp DESC LIMIT 1");
|
||||
$last_update_time = $stmt_weather_time->fetchColumn();
|
||||
|
||||
if (!$last_update_time || (time() - strtotime($last_update_time)) > 900) { // 900 seconds = 15 minutes
|
||||
update_weather_data();
|
||||
}
|
||||
|
||||
// Analytics Queries
|
||||
// Total Revenue
|
||||
$stmt_revenue = $pdo->prepare("SELECT SUM(total_price) as total_revenue FROM orders WHERE status = ?");
|
||||
@ -69,6 +78,57 @@ $restaurants = $stmt_restaurants->fetchAll();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weather Monitor -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Weather Monitor
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php
|
||||
// Fetch latest weather data
|
||||
$stmt_weather = $pdo->query("SELECT * FROM weather_status ORDER BY timestamp DESC LIMIT 1");
|
||||
$weather = $stmt_weather->fetch();
|
||||
|
||||
if ($weather) {
|
||||
$alert_class = 'bg-success'; // Green for safe
|
||||
if ($weather['precipitation'] > 0 || $weather['wind_speed'] > 10) {
|
||||
$alert_class = 'bg-warning'; // Yellow for caution
|
||||
}
|
||||
if ($weather['alert_status']) {
|
||||
$alert_class = 'bg-danger'; // Red for emergency
|
||||
}
|
||||
?>
|
||||
<div class="alert <?php echo $alert_class; ?> text-white">
|
||||
<strong>Status:</strong> <?php echo htmlspecialchars($weather['alert_message']); ?>
|
||||
</div>
|
||||
<p><strong>Temperature:</strong> <?php echo round($weather['temperature'], 1); ?> °C</p>
|
||||
<p><strong>Wind Speed:</strong> <?php echo round($weather['wind_speed'], 1); ?> m/s</p>
|
||||
<p><strong>Humidity:</strong> <?php echo round($weather['humidity'], 1); ?>%</p>
|
||||
<p><strong>Precipitation (last hour):</strong> <?php echo $weather['precipitation']; ?> mm</p>
|
||||
|
||||
<?php
|
||||
// Emergency Shutdown Button
|
||||
$stmt_shutdown = $pdo->prepare("SELECT value FROM settings WHERE name = ?");
|
||||
$stmt_shutdown->execute(['emergency_shutdown']);
|
||||
$shutdown_status = $stmt_shutdown->fetchColumn();
|
||||
$shutdown_active = ($shutdown_status === 'true');
|
||||
?>
|
||||
<form action="toggle_emergency_shutdown.php" method="POST" class="mt-3">
|
||||
<button type="submit" class="btn <?php echo $shutdown_active ? 'btn-success' : 'btn-danger'; ?>">
|
||||
<?php echo $shutdown_active ? 'Activate Ordering' : 'Emergency Shutdown'; ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php } else { ?>
|
||||
<p>No weather data available.</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
|
||||
124
admin/system_events.php
Normal file
124
admin/system_events.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
require_once 'header.php';
|
||||
require_once '../db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Filters
|
||||
$event_type = $_GET['event_type'] ?? '';
|
||||
$severity = $_GET['severity'] ?? '';
|
||||
$date_range = $_GET['date_range'] ?? '';
|
||||
|
||||
$where_clauses = [];
|
||||
$params = [];
|
||||
|
||||
if ($event_type) {
|
||||
$where_clauses[] = 'event_type = ?';
|
||||
$params[] = $event_type;
|
||||
}
|
||||
|
||||
if ($severity) {
|
||||
$where_clauses[] = 'severity = ?';
|
||||
$params[] = $severity;
|
||||
}
|
||||
|
||||
if ($date_range) {
|
||||
list($start_date, $end_date) = explode(' - ', $date_range);
|
||||
$where_clauses[] = 'timestamp BETWEEN ? AND ?';
|
||||
$params[] = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$params[] = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM system_events";
|
||||
if (!empty($where_clauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $where_clauses);
|
||||
}
|
||||
$sql .= " ORDER BY timestamp DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$events = $stmt->fetchAll();
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2>System Events Log</h2>
|
||||
<p class="text-muted">Monitor weather alerts, emergency actions, and system status changes.</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-primary" onclick="location.reload();">🔄 Refresh</button>
|
||||
<a href="export_events.php?<?= http_build_query($_GET) ?>" class="btn btn-secondary">⬇ Export Log (CSV)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="event_type" class="form-label">Event Type</label>
|
||||
<select id="event_type" name="event_type" class="form-select">
|
||||
<option value="">All</option>
|
||||
<option value="Weather Alert" <?= $event_type === 'Weather Alert' ? 'selected' : '' ?>>Weather Alert</option>
|
||||
<option value="Emergency Shutdown" <?= $event_type === 'Emergency Shutdown' ? 'selected' : '' ?>>Emergency Shutdown</option>
|
||||
<option value="System Restart" <?= $event_type === 'System Restart' ? 'selected' : '' ?>>System Restart</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="date_range" class="form-label">Date Range</label>
|
||||
<input type="text" id="date_range" name="date_range" class="form-control" value="<?= htmlspecialchars($date_range) ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="severity" class="form-label">Severity</label>
|
||||
<select id="severity" name="severity" class="form-select">
|
||||
<option value="">All</option>
|
||||
<option value="Info" <?= $severity === 'Info' ? 'selected' : '' ?>>Info</option>
|
||||
<option value="Warning" <?= $severity === 'Warning' ? 'selected' : '' ?>>Warning</option>
|
||||
<option value="Emergency" <?= $severity === 'Emergency' ? 'selected' : '' ?>>Emergency</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary me-2">Apply Filters</button>
|
||||
<a href="system_events.php" class="btn btn-outline-secondary">Clear</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Event ID</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Event Type</th>
|
||||
<th>Description</th>
|
||||
<th>Severity</th>
|
||||
<th>Triggered By</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($events)):
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">No events found.</td>
|
||||
</tr>
|
||||
<?php else:
|
||||
foreach ($events as $event):
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($event['id']) ?></td>
|
||||
<td><?= htmlspecialchars($event['timestamp']) ?></td>
|
||||
<td><?= htmlspecialchars($event['event_type']) ?></td>
|
||||
<td><?= htmlspecialchars($event['description']) ?></td>
|
||||
<td><?= htmlspecialchars($event['severity']) ?></td>
|
||||
<td><?= htmlspecialchars($event['triggered_by']) ?></td>
|
||||
<td><?= htmlspecialchars($event['status']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach;
|
||||
endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
35
admin/toggle_emergency_shutdown.php
Normal file
35
admin/toggle_emergency_shutdown.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once '../db/config.php';
|
||||
|
||||
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Get current shutdown status
|
||||
$stmt_shutdown = $pdo->prepare("SELECT value FROM settings WHERE name = ?");
|
||||
$stmt_shutdown->execute(['emergency_shutdown']);
|
||||
$shutdown_status = $stmt_shutdown->fetchColumn();
|
||||
|
||||
// Toggle the status
|
||||
$new_status = ($shutdown_status === 'true') ? 'false' : 'true';
|
||||
|
||||
// Update or insert the setting
|
||||
$stmt_update = $pdo->prepare("INSERT INTO settings (name, value) VALUES (?, ?) ON CONFLICT (name) DO UPDATE SET value = ?");
|
||||
$stmt_update->execute(['emergency_shutdown', $new_status, $new_status]);
|
||||
|
||||
// Log the event
|
||||
$event_type = ($new_status === 'true') ? 'Emergency Shutdown' : 'System Restart';
|
||||
$description = ($new_status === 'true') ? 'Emergency shutdown activated' : 'System restarted';
|
||||
$severity = ($new_status === 'true') ? 'Emergency' : 'Info';
|
||||
$triggered_by = $_SESSION['admin_email'] ?? 'Admin';
|
||||
$status = ($new_status === 'true') ? 'Ongoing' : 'Resolved';
|
||||
|
||||
$stmt_log = $pdo->prepare("INSERT INTO system_events (event_type, description, severity, triggered_by, status) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt_log->execute([$event_type, $description, $severity, $triggered_by, $status]);
|
||||
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
29
api/get_new_orders.php
Normal file
29
api/get_new_orders.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// Ensure the user is a logged-in restaurant owner
|
||||
if (!isset($_SESSION['restaurant_id'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Access denied.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$restaurant_id = $_SESSION['restaurant_id'];
|
||||
$last_check_timestamp = $_GET['since'] ?? '1970-01-01 00:00:00';
|
||||
|
||||
// Fetch new orders since the last check
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT o.id, o.total_price, o.status, o.created_at, u.name as user_name
|
||||
FROM orders o
|
||||
JOIN users u ON o.user_id = u.id
|
||||
WHERE o.restaurant_id = ? AND o.created_at > ?
|
||||
ORDER BY o.created_at DESC
|
||||
");
|
||||
$stmt->execute([$restaurant_id, $last_check_timestamp]);
|
||||
$new_orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($new_orders);
|
||||
?>
|
||||
74
api/support.php
Normal file
74
api/support.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once '../db/config.php';
|
||||
require_once '../mail/MailService.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'message' => 'Method Not Allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$full_name = trim($_POST['full_name'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? null);
|
||||
$subject = trim($_POST['subject'] ?? '');
|
||||
$message = trim($_POST['message'] ?? '');
|
||||
$latitude = !empty($_POST['latitude']) ? trim($_POST['latitude']) : null;
|
||||
$longitude = !empty($_POST['longitude']) ? trim($_POST['longitude']) : null;
|
||||
|
||||
if (empty($full_name) || empty($email) || empty($subject) || empty($message) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Please fill in all required fields and provide a valid email.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$stmt = $db->prepare(
|
||||
"INSERT INTO support_tickets (full_name, email, phone, subject, message, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->execute([$full_name, $email, $phone, $subject, $message, $latitude, $longitude]);
|
||||
|
||||
// Notify support team
|
||||
$support_email_to = 'support@majuroeats.com';
|
||||
$support_email_subject = "New Support Ticket: " . htmlspecialchars($subject);
|
||||
$support_email_html = "
|
||||
<h1>New Support Inquiry</h1>
|
||||
<p><strong>Name:</strong> " . htmlspecialchars($full_name) . "</p>
|
||||
<p><strong>Email:</strong> " . htmlspecialchars($email) . "</p>
|
||||
" . ($phone ? "<p><strong>Phone:</strong> " . htmlspecialchars($phone) . "</p>" : "") . "
|
||||
<p><strong>Subject:</strong> " . htmlspecialchars($subject) . "</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>" . nl2br(htmlspecialchars($message)) . "</p>
|
||||
" . ($latitude && $longitude ? "<p><strong>Location:</strong> <a href=\"https://www.google.com/maps?q={$latitude},{$longitude}\" target=\"_blank\">View on Map</a></p>" : "") . "
|
||||
";
|
||||
$support_email_text = strip_tags($support_email_html);
|
||||
MailService::sendMail($support_email_to, $support_email_subject, $support_email_html, $support_email_text, ['reply_to' => $email]);
|
||||
|
||||
// Send confirmation to user
|
||||
$user_email_subject = "We've received your message | MajuroEats Support";
|
||||
$user_email_html = "
|
||||
<h1>Thank You For Reaching Out!</h1>
|
||||
<p>Hi " . htmlspecialchars($full_name) . ",</p>
|
||||
<p>We've received your support request and a member of our team will get back to you shortly. Here is a copy of your message:</p>
|
||||
<hr>
|
||||
<p><strong>Subject:</strong> " . htmlspecialchars($subject) . "</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>" . nl2br(htmlspecialchars($message)) . "</p>
|
||||
<hr>
|
||||
<p>With thanks,</p>
|
||||
<p>The MajuroEats Team</p>
|
||||
";
|
||||
$user_email_text = strip_tags($user_email_html);
|
||||
MailService::sendMail($email, $user_email_subject, $user_email_html, $user_email_text);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Thank you! Our support team will contact you soon.']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
// In a real app, you would log this error, not expose it to the user.
|
||||
echo json_encode(['success' => false, 'message' => 'An unexpected error occurred. Please try again later.', 'error' => $e->getMessage()]);
|
||||
}
|
||||
@ -23,7 +23,7 @@ MajuroEats Theme
|
||||
/* --- Global Styles --- */
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
background-image: url('../assets/pasted-20251016-135055-b29d8da1.png');
|
||||
background-image: url('../assets/pasted-20251016-192041-2abf91d9.jpg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-position: center;
|
||||
@ -188,11 +188,11 @@ main {
|
||||
|
||||
/* --- Hero Section --- */
|
||||
.hero-section {
|
||||
position: relative;
|
||||
background-image: url('../assets/pasted-20251016-083643-5429546d.jpg');
|
||||
background-image: url('../assets/pasted-20251016-192041-2abf91d9.jpg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-position: center;
|
||||
color: var(--coconut-white);
|
||||
position: relative;
|
||||
padding: 120px 0;
|
||||
text-align: center;
|
||||
}
|
||||
@ -203,7 +203,6 @@ main {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.hero-content-container {
|
||||
@ -484,7 +483,74 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Location Modal --- */
|
||||
|
||||
|
||||
/* --- Empty State --- */
|
||||
.empty-state {
|
||||
padding: 80px 40px;
|
||||
background-color: var(--sand);
|
||||
border-radius: var(--border-radius);
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 800;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-light);
|
||||
max-width: 400px;
|
||||
margin: 10px auto 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 15px 35px;
|
||||
border-radius: 50px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
border: 2px solid transparent;
|
||||
transition: var(--transition);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--coral);
|
||||
color: var(--coconut-white);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--orange);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-medium);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: var(--coconut-white);
|
||||
border-color: var(--coconut-white);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--coconut-white);
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.location-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
||||
@ -1,68 +1,87 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
const navActions = document.querySelector('.nav-actions');
|
||||
|
||||
if (hamburger) {
|
||||
hamburger.addEventListener('click', () => {
|
||||
navLinks.classList.toggle('active');
|
||||
navActions.classList.toggle('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Location Modal
|
||||
const locationModal = document.getElementById('location-modal');
|
||||
const mapElement = document.getElementById('map');
|
||||
const modal = document.getElementById('location-modal');
|
||||
const pinLocationBtn = document.getElementById('pin-location-btn');
|
||||
const closeModalBtn = document.querySelector('.close-button');
|
||||
const confirmLocationBtn = document.getElementById('confirm-location');
|
||||
const useLocationBtn = document.getElementById('use-location-btn');
|
||||
const closeBtn = document.querySelector('.close-btn');
|
||||
const confirmLocationBtn = document.getElementById('confirm-location-btn');
|
||||
const findRestaurantsBtn = document.querySelector('.find-restaurants-btn');
|
||||
|
||||
let map, marker;
|
||||
let markerPosition = { lat: 6.9271, lng: 171.1845 }; // Default to Majuro
|
||||
let selectedCoords = null;
|
||||
|
||||
function initMap() {
|
||||
if (map) {
|
||||
map.remove();
|
||||
}
|
||||
map = L.map('map').setView(markerPosition, 13);
|
||||
function initMap(lat, lng) {
|
||||
if (map) map.remove();
|
||||
map = L.map(mapElement).setView([lat, lng], 13);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
marker = L.marker([lat, lng], { draggable: true }).addTo(map);
|
||||
|
||||
marker = L.marker(markerPosition, { draggable: true }).addTo(map);
|
||||
map.on('click', function(e) {
|
||||
marker.setLatLng(e.latlng);
|
||||
selectedCoords = e.latlng;
|
||||
});
|
||||
|
||||
marker.on('dragend', function(event) {
|
||||
const position = marker.getLatLng();
|
||||
markerPosition = { lat: position.lat, lng: position.lng };
|
||||
marker.on('dragend', function(e) {
|
||||
selectedCoords = marker.getLatLng();
|
||||
});
|
||||
}
|
||||
|
||||
if (pinLocationBtn) {
|
||||
pinLocationBtn.addEventListener('click', () => {
|
||||
locationModal.style.display = 'block';
|
||||
// Invalidate map size on modal open to ensure it renders correctly
|
||||
setTimeout(initMap, 10);
|
||||
pinLocationBtn.addEventListener('click', function() {
|
||||
modal.style.display = 'block';
|
||||
// Default to a central Majuro location
|
||||
initMap(7.09, 171.38);
|
||||
});
|
||||
}
|
||||
|
||||
if (closeModalBtn) {
|
||||
closeModalBtn.addEventListener('click', () => {
|
||||
locationModal.style.display = 'none';
|
||||
if (useLocationBtn) {
|
||||
useLocationBtn.addEventListener('click', function() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
const userCoords = { lat: position.coords.latitude, lng: position.coords.longitude };
|
||||
sessionStorage.setItem('userLocation', JSON.stringify(userCoords));
|
||||
findRestaurantsBtn.click();
|
||||
}, function() {
|
||||
alert('Could not get your location. Please pin it on the map.');
|
||||
});
|
||||
} else {
|
||||
alert('Geolocation is not supported by this browser.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target == locationModal) {
|
||||
locationModal.style.display = 'none';
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
}
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
if (confirmLocationBtn) {
|
||||
confirmLocationBtn.addEventListener('click', () => {
|
||||
sessionStorage.setItem('user_latitude', markerPosition.lat);
|
||||
sessionStorage.setItem('user_longitude', markerPosition.lng);
|
||||
locationModal.style.display = 'none';
|
||||
// Optional: Update UI to show location is set
|
||||
pinLocationBtn.textContent = 'Location Set!';
|
||||
pinLocationBtn.style.backgroundColor = '#28a745';
|
||||
confirmLocationBtn.addEventListener('click', function() {
|
||||
if (selectedCoords) {
|
||||
sessionStorage.setItem('userLocation', JSON.stringify(selectedCoords));
|
||||
modal.style.display = 'none';
|
||||
findRestaurantsBtn.click();
|
||||
} else {
|
||||
alert('Please select a location on the map.');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if(findRestaurantsBtn) {
|
||||
findRestaurantsBtn.addEventListener('click', function(e) {
|
||||
const userLocation = sessionStorage.getItem('userLocation');
|
||||
if (!userLocation) {
|
||||
e.preventDefault();
|
||||
alert('Please set your location first to find restaurants near you.');
|
||||
pinLocationBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
BIN
assets/pasted-20251016-191630-88be204a.png
Normal file
BIN
assets/pasted-20251016-191630-88be204a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
BIN
assets/pasted-20251016-192041-2abf91d9.jpg
Normal file
BIN
assets/pasted-20251016-192041-2abf91d9.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 779 KiB |
14
checkout.php
14
checkout.php
@ -3,6 +3,11 @@ session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/api_keys.php';
|
||||
|
||||
// Fetch emergency shutdown status
|
||||
$stmt_shutdown = db()->prepare("SELECT value FROM settings WHERE name = ?");
|
||||
$stmt_shutdown->execute(['emergency_shutdown']);
|
||||
$shutdown_active = ($stmt_shutdown->fetchColumn() === 'true');
|
||||
|
||||
$is_guest = !isset($_SESSION['user_id']);
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
$session_id = session_id();
|
||||
@ -55,6 +60,14 @@ include 'header.php';
|
||||
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo $paypalClientId; ?>¤cy=USD"></script>
|
||||
|
||||
<div class="checkout-container">
|
||||
<?php if ($shutdown_active): ?>
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<h4 class="alert-heading">Ordering Temporarily Disabled</h4>
|
||||
<p>Due to severe weather conditions, we have temporarily suspended all delivery services. The safety of our drivers and customers is our top priority.</p>
|
||||
<hr>
|
||||
<p class="mb-0">We apologize for any inconvenience and will resume operations as soon as it is safe to do so.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="checkout-main">
|
||||
<div class="checkout-header">
|
||||
<a href="index.php" class="checkout-logo">
|
||||
@ -117,6 +130,7 @@ include 'header.php';
|
||||
<button type="button" id="back-to-delivery-btn" class="btn-secondary">Back to Delivery</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="checkout-summary">
|
||||
<h4>Order Summary</h4>
|
||||
|
||||
16
footer.php
16
footer.php
@ -1,3 +1,4 @@
|
||||
<?php require_once 'includes/api_keys.php'; ?>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© <?php echo date("Y"); ?> Majuro Eats. All Rights Reserved. | <a href="/admin/login.php">Admin Login</a></p>
|
||||
@ -18,7 +19,22 @@ s1.setAttribute('crossorigin','*');
|
||||
s0.parentNode.insertBefore(s1,s0);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--End of Tawk.to Script-->
|
||||
|
||||
|
||||
|
||||
<!-- OneSignal Push Notifications -->
|
||||
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
|
||||
<script>
|
||||
window.OneSignal = window.OneSignal || [];
|
||||
OneSignal.push(function() {
|
||||
OneSignal.init({
|
||||
appId: "<?php echo ONESIGNAL_APP_ID; ?>",
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php if (extension_loaded('newrelic')) { echo newrelic_get_browser_timing_footer(); } ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
19
header.php
19
header.php
@ -11,12 +11,27 @@ session_start();
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="assets/css/main.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=<?php echo time(); ?>">
|
||||
<?php if (extension_loaded('newrelic')) { echo newrelic_get_browser_timing_header(); } ?>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<?php if (basename($_SERVER['PHP_SELF']) != 'index.php'): ?>
|
||||
<?php
|
||||
$pages_with_back_button = [
|
||||
'/login.php',
|
||||
'/signup.php',
|
||||
'/forgot_password.php',
|
||||
'/reset_password.php',
|
||||
'/restaurant_login.php',
|
||||
'/restaurant_signup.php',
|
||||
'/driver_signup.php',
|
||||
'/driver_login.php',
|
||||
'/admin/login.php',
|
||||
];
|
||||
if (in_array($_SERVER['PHP_SELF'], $pages_with_back_button)):
|
||||
?>
|
||||
<a href="/" class="back-to-home-btn">← Back to Home</a>
|
||||
<?php endif; ?>
|
||||
<nav class="main-nav">
|
||||
|
||||
302
help.php
302
help.php
@ -1,112 +1,158 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$stmt = $db->query("SELECT question, answer FROM faqs ORDER BY sort_order ASC");
|
||||
$faqs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
// Handle DB error gracefully
|
||||
$faqs = [];
|
||||
}
|
||||
|
||||
require_once 'includes/api_keys.php';
|
||||
|
||||
// GOOGLE_MAPS_API_KEY is needed for the map functionality.
|
||||
$google_maps_api_key = defined('GOOGLE_MAPS_API_KEY') ? GOOGLE_MAPS_API_KEY : '';
|
||||
|
||||
?>
|
||||
|
||||
<style>
|
||||
.faq-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
.hero-section {
|
||||
background: linear-gradient(to bottom, #40E0D0, #FFFFFF);
|
||||
padding: 60px 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
.faq-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.faq-question {
|
||||
.hero-section h1 {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 3rem;
|
||||
color: #343a40;
|
||||
}
|
||||
.faq-question::after {
|
||||
content: '+';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: transform 0.3s;
|
||||
.hero-section p {
|
||||
font-size: 1.25rem;
|
||||
color: #555;
|
||||
}
|
||||
.faq-answer {
|
||||
.tropical-header-image {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
object-fit: cover;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.contact-form-section, .faq-section {
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
|
||||
}
|
||||
#map {
|
||||
height: 250px;
|
||||
border-radius: 8px;
|
||||
background-color: #e9ecef;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.accordion-button:not(.collapsed) {
|
||||
color: #007bff;
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
.accordion-button:focus {
|
||||
box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
#confirmation-message {
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
padding-left: 20px;
|
||||
border-left: 2px solid #007bff;
|
||||
}
|
||||
.faq-item.active .faq-question::after {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container my-5">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-4">Help & Support</h1>
|
||||
<p class="lead text-muted">We're here to help. Find answers to common questions or get in touch with our team.</p>
|
||||
<div class="container-fluid hero-section">
|
||||
<div class="container">
|
||||
<h1>We’re here to help</h1>
|
||||
<p>Whether you have a question, concern, or just want to say Iakwe! 🌴</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success">
|
||||
<?php echo $_SESSION['success_message']; unset($_SESSION['success_message']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo $_SESSION['error_message']; unset($_SESSION['error_message']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="container my-5">
|
||||
<img src="https://images.pexels.com/photos/1671325/pexels-photo-1671325.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="Tropical Header" class="tropical-header-image shadow-lg">
|
||||
|
||||
<div id="confirmation-message" class="alert alert-success"></div>
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-lg-6">
|
||||
<h3 class="mb-4">Contact Us</h3>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<form action="help_process.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Phone Number (Optional)</label>
|
||||
<input type="tel" class="form-control" id="phone" name="phone">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">How can we help?</label>
|
||||
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Send Message</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="contact-form-section">
|
||||
<h3 class="mb-4">Send Us a Message</h3>
|
||||
<form id="contact-form">
|
||||
<div class="mb-3">
|
||||
<label for="full_name" class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" id="full_name" name="full_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Phone Number (Optional)</label>
|
||||
<input type="tel" class="form-control" id="phone" name="phone">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="subject" class="form-label">Subject</label>
|
||||
<select class="form-select" id="subject" name="subject" required>
|
||||
<option value="" disabled selected>Select a subject</option>
|
||||
<option value="Order Issue">Order Issue</option>
|
||||
<option value="Account Help">Account Help</option>
|
||||
<option value="Restaurant Inquiry">Restaurant Inquiry</option>
|
||||
<option value="Feedback">Feedback</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">How can we help?</label>
|
||||
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Optional: Pin your delivery location so we can assist you better.</label>
|
||||
<?php if ($google_maps_api_key): ?>
|
||||
<div id="map"></div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-warning">
|
||||
Map functionality is currently unavailable. Please provide a Google Maps API key.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="hidden" id="latitude" name="latitude">
|
||||
<input type="hidden" id="longitude" name="longitude">
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Send Message</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<h3 class="mb-4">Frequently Asked Questions</h3>
|
||||
<div class="faq-section">
|
||||
<div class="faq-item">
|
||||
<div class="faq-question">How do I track my order?</div>
|
||||
<div class="faq-answer">
|
||||
<p>You can track your order in real-time from the "Order Status" page. Once a driver is assigned, you'll see their location on the map.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<div class="faq-question">What payment methods do you accept?</div>
|
||||
<div class="faq-answer">
|
||||
<p>We accept all major credit cards, PayPal, and Stripe. You can save your payment method for faster checkout next time.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<div class="faq-question">How is the delivery fee calculated?</div>
|
||||
<div class="faq-answer">
|
||||
<p>The delivery fee is based on the distance between the restaurant and your location. You'll see the exact fee at checkout before you confirm your order.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="faq-item">
|
||||
<div class="faq-question">Can I change my delivery address?</div>
|
||||
<div class="faq-answer">
|
||||
<p>If the restaurant has not yet accepted your order, you may be able to cancel it and place a new one with the correct address. Once the order is being prepared, it cannot be changed.</p>
|
||||
</div>
|
||||
<h3 class="mb-4">Frequently Asked Questions</h3>
|
||||
<div class="accordion" id="faqAccordion">
|
||||
<?php if (!empty($faqs)): ?>
|
||||
<?php foreach ($faqs as $index => $faq): ?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading<?php echo $index; ?>">
|
||||
<button class="accordion-button <?php echo $index > 0 ? 'collapsed' : ''; ?>" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?php echo $index; ?>" aria-expanded="<?php echo $index === 0 ? 'true' : 'false'; ?>" aria-controls="collapse<?php echo $index; ?>">
|
||||
<?php echo htmlspecialchars($faq['question']); ?>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse<?php echo $index; ?>" class="accordion-collapse collapse <?php echo $index === 0 ? 'show' : ''; ?>" aria-labelledby="heading<?php echo $index; ?>" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">
|
||||
<?php echo nl2br(htmlspecialchars($faq['answer'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p>No FAQs found.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,21 +161,83 @@ require_once 'header.php';
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const faqItems = document.querySelectorAll('.faq-item');
|
||||
faqItems.forEach(item => {
|
||||
const question = item.querySelector('.faq-question');
|
||||
question.addEventListener('click', () => {
|
||||
const answer = item.querySelector('.faq-answer');
|
||||
if (item.classList.contains('active')) {
|
||||
item.classList.remove('active');
|
||||
answer.style.display = 'none';
|
||||
} else {
|
||||
item.classList.add('active');
|
||||
answer.style.display = 'block';
|
||||
const contactForm = document.getElementById('contact-form');
|
||||
const confirmationMessage = document.getElementById('confirmation-message');
|
||||
|
||||
contactForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(contactForm);
|
||||
const submitButton = contactForm.querySelector('button[type="submit"]');
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = 'Sending...';
|
||||
|
||||
fetch('/api/support.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
confirmationMessage.textContent = data.message;
|
||||
confirmationMessage.className = data.success ? 'alert alert-success' : 'alert alert-danger';
|
||||
confirmationMessage.style.display = 'block';
|
||||
|
||||
if (data.success) {
|
||||
contactForm.reset();
|
||||
if (window.marker) {
|
||||
window.marker.setMap(null); // Remove marker from map
|
||||
}
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
})
|
||||
.catch(error => {
|
||||
confirmationMessage.textContent = 'An error occurred. Please try again.';
|
||||
confirmationMessage.className = 'alert alert-danger';
|
||||
confirmationMessage.style.display = 'block';
|
||||
})
|
||||
.finally(() => {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = 'Send Message';
|
||||
});
|
||||
});
|
||||
|
||||
<?php if ($google_maps_api_key): ?>
|
||||
// Load Google Maps script
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://maps.googleapis.com/maps/api/js?key=<?php echo $google_maps_api_key; ?>&callback=initMap`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.head.appendChild(script);
|
||||
<?php endif; ?>
|
||||
});
|
||||
|
||||
function initMap() {
|
||||
const majuro = { lat: 7.13, lng: 171.38 }; // Default center for Majuro
|
||||
const map = new google.maps.Map(document.getElementById('map'), {
|
||||
zoom: 12,
|
||||
center: majuro,
|
||||
});
|
||||
|
||||
window.marker = null;
|
||||
|
||||
map.addListener('click', (e) => {
|
||||
placeMarker(e.latLng, map);
|
||||
});
|
||||
|
||||
function placeMarker(latLng, map) {
|
||||
if (window.marker) {
|
||||
window.marker.setPosition(latLng);
|
||||
} else {
|
||||
window.marker = new google.maps.Marker({
|
||||
position: latLng,
|
||||
map: map,
|
||||
});
|
||||
}
|
||||
document.getElementById('latitude').value = latLng.lat();
|
||||
document.getElementById('longitude').value = latLng.lng();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
<?php require_once 'footer.php'; ?>
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/mail/MailService.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
$message = trim($_POST['message'] ?? '');
|
||||
|
||||
if (empty($name) || empty($email) || empty($message)) {
|
||||
$_SESSION['error_message'] = 'Please fill in all required fields.';
|
||||
header('Location: help.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['error_message'] = 'Invalid email format.';
|
||||
header('Location: help.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$subject = 'New Help Request from ' . $name;
|
||||
$html_content = "<p><strong>Name:</strong> {$name}</p>\n <p><strong>Email:</strong> {$email}</p>\n <p><strong>Phone:</strong> {$phone}</p>\n <p><strong>Message:</strong></p>\n <p>{$message}</p>";
|
||||
$text_content = "Name: {$name}\nEmail: {$email}\nPhone: {$phone}\nMessage:\n{$message}";
|
||||
|
||||
$db = db();
|
||||
$stmt = $db->prepare("SELECT email FROM email_recipients WHERE form_type = ?");
|
||||
$stmt->execute(['help']);
|
||||
$recipients = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($recipients)) {
|
||||
$to = $recipients;
|
||||
} else {
|
||||
$to = getenv('MAIL_TO') ?: 'support@majuroeats.com';
|
||||
}
|
||||
|
||||
$result = MailService::sendMail($to, $subject, $html_content, $text_content, ['reply_to' => $email]);
|
||||
|
||||
if ($result['success']) {
|
||||
$_SESSION['success_message'] = 'Thank you for your message! We will get back to you shortly.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Sorry, there was an error sending your message. Please try again later.';
|
||||
// Optional: Log the error for debugging
|
||||
// error_log('MailService Error: ' . $result['error']);
|
||||
}
|
||||
|
||||
header('Location: help.php');
|
||||
exit;
|
||||
} else {
|
||||
header('Location: help.php');
|
||||
exit;
|
||||
}
|
||||
21
hero.php
21
hero.php
@ -3,12 +3,23 @@
|
||||
<div class="container hero-content-container">
|
||||
<div class="hero-content">
|
||||
<h1>Your favorite local food, delivered.</h1>
|
||||
<p>Enter your address to find restaurants near you.</p>
|
||||
<div class="hero-search-form">
|
||||
<input type="text" id="address-input" placeholder="Enter your delivery address">
|
||||
<button id="find-food-btn">Find Food</button>
|
||||
<p>Set your location to find restaurants near you.</p>
|
||||
<div class="location-actions">
|
||||
<button id="pin-location-btn" class="btn btn-primary">Pin a Location</button>
|
||||
<button id="use-location-btn" class="btn btn-secondary">Use My Location</button>
|
||||
</div>
|
||||
<a href="menu.php" class="find-restaurants-btn" style="display: none;"></a>
|
||||
<p class="delivery-note">MajuroEats delivers only within the main island zone (Rita–Laura).</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<div id="location-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-btn">×</span>
|
||||
<h2>Pin Your Location</h2>
|
||||
<p>Click on the map to set your delivery address.</p>
|
||||
<div id="map" style="height: 400px; width: 100%;"></div>
|
||||
<button id="confirm-location-btn" class="btn btn-primary w-100">Confirm Location</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,7 +1,12 @@
|
||||
<?php
|
||||
// Store your API keys here. Do not commit this file to version control if it contains sensitive information.
|
||||
// For demonstration purposes, it's okay to have keys here.
|
||||
// In a real-world application, these should be stored securely in environment variables.
|
||||
|
||||
define('STRIPE_PUBLIC_KEY', 'pk_test_51P4pA2Rpc52y31alA53xvt2gih3jAg2yJ23pQ32y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p');
|
||||
define('STRIPE_SECRET_KEY', 'sk_test_51P4pA2Rpc52y31alA53xvt2gih3jAg2yJ23pQ32y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p5y2y2p');
|
||||
define('GOOGLE_CLIENT_ID', 'YOUR_GOOGLE_CLIENT_ID');
|
||||
define('GOOGLE_CLIENT_SECRET', 'YOUR_GOOGLE_CLIENT_SECRET');
|
||||
define('PAYPAL_CLIENT_ID', 'YOUR_PAYPAL_CLIENT_ID');
|
||||
define('PAYPAL_CLIENT_SECRET', 'YOUR_PAYPAL_CLIENT_SECRET');
|
||||
define('OPENWEATHERMAP_API_KEY', 'd32bb14b5483bc76851d4237fa882624');
|
||||
|
||||
// Google OAuth
|
||||
define('GOOGLE_CLIENT_ID', '177199405877-ip94unn5ff0027q55c3242pr64nn0jmg.apps.googleusercontent.com');
|
||||
define('GOOGLE_CLIENT_SECRET', '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');
|
||||
?>
|
||||
|
||||
59
includes/weather_service.php
Normal file
59
includes/weather_service.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/api_keys.php';
|
||||
require_once __DIR__ . '/../mail/MailService.php';
|
||||
|
||||
function update_weather_data() {
|
||||
$api_key = OPENWEATHERMAP_API_KEY;
|
||||
$lat = 7.1164;
|
||||
$lon = 171.1850;
|
||||
$url = "https://api.openweathermap.org/data/3.0/onecall?lat={$lat}&lon={$lon}&exclude=minutely,hourly,daily&appid={$api_key}&units=metric";
|
||||
|
||||
$response = file_get_contents($url);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data) {
|
||||
$temperature = $data['current']['temp'];
|
||||
$humidity = $data['current']['humidity'];
|
||||
$wind_speed = $data['current']['wind_speed'];
|
||||
$precipitation = isset($data['current']['rain']['1h']) ? $data['current']['rain']['1h'] : 0;
|
||||
|
||||
$alert_status = false;
|
||||
$alert_message = 'No alerts';
|
||||
|
||||
$db = db();
|
||||
|
||||
if (!empty($data['alerts'])) {
|
||||
$alert_status = true;
|
||||
$alert_message = $data['alerts'][0]['event'] . ": " . $data['alerts'][0]['description'];
|
||||
|
||||
// Send email alert
|
||||
$to = 'admin@majuroeats.com';
|
||||
$subject = 'Weather Alert for Majuro';
|
||||
$htmlBody = "<h1>Weather Alert</h1><p>{$alert_message}</p>";
|
||||
MailService::sendMail($to, $subject, $htmlBody);
|
||||
|
||||
// Log the event
|
||||
$event_type = 'Weather Alert';
|
||||
$description = $alert_message;
|
||||
$severity = 'Warning'; // Or determine from alert data if possible
|
||||
$triggered_by = 'System (Weather Monitor)';
|
||||
$status = 'Ongoing';
|
||||
|
||||
$stmt_log = $db->prepare("INSERT INTO system_events (event_type, description, severity, triggered_by, status) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt_log->execute([$event_type, $description, $severity, $triggered_by, $status]);
|
||||
}
|
||||
|
||||
$stmt = $db->prepare(
|
||||
'INSERT INTO weather_status (temperature, humidity, wind_speed, precipitation, alert_status, alert_message)'
|
||||
. ' VALUES (?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
|
||||
try {
|
||||
$stmt->execute([$temperature, $humidity, $wind_speed, $precipitation, $alert_status ? 'true' : 'false', $alert_message]);
|
||||
echo "Weather data updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error updating weather data: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
menu.php
36
menu.php
@ -2,6 +2,11 @@
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Fetch emergency shutdown status
|
||||
$stmt_shutdown = db()->prepare("SELECT value FROM settings WHERE name = ?");
|
||||
$stmt_shutdown->execute(['emergency_shutdown']);
|
||||
$shutdown_active = ($stmt_shutdown->fetchColumn() === 'true');
|
||||
|
||||
$restaurant_id = isset($_GET['restaurant_id']) ? (int)$_GET['restaurant_id'] : 0;
|
||||
|
||||
if ($restaurant_id === 0) {
|
||||
@ -89,6 +94,15 @@ try {
|
||||
</div>
|
||||
|
||||
<div class="container mt-5">
|
||||
<?php if ($shutdown_active): ?>
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<h4 class="alert-heading">Ordering Temporarily Disabled</h4>
|
||||
<p>Due to severe weather conditions, we have temporarily suspended all delivery services. The safety of our drivers and customers is our top priority.</p>
|
||||
<hr>
|
||||
<p class="mb-0">We apologize for any inconvenience and will resume operations as soon as it is safe to do so.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h2 class="mb-4">Menu</h2>
|
||||
@ -101,15 +115,19 @@ try {
|
||||
<p class="description"><?php echo htmlspecialchars($item['description']); ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="price">$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></span>
|
||||
<form action="cart_actions.php" method="post" class="add-to-cart-form">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
|
||||
<input type="hidden" name="menu_item_id" value="<?php echo $item['id']; ?>">
|
||||
<div class="input-group">
|
||||
<input type="number" name="quantity" class="form-control quantity-input" value="1" min="1">
|
||||
<button type="submit" class="btn btn-primary add-to-cart-btn">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php if ($shutdown_active): ?>
|
||||
<button type="button" class="btn btn-danger disabled">Ordering Disabled</button>
|
||||
<?php else: ?>
|
||||
<form action="cart_actions.php" method="post" class="add-to-cart-form">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
|
||||
<input type="hidden" name="menu_item_id" value="<?php echo $item['id']; ?>">
|
||||
<div class="input-group">
|
||||
<input type="number" name="quantity" class="form-control quantity-input" value="1" min="1">
|
||||
<button type="submit" class="btn btn-primary add-to-cart-btn">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($item['image_url'])): ?>
|
||||
|
||||
15
migrations/20251016_create_faqs_table.sql
Normal file
15
migrations/20251016_create_faqs_table.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS faqs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
question VARCHAR(255) NOT NULL,
|
||||
answer TEXT NOT NULL,
|
||||
sort_order INT DEFAULT 0
|
||||
);
|
||||
|
||||
-- Insert sample FAQs
|
||||
INSERT INTO faqs (question, answer, sort_order) VALUES
|
||||
('How do I track my order?', 'You can track your order in real-time from the "Order Status" page. Once a driver is assigned, you will see their location on the map.', 1),
|
||||
('What payment methods do you accept?', 'We accept all major credit cards, PayPal, and loyalty points.', 2),
|
||||
('Can I order without creating an account?', 'Yes, you can place an order as a guest. However, creating an account allows you to save your delivery information and earn rewards points.', 3),
|
||||
('How is the delivery fee calculated?', 'The delivery fee is based on the distance between the restaurant and your delivery location. The exact amount is calculated at checkout.', 4),
|
||||
('Can I change my delivery pin after ordering?', 'Unfortunately, you cannot change your delivery pin after an order has been placed. Please double-check your location before confirming your order. If you need to make a change, please contact support immediately.', 5),
|
||||
('What should I do if my food arrives late?', 'We are sorry for the delay! Please contact our support team through the chat or contact form, and we will investigate the issue with the driver and restaurant.', 6);
|
||||
12
migrations/20251016_create_support_tickets_table.sql
Normal file
12
migrations/20251016_create_support_tickets_table.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE TABLE IF NOT EXISTS support_tickets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
full_name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(50) NULL,
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
latitude DECIMAL(10, 8) NULL,
|
||||
longitude DECIMAL(11, 8) NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'open',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
9
migrations/20251016_create_system_events_table.sql
Normal file
9
migrations/20251016_create_system_events_table.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS system_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
event_type VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
severity VARCHAR(50),
|
||||
triggered_by VARCHAR(255),
|
||||
status VARCHAR(50)
|
||||
);
|
||||
10
migrations/20251016_create_weather_status_table.sql
Normal file
10
migrations/20251016_create_weather_status_table.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS weather_status (
|
||||
id SERIAL PRIMARY KEY,
|
||||
"timestamp" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
temperature FLOAT,
|
||||
humidity FLOAT,
|
||||
wind_speed FLOAT,
|
||||
precipitation FLOAT,
|
||||
alert_status BOOLEAN DEFAULT FALSE,
|
||||
alert_message VARCHAR(255)
|
||||
);
|
||||
@ -32,7 +32,7 @@ if (!$order) {
|
||||
|
||||
// Fetch order items
|
||||
$itemsStmt = $pdo->prepare("
|
||||
SELECT oi.quantity, mi.name, mi.price
|
||||
SELECT oi.quantity, mi.name, mi.price, mi.restaurant_id
|
||||
FROM order_items oi
|
||||
JOIN menu_items mi ON oi.menu_item_id = mi.id
|
||||
WHERE oi.order_id = :order_id
|
||||
@ -112,6 +112,10 @@ include 'header.php';
|
||||
<p>We've received your order and will begin processing it shortly. You can track the progress of your order using the button below.</p>
|
||||
<a href="index.php" class="btn btn-secondary">Continue Shopping</a>
|
||||
<a href="<?php echo $tracking_url; ?>" class="btn btn-primary">Track Order</a>
|
||||
<?php if (!empty($orderItems)): ?>
|
||||
<?php $restaurant_id = $orderItems[0]['restaurant_id']; ?>
|
||||
<a href="leave_review.php?restaurant_id=<?php echo $restaurant_id; ?>" class="btn btn-success">Leave a Review</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -74,25 +74,48 @@ $possible_statuses = ['Pending', 'Confirmed', 'Preparing', 'Out for Delivery', '
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Order Notification Modal -->
|
||||
<div class="modal fade" id="newOrderModal" tabindex="-1" role="dialog" aria-labelledby="newOrderModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content border-success">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="newOrderModalLabel">You have a new order!</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
A new order has just arrived. Please check your order list.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" data-dismiss="modal">Got it</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const ordersTbody = document.getElementById('orders-tbody');
|
||||
const updateIndicator = document.getElementById('update-indicator');
|
||||
const noOrdersRow = document.querySelector('#orders-tbody td[colspan="6"]');
|
||||
let lastCheckTimestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
const notificationSound = new Audio('https://cdn.jsdelivr.net/npm/ion-sound@3.0.7/sounds/bell_ring.mp3');
|
||||
notificationSound.preload = 'auto';
|
||||
|
||||
const possibleStatuses = <?php echo json_encode($possible_statuses); ?>;
|
||||
|
||||
// --- Function to update status via API ---
|
||||
async function updateOrderStatus(orderId, newStatus) {
|
||||
try {
|
||||
const response = await fetch('update_order_status.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ order_id: orderId, status: newStatus }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Failed to update status');
|
||||
}
|
||||
if (!response.ok) throw new Error(result.error || 'Failed to update status');
|
||||
console.log('Status updated:', result.message);
|
||||
showUpdateIndicator();
|
||||
} catch (error) {
|
||||
@ -113,35 +136,74 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// --- Function to show a brief update indicator ---
|
||||
function showUpdateIndicator() {
|
||||
updateIndicator.style.display = 'inline';
|
||||
setTimeout(() => {
|
||||
updateIndicator.style.display = 'none';
|
||||
}, 2000);
|
||||
setTimeout(() => { updateIndicator.style.display = 'none'; }, 2000);
|
||||
}
|
||||
|
||||
// --- Function to fetch and refresh the order list ---
|
||||
async function fetchOrders() {
|
||||
// --- Create a new table row for a new order ---
|
||||
function createOrderRow(order) {
|
||||
const row = document.createElement('tr');
|
||||
row.dataset.orderId = order.id;
|
||||
|
||||
const statusOptions = possibleStatuses.map(status =>
|
||||
`<option value="${status}" ${order.status === status ? 'selected' : ''}>${status}</option>`
|
||||
).join('');
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${order.id}</td>
|
||||
<td>${order.user_name}</td>
|
||||
<td>${parseFloat(order.total_price).toFixed(2)}</td>
|
||||
<td>${order.created_at}</td>
|
||||
<td>
|
||||
<select class="form-control form-control-sm status-select">
|
||||
${statusOptions}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<a href="order_details.php?order_id=${order.id}" class="btn btn-info btn-sm">View Details</a>
|
||||
</td>
|
||||
`;
|
||||
return row;
|
||||
}
|
||||
|
||||
// --- Function to check for new orders ---
|
||||
async function checkForNewOrders() {
|
||||
try {
|
||||
const response = await fetch(window.location.href, { // Fetch the same page
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest' // To identify AJAX request on server if needed
|
||||
const response = await fetch(`../api/get_new_orders.php?since=${encodeURIComponent(lastCheckTimestamp)}`);
|
||||
if (!response.ok) {
|
||||
console.error('Failed to check for new orders. Status:', response.status);
|
||||
return;
|
||||
}
|
||||
const newOrders = await response.json();
|
||||
|
||||
if (newOrders.length > 0) {
|
||||
console.log(`Found ${newOrders.length} new order(s).`);
|
||||
|
||||
// Play sound and show modal
|
||||
notificationSound.play().catch(e => console.error("Audio play failed:", e));
|
||||
$('#newOrderModal').modal('show');
|
||||
|
||||
// Remove "No orders found" message if it exists
|
||||
if (noOrdersRow && noOrdersRow.parentElement.parentElement === ordersTbody) {
|
||||
ordersTbody.innerHTML = '';
|
||||
}
|
||||
});
|
||||
const html = await response.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const newTbody = doc.getElementById('orders-tbody');
|
||||
if (newTbody && ordersTbody.innerHTML.trim() !== newTbody.innerHTML.trim()) {
|
||||
ordersTbody.innerHTML = newTbody.innerHTML;
|
||||
console.log('Orders refreshed');
|
||||
showUpdateIndicator();
|
||||
|
||||
// Add new orders to the top of the table
|
||||
newOrders.forEach(order => {
|
||||
const newRow = createOrderRow(order);
|
||||
ordersTbody.prepend(newRow);
|
||||
});
|
||||
|
||||
// Update the timestamp to the latest order's creation time
|
||||
lastCheckTimestamp = newOrders[0].created_at;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching orders:', error);
|
||||
console.error('Error checking for new orders:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Polling: Refresh orders every 15 seconds ---
|
||||
setInterval(fetchOrders, 15000);
|
||||
// --- Initial timestamp and polling ---
|
||||
console.log('Starting order polling. Last check:', lastCheckTimestamp);
|
||||
setInterval(checkForNewOrders, 15000); // Check every 15 seconds
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -3,6 +3,11 @@ session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'header.php';
|
||||
|
||||
// Fetch emergency shutdown status
|
||||
$stmt_shutdown = db()->prepare("SELECT value FROM settings WHERE name = ?");
|
||||
$stmt_shutdown->execute(['emergency_shutdown']);
|
||||
$shutdown_active = ($stmt_shutdown->fetchColumn() === 'true');
|
||||
|
||||
// Fetch all cuisines for the filter dropdown
|
||||
$cuisines_stmt = db()->query("SELECT * FROM cuisines ORDER BY name");
|
||||
$cuisines = $cuisines_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@ -55,6 +60,15 @@ $restaurants = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
<?php if ($shutdown_active): ?>
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<h4 class="alert-heading">Ordering Temporarily Disabled</h4>
|
||||
<p>Due to severe weather conditions, we have temporarily suspended all delivery services. The safety of our drivers and customers is our top priority.</p>
|
||||
<hr>
|
||||
<p class="mb-0">We apologize for any inconvenience and will resume operations as soon as it is safe to do so.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h1 class="text-center mb-5">Our Restaurants</h1>
|
||||
|
||||
<!-- Filters and Sorting -->
|
||||
@ -98,15 +112,21 @@ $restaurants = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<p class="card-text text-muted"><?php echo htmlspecialchars($restaurant['cuisines']); ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-warning"><?php echo $restaurant['average_rating'] ? round($restaurant['average_rating'], 1) . ' ★' : 'No ratings'; ?></span>
|
||||
<a href="menu.php?restaurant_id=<?php echo $restaurant['id']; ?>" class="btn btn-outline-primary">View Menu</a>
|
||||
<?php if ($shutdown_active): ?>
|
||||
<a href="#" class="btn btn-outline-danger disabled">Ordering Disabled</a>
|
||||
<?php else: ?>
|
||||
<a href="menu.php?restaurant_id=<?php echo $restaurant['id']; ?>" class="btn btn-outline-primary">View Menu</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="col">
|
||||
<p class="alert alert-info">No restaurants found matching your criteria.</p>
|
||||
<div class='col-12 text-center empty-state'>
|
||||
<i class='fas fa-store-slash fa-4x mb-3 text-muted'></i>
|
||||
<h3 class='mt-4'>No Restaurants Found</h3>
|
||||
<p>Try adjusting your search or filters to find what you're looking for.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user