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>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -11,7 +11,9 @@ if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== tru
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Admin Dashboard</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<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">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="notification_emails.php">Notification Emails</a>
|
<a class="nav-link" href="notification_emails.php">Notification Emails</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="system_events.php">System Events</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ms-auto">
|
<ul class="navbar-nav ms-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
include 'header.php';
|
include 'header.php';
|
||||||
require_once '../db/config.php';
|
require_once '../db/config.php';
|
||||||
|
require_once '../includes/weather_service.php';
|
||||||
|
|
||||||
// Check if the user is logged in as an admin
|
// Check if the user is logged in as an admin
|
||||||
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
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();
|
$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
|
// Analytics Queries
|
||||||
// Total Revenue
|
// Total Revenue
|
||||||
$stmt_revenue = $pdo->prepare("SELECT SUM(total_price) as total_revenue FROM orders WHERE status = ?");
|
$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>
|
||||||
</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="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card">
|
<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 --- */
|
/* --- Global Styles --- */
|
||||||
body {
|
body {
|
||||||
font-family: var(--font-family);
|
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-size: cover;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@ -188,11 +188,11 @@ main {
|
|||||||
|
|
||||||
/* --- Hero Section --- */
|
/* --- Hero Section --- */
|
||||||
.hero-section {
|
.hero-section {
|
||||||
position: relative;
|
background-image: url('../assets/pasted-20251016-192041-2abf91d9.jpg');
|
||||||
background-image: url('../assets/pasted-20251016-083643-5429546d.jpg');
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
background-attachment: fixed;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
color: var(--coconut-white);
|
position: relative;
|
||||||
padding: 120px 0;
|
padding: 120px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -203,7 +203,6 @@ main {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-content-container {
|
.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 {
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -1,68 +1,87 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const hamburger = document.querySelector('.hamburger');
|
const mapElement = document.getElementById('map');
|
||||||
const navLinks = document.querySelector('.nav-links');
|
const modal = document.getElementById('location-modal');
|
||||||
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 pinLocationBtn = document.getElementById('pin-location-btn');
|
const pinLocationBtn = document.getElementById('pin-location-btn');
|
||||||
const closeModalBtn = document.querySelector('.close-button');
|
const useLocationBtn = document.getElementById('use-location-btn');
|
||||||
const confirmLocationBtn = document.getElementById('confirm-location');
|
const closeBtn = document.querySelector('.close-btn');
|
||||||
|
const confirmLocationBtn = document.getElementById('confirm-location-btn');
|
||||||
|
const findRestaurantsBtn = document.querySelector('.find-restaurants-btn');
|
||||||
|
|
||||||
let map, marker;
|
let map, marker;
|
||||||
let markerPosition = { lat: 6.9271, lng: 171.1845 }; // Default to Majuro
|
let selectedCoords = null;
|
||||||
|
|
||||||
function initMap() {
|
function initMap(lat, lng) {
|
||||||
if (map) {
|
if (map) map.remove();
|
||||||
map.remove();
|
map = L.map(mapElement).setView([lat, lng], 13);
|
||||||
}
|
|
||||||
map = L.map('map').setView(markerPosition, 13);
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
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);
|
}).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) {
|
marker.on('dragend', function(e) {
|
||||||
const position = marker.getLatLng();
|
selectedCoords = marker.getLatLng();
|
||||||
markerPosition = { lat: position.lat, lng: position.lng };
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinLocationBtn) {
|
if (pinLocationBtn) {
|
||||||
pinLocationBtn.addEventListener('click', () => {
|
pinLocationBtn.addEventListener('click', function() {
|
||||||
locationModal.style.display = 'block';
|
modal.style.display = 'block';
|
||||||
// Invalidate map size on modal open to ensure it renders correctly
|
// Default to a central Majuro location
|
||||||
setTimeout(initMap, 10);
|
initMap(7.09, 171.38);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closeModalBtn) {
|
if (useLocationBtn) {
|
||||||
closeModalBtn.addEventListener('click', () => {
|
useLocationBtn.addEventListener('click', function() {
|
||||||
locationModal.style.display = 'none';
|
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 (closeBtn) {
|
||||||
if (event.target == locationModal) {
|
closeBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||||
locationModal.style.display = 'none';
|
}
|
||||||
|
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target == modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (confirmLocationBtn) {
|
if (confirmLocationBtn) {
|
||||||
confirmLocationBtn.addEventListener('click', () => {
|
confirmLocationBtn.addEventListener('click', function() {
|
||||||
sessionStorage.setItem('user_latitude', markerPosition.lat);
|
if (selectedCoords) {
|
||||||
sessionStorage.setItem('user_longitude', markerPosition.lng);
|
sessionStorage.setItem('userLocation', JSON.stringify(selectedCoords));
|
||||||
locationModal.style.display = 'none';
|
modal.style.display = 'none';
|
||||||
// Optional: Update UI to show location is set
|
findRestaurantsBtn.click();
|
||||||
pinLocationBtn.textContent = 'Location Set!';
|
} else {
|
||||||
pinLocationBtn.style.backgroundColor = '#28a745';
|
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 'db/config.php';
|
||||||
require_once 'includes/api_keys.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']);
|
$is_guest = !isset($_SESSION['user_id']);
|
||||||
$user_id = $_SESSION['user_id'] ?? null;
|
$user_id = $_SESSION['user_id'] ?? null;
|
||||||
$session_id = session_id();
|
$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>
|
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo $paypalClientId; ?>¤cy=USD"></script>
|
||||||
|
|
||||||
<div class="checkout-container">
|
<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-main">
|
||||||
<div class="checkout-header">
|
<div class="checkout-header">
|
||||||
<a href="index.php" class="checkout-logo">
|
<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>
|
<button type="button" id="back-to-delivery-btn" class="btn-secondary">Back to Delivery</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="checkout-summary">
|
<div class="checkout-summary">
|
||||||
<h4>Order Summary</h4>
|
<h4>Order Summary</h4>
|
||||||
|
|||||||
16
footer.php
16
footer.php
@ -1,3 +1,4 @@
|
|||||||
|
<?php require_once 'includes/api_keys.php'; ?>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p>© <?php echo date("Y"); ?> Majuro Eats. All Rights Reserved. | <a href="/admin/login.php">Admin Login</a></p>
|
<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);
|
s0.parentNode.insertBefore(s1,s0);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!--End of Tawk.to 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
19
header.php
19
header.php
@ -11,12 +11,27 @@ session_start();
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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 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="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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<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>
|
<a href="/" class="back-to-home-btn">← Back to Home</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<nav class="main-nav">
|
<nav class="main-nav">
|
||||||
|
|||||||
258
help.php
258
help.php
@ -1,65 +1,91 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
require_once 'header.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>
|
<style>
|
||||||
.faq-section {
|
.hero-section {
|
||||||
background-color: #f8f9fa;
|
background: linear-gradient(to bottom, #40E0D0, #FFFFFF);
|
||||||
padding: 40px;
|
padding: 60px 0;
|
||||||
border-radius: 8px;
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
.faq-item {
|
.hero-section h1 {
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.faq-question {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
font-size: 3rem;
|
||||||
position: relative;
|
color: #343a40;
|
||||||
}
|
}
|
||||||
.faq-question::after {
|
.hero-section p {
|
||||||
content: '+';
|
font-size: 1.25rem;
|
||||||
position: absolute;
|
color: #555;
|
||||||
right: 0;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
}
|
||||||
.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;
|
display: none;
|
||||||
margin-top: 10px;
|
|
||||||
padding-left: 20px;
|
|
||||||
border-left: 2px solid #007bff;
|
|
||||||
}
|
|
||||||
.faq-item.active .faq-question::after {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container my-5">
|
<div class="container-fluid hero-section">
|
||||||
<div class="text-center mb-5">
|
<div class="container">
|
||||||
<h1 class="display-4">Help & Support</h1>
|
<h1>We’re here to help</h1>
|
||||||
<p class="lead text-muted">We're here to help. Find answers to common questions or get in touch with our team.</p>
|
<p>Whether you have a question, concern, or just want to say Iakwe! 🌴</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success_message'])): ?>
|
<div class="container my-5">
|
||||||
<div class="alert alert-success">
|
<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">
|
||||||
<?php echo $_SESSION['success_message']; unset($_SESSION['success_message']); ?>
|
|
||||||
</div>
|
<div id="confirmation-message" class="alert alert-success"></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="row g-5">
|
<div class="row g-5">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<h3 class="mb-4">Contact Us</h3>
|
<div class="contact-form-section">
|
||||||
<div class="card shadow-sm">
|
<h3 class="mb-4">Send Us a Message</h3>
|
||||||
<div class="card-body p-4">
|
<form id="contact-form">
|
||||||
<form action="help_process.php" method="POST">
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Full Name</label>
|
<label for="full_name" class="form-label">Full Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" required>
|
<input type="text" class="form-control" id="full_name" name="full_name" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="email" class="form-label">Email Address</label>
|
<label for="email" class="form-label">Email Address</label>
|
||||||
@ -69,44 +95,64 @@ require_once 'header.php';
|
|||||||
<label for="phone" class="form-label">Phone Number (Optional)</label>
|
<label for="phone" class="form-label">Phone Number (Optional)</label>
|
||||||
<input type="tel" class="form-control" id="phone" name="phone">
|
<input type="tel" class="form-control" id="phone" name="phone">
|
||||||
</div>
|
</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">
|
<div class="mb-3">
|
||||||
<label for="message" class="form-label">How can we help?</label>
|
<label for="message" class="form-label">How can we help?</label>
|
||||||
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
|
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
|
||||||
</div>
|
</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">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary btn-lg">Send Message</button>
|
<button type="submit" class="btn btn-primary btn-lg">Send Message</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<h3 class="mb-4">Frequently Asked Questions</h3>
|
|
||||||
<div class="faq-section">
|
<div class="faq-section">
|
||||||
<div class="faq-item">
|
<h3 class="mb-4">Frequently Asked Questions</h3>
|
||||||
<div class="faq-question">How do I track my order?</div>
|
<div class="accordion" id="faqAccordion">
|
||||||
<div class="faq-answer">
|
<?php if (!empty($faqs)): ?>
|
||||||
<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>
|
<?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>
|
</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>
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p>No FAQs found.</p>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,21 +161,83 @@ require_once 'header.php';
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const faqItems = document.querySelectorAll('.faq-item');
|
const contactForm = document.getElementById('contact-form');
|
||||||
faqItems.forEach(item => {
|
const confirmationMessage = document.getElementById('confirmation-message');
|
||||||
const question = item.querySelector('.faq-question');
|
|
||||||
question.addEventListener('click', () => {
|
contactForm.addEventListener('submit', function (e) {
|
||||||
const answer = item.querySelector('.faq-answer');
|
e.preventDefault();
|
||||||
if (item.classList.contains('active')) {
|
|
||||||
item.classList.remove('active');
|
const formData = new FormData(contactForm);
|
||||||
answer.style.display = 'none';
|
const submitButton = contactForm.querySelector('button[type="submit"]');
|
||||||
} else {
|
submitButton.disabled = true;
|
||||||
item.classList.add('active');
|
submitButton.textContent = 'Sending...';
|
||||||
answer.style.display = 'block';
|
|
||||||
|
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>
|
</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;
|
|
||||||
}
|
|
||||||
19
hero.php
19
hero.php
@ -3,12 +3,23 @@
|
|||||||
<div class="container hero-content-container">
|
<div class="container hero-content-container">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<h1>Your favorite local food, delivered.</h1>
|
<h1>Your favorite local food, delivered.</h1>
|
||||||
<p>Enter your address to find restaurants near you.</p>
|
<p>Set your location to find restaurants near you.</p>
|
||||||
<div class="hero-search-form">
|
<div class="location-actions">
|
||||||
<input type="text" id="address-input" placeholder="Enter your delivery address">
|
<button id="pin-location-btn" class="btn btn-primary">Pin a Location</button>
|
||||||
<button id="find-food-btn">Find Food</button>
|
<button id="use-location-btn" class="btn btn-secondary">Use My Location</button>
|
||||||
</div>
|
</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>
|
<p class="delivery-note">MajuroEats delivers only within the main island zone (Rita–Laura).</p>
|
||||||
</div>
|
</div>
|
||||||
</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
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
menu.php
18
menu.php
@ -2,6 +2,11 @@
|
|||||||
session_start();
|
session_start();
|
||||||
require_once 'db/config.php';
|
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;
|
$restaurant_id = isset($_GET['restaurant_id']) ? (int)$_GET['restaurant_id'] : 0;
|
||||||
|
|
||||||
if ($restaurant_id === 0) {
|
if ($restaurant_id === 0) {
|
||||||
@ -89,6 +94,15 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container mt-5">
|
<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="row">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<h2 class="mb-4">Menu</h2>
|
<h2 class="mb-4">Menu</h2>
|
||||||
@ -101,6 +115,9 @@ try {
|
|||||||
<p class="description"><?php echo htmlspecialchars($item['description']); ?></p>
|
<p class="description"><?php echo htmlspecialchars($item['description']); ?></p>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<span class="price">$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></span>
|
<span class="price">$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></span>
|
||||||
|
<?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">
|
<form action="cart_actions.php" method="post" class="add-to-cart-form">
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
|
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
|
||||||
@ -110,6 +127,7 @@ try {
|
|||||||
<button type="submit" class="btn btn-primary add-to-cart-btn">Add</button>
|
<button type="submit" class="btn btn-primary add-to-cart-btn">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if (!empty($item['image_url'])): ?>
|
<?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
|
// Fetch order items
|
||||||
$itemsStmt = $pdo->prepare("
|
$itemsStmt = $pdo->prepare("
|
||||||
SELECT oi.quantity, mi.name, mi.price
|
SELECT oi.quantity, mi.name, mi.price, mi.restaurant_id
|
||||||
FROM order_items oi
|
FROM order_items oi
|
||||||
JOIN menu_items mi ON oi.menu_item_id = mi.id
|
JOIN menu_items mi ON oi.menu_item_id = mi.id
|
||||||
WHERE oi.order_id = :order_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>
|
<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="index.php" class="btn btn-secondary">Continue Shopping</a>
|
||||||
<a href="<?php echo $tracking_url; ?>" class="btn btn-primary">Track Order</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -74,25 +74,48 @@ $possible_statuses = ['Pending', 'Confirmed', 'Preparing', 'Out for Delivery', '
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const ordersTbody = document.getElementById('orders-tbody');
|
const ordersTbody = document.getElementById('orders-tbody');
|
||||||
const updateIndicator = document.getElementById('update-indicator');
|
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 ---
|
// --- Function to update status via API ---
|
||||||
async function updateOrderStatus(orderId, newStatus) {
|
async function updateOrderStatus(orderId, newStatus) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('update_order_status.php', {
|
const response = await fetch('update_order_status.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ order_id: orderId, status: newStatus }),
|
body: JSON.stringify({ order_id: orderId, status: newStatus }),
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(result.error || 'Failed to update status');
|
||||||
throw new Error(result.error || 'Failed to update status');
|
|
||||||
}
|
|
||||||
console.log('Status updated:', result.message);
|
console.log('Status updated:', result.message);
|
||||||
showUpdateIndicator();
|
showUpdateIndicator();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -113,35 +136,74 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
// --- Function to show a brief update indicator ---
|
// --- Function to show a brief update indicator ---
|
||||||
function showUpdateIndicator() {
|
function showUpdateIndicator() {
|
||||||
updateIndicator.style.display = 'inline';
|
updateIndicator.style.display = 'inline';
|
||||||
setTimeout(() => {
|
setTimeout(() => { updateIndicator.style.display = 'none'; }, 2000);
|
||||||
updateIndicator.style.display = 'none';
|
|
||||||
}, 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Function to fetch and refresh the order list ---
|
// --- Create a new table row for a new order ---
|
||||||
async function fetchOrders() {
|
function createOrderRow(order) {
|
||||||
try {
|
const row = document.createElement('tr');
|
||||||
const response = await fetch(window.location.href, { // Fetch the same page
|
row.dataset.orderId = order.id;
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest' // To identify AJAX request on server if needed
|
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(`../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 = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new orders to the top of the table
|
||||||
|
newOrders.forEach(order => {
|
||||||
|
const newRow = createOrderRow(order);
|
||||||
|
ordersTbody.prepend(newRow);
|
||||||
});
|
});
|
||||||
const html = await response.text();
|
|
||||||
const parser = new DOMParser();
|
// Update the timestamp to the latest order's creation time
|
||||||
const doc = parser.parseFromString(html, 'text/html');
|
lastCheckTimestamp = newOrders[0].created_at;
|
||||||
const newTbody = doc.getElementById('orders-tbody');
|
|
||||||
if (newTbody && ordersTbody.innerHTML.trim() !== newTbody.innerHTML.trim()) {
|
|
||||||
ordersTbody.innerHTML = newTbody.innerHTML;
|
|
||||||
console.log('Orders refreshed');
|
|
||||||
showUpdateIndicator();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching orders:', error);
|
console.error('Error checking for new orders:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Polling: Refresh orders every 15 seconds ---
|
// --- Initial timestamp and polling ---
|
||||||
setInterval(fetchOrders, 15000);
|
console.log('Starting order polling. Last check:', lastCheckTimestamp);
|
||||||
|
setInterval(checkForNewOrders, 15000); // Check every 15 seconds
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,11 @@ session_start();
|
|||||||
require_once 'db/config.php';
|
require_once 'db/config.php';
|
||||||
require_once 'header.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
|
// Fetch all cuisines for the filter dropdown
|
||||||
$cuisines_stmt = db()->query("SELECT * FROM cuisines ORDER BY name");
|
$cuisines_stmt = db()->query("SELECT * FROM cuisines ORDER BY name");
|
||||||
$cuisines = $cuisines_stmt->fetchAll(PDO::FETCH_ASSOC);
|
$cuisines = $cuisines_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@ -55,6 +60,15 @@ $restaurants = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="container mt-5">
|
<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>
|
<h1 class="text-center mb-5">Our Restaurants</h1>
|
||||||
|
|
||||||
<!-- Filters and Sorting -->
|
<!-- 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>
|
<p class="card-text text-muted"><?php echo htmlspecialchars($restaurant['cuisines']); ?></p>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<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>
|
<span class="text-warning"><?php echo $restaurant['average_rating'] ? round($restaurant['average_rating'], 1) . ' ★' : 'No ratings'; ?></span>
|
||||||
|
<?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>
|
<a href="menu.php?restaurant_id=<?php echo $restaurant['id']; ?>" class="btn btn-outline-primary">View Menu</a>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="col">
|
<div class='col-12 text-center empty-state'>
|
||||||
<p class="alert alert-info">No restaurants found matching your criteria.</p>
|
<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>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user