Compare commits

...

26 Commits

Author SHA1 Message Date
Flatlogic Bot
2d8abe32bb V27 2025-10-17 06:23:25 +00:00
Flatlogic Bot
acff14d6dc V24 2025-10-16 20:45:21 +00:00
Flatlogic Bot
83696c725a V23 2025-10-16 20:04:54 +00:00
Flatlogic Bot
8795a633f6 V22 2025-10-16 20:00:52 +00:00
Flatlogic Bot
2be0d2d4c4 V20 2025-10-16 14:27:25 +00:00
Flatlogic Bot
42cfa7481c V19 2025-10-16 08:36:38 +00:00
Flatlogic Bot
44a5c2df2d V18 2025-10-16 08:12:27 +00:00
Flatlogic Bot
64ad8fe24a V17 2025-10-16 07:51:21 +00:00
Flatlogic Bot
be8857804b V16 2025-10-16 07:18:33 +00:00
Flatlogic Bot
4c995f3f6b V15 2025-10-16 07:08:27 +00:00
Flatlogic Bot
adf8c9c972 V14 2025-10-16 06:28:46 +00:00
Flatlogic Bot
5fc6fe4ad4 V13 2025-10-15 15:33:06 +00:00
Flatlogic Bot
7a0a2165fc V12 2025-10-15 14:58:19 +00:00
Flatlogic Bot
e98192b894 V11 2025-10-15 04:36:26 +00:00
Flatlogic Bot
c3d7232b7a V10 2025-10-15 04:10:46 +00:00
Flatlogic Bot
727b6fcf29 V10 2025-10-15 04:02:50 +00:00
Flatlogic Bot
ec17506c33 V9 2025-10-15 02:41:21 +00:00
Flatlogic Bot
ef71b241ae V8 2025-10-15 02:30:11 +00:00
Flatlogic Bot
2c8c9bfe04 V7 2025-10-15 01:01:54 +00:00
Flatlogic Bot
369f552c9f V6 2025-10-15 00:49:03 +00:00
Flatlogic Bot
a6434721b7 V5 2025-10-15 00:36:03 +00:00
Flatlogic Bot
eb2cf1a3fb V5 2025-10-15 00:18:12 +00:00
Flatlogic Bot
d22c607918 V4 2025-10-15 00:02:03 +00:00
Flatlogic Bot
793d212dbb V3 2025-10-14 23:43:54 +00:00
Flatlogic Bot
ab1ae8b39b V2 2025-10-14 23:38:25 +00:00
Flatlogic Bot
bd0cd9042d V1 2025-10-14 22:17:27 +00:00
33646 changed files with 3556950 additions and 154 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules/ node_modules/
*/node_modules/ */node_modules/
*/build/ */build/
includes/api_keys.php

28
about.php Normal file
View File

@ -0,0 +1,28 @@
<?php
require_once 'header.php';
require_once __DIR__ . '/db/config.php';
$db = db();
$stmt = $db->prepare("SELECT * FROM pages WHERE page_key = :page_key");
$stmt->bindValue(':page_key', 'about_us');
$stmt->execute();
$page = $stmt->fetch();
?>
<div class="container mt-5">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<?php if ($page): ?>
<?php echo $page['content']; ?>
<?php else: ?>
<h1 class="text-center mb-4">About Us</h1>
<p class="lead">Content not found. Please set up the 'About Us' page in the admin dashboard.</p>
<?php endif; ?>
</div>
</div>
</div>
<?php
require_once 'footer.php';
?>

61
admin/add_menu_item.php Normal file
View File

@ -0,0 +1,61 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$restaurant_id = $_GET['restaurant_id'] ?? null;
if (!$restaurant_id) {
header('Location: index.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$price = $_POST['price'] ?? '';
$restaurant_id = $_POST['restaurant_id'] ?? null;
if ($name && $price && $restaurant_id) {
$pdo = db();
$stmt = $pdo->prepare("INSERT INTO menu_items (restaurant_id, name, description, price) VALUES (?, ?, ?, ?)");
$stmt->execute([$restaurant_id, $name, $description, $price]);
header('Location: menu.php?restaurant_id=' . $restaurant_id);
exit;
} else {
$error = "Name and price are required.";
}
}
?>
<div class="container mt-4">
<h2>Add New Menu Item</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="add_menu_item.php" method="POST">
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"></textarea>
</div>
<div class="mb-3">
<label for="price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" required>
</div>
<button type="submit" class="btn btn-primary">Add Item</button>
<a href="menu.php?restaurant_id=<?php echo $restaurant_id; ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include 'footer.php'; ?>

91
admin/add_restaurant.php Normal file
View File

@ -0,0 +1,91 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$pdo = db();
// Fetch all cuisines
$cuisines_stmt = $pdo->query("SELECT * FROM cuisines ORDER BY name ASC");
$cuisines = $cuisines_stmt->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$image_url = $_POST['image_url'] ?? '';
$selected_cuisines = $_POST['cuisines'] ?? [];
if ($name && !empty($selected_cuisines)) {
try {
$pdo->beginTransaction();
// Insert into restaurants table
$stmt = $pdo->prepare("INSERT INTO restaurants (name, description, image_url) VALUES (?, ?, ?)");
$stmt->execute([$name, $description, $image_url]);
$restaurant_id = $pdo->lastInsertId();
// Insert into restaurant_cuisines table
$cuisine_stmt = $pdo->prepare("INSERT INTO restaurant_cuisines (restaurant_id, cuisine_id) VALUES (?, ?)");
foreach ($selected_cuisines as $cuisine_id) {
$cuisine_stmt->execute([$restaurant_id, $cuisine_id]);
}
$pdo->commit();
header('Location: index.php');
exit;
} catch (Exception $e) {
$pdo->rollBack();
$error = "Error adding restaurant: " . $e->getMessage();
}
} else {
$error = "Name and at least one cuisine are required.";
}
}
?>
<div class="container mt-4">
<h2>Add New Restaurant</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="add_restaurant.php" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Cuisines</label>
<div class="row">
<?php foreach ($cuisines as $cuisine): ?>
<div class="col-md-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="cuisines[]" value="<?php echo $cuisine['id']; ?>" id="cuisine_<?php echo $cuisine['id']; ?>">
<label class="form-check-label" for="cuisine_<?php echo $cuisine['id']; ?>">
<?php echo htmlspecialchars($cuisine['name']); ?>
</label>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url">
</div>
<button type="submit" class="btn btn-primary">Add Restaurant</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include 'footer.php'; ?>

41
admin/assign_driver.php Normal file
View File

@ -0,0 +1,41 @@
<?php
require_once '../db/config.php';
session_start();
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['order_id']) && isset($_POST['driver_id'])) {
$order_id = $_POST['order_id'];
$driver_id = $_POST['driver_id'];
if (!empty($order_id) && !empty($driver_id)) {
try {
$pdo = db();
// Check if an assignment already exists
$check_stmt = $pdo->prepare("SELECT id FROM driver_assignments WHERE order_id = ?");
$check_stmt->execute([$order_id]);
if (!$check_stmt->fetch()) {
// Create new assignment
$insert_stmt = $pdo->prepare("INSERT INTO driver_assignments (order_id, driver_id) VALUES (?, ?)");
$insert_stmt->execute([$order_id, $driver_id]);
// Update order status
$update_stmt = $pdo->prepare("UPDATE orders SET status = 'Confirmed' WHERE id = ?");
$update_stmt->execute([$order_id]);
}
} catch (PDOException $e) {
// Log error or handle it appropriately
die("Database error: " . $e->getMessage());
}
}
}
header('Location: orders.php');
exit;
?>

134
admin/coupons.php Normal file
View File

@ -0,0 +1,134 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Ensure user is admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$pdo = db();
$feedback = [];
// Handle POST requests for creating or toggling coupons
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Create a new coupon
if (isset($_POST['create_coupon'])) {
$code = trim($_POST['code']);
$discount = trim($_POST['discount_percentage']);
if (!empty($code) && is_numeric($discount) && $discount > 0 && $discount <= 100) {
try {
$stmt = $pdo->prepare("INSERT INTO coupons (code, discount_percentage) VALUES (:code, :discount)");
$stmt->execute(['code' => $code, 'discount' => $discount]);
$feedback = ['type' => 'success', 'message' => 'Coupon created successfully.'];
} catch (PDOException $e) {
if ($e->getCode() == '23505') { // Unique violation
$feedback = ['type' => 'danger', 'message' => 'Error: Coupon code already exists.'];
} else {
$feedback = ['type' => 'danger', 'message' => 'Error creating coupon.'];
}
}
} else {
$feedback = ['type' => 'danger', 'message' => 'Invalid input. Please provide a valid code and a discount percentage between 0 and 100.'];
}
}
// Toggle coupon status
if (isset($_POST['toggle_status'])) {
$coupon_id = $_POST['coupon_id'];
$current_status = $_POST['current_status'];
$new_status = $current_status ? 'false' : 'true';
$stmt = $pdo->prepare("UPDATE coupons SET is_active = :status WHERE id = :id");
$stmt->execute(['status' => $new_status, 'id' => $coupon_id]);
$feedback = ['type' => 'success', 'message' => 'Coupon status updated.'];
}
}
// Fetch all coupons
$coupons = $pdo->query("SELECT * FROM coupons ORDER BY created_at DESC")->fetchAll();
?>
<div class="container mt-4">
<h2>Coupon Management</h2>
<?php if (!empty($feedback)): ?>
<div class="alert alert-<?php echo $feedback['type']; ?>" role="alert">
<?php echo htmlspecialchars($feedback['message']); ?>
</div>
<?php endif; ?>
<!-- Create Coupon Form -->
<div class="card mb-4">
<div class="card-header">Create New Coupon</div>
<div class="card-body">
<form action="coupons.php" method="POST">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="code">Coupon Code</label>
<input type="text" name="code" id="code" class="form-control" required>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="discount_percentage">Discount (%)</label>
<input type="number" name="discount_percentage" id="discount_percentage" class="form-control" step="0.01" min="0.01" max="100" required>
</div>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" name="create_coupon" class="btn btn-primary w-100">Create</button>
</div>
</div>
</form>
</div>
</div>
<!-- Coupons Table -->
<div class="card">
<div class="card-header">Existing Coupons</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>Code</th>
<th>Discount</th>
<th>Status</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($coupons as $coupon): ?>
<tr>
<td><?php echo htmlspecialchars($coupon['code']); ?></td>
<td><?php echo number_format($coupon['discount_percentage'], 2); ?>%</td>
<td>
<?php if ($coupon['is_active']): ?>
<span class="badge bg-success">Active</span>
<?php else: ?>
<span class="badge bg-secondary">Inactive</span>
<?php endif; ?>
</td>
<td><?php echo $coupon['created_at']; ?></td>
<td>
<form action="coupons.php" method="POST">
<input type="hidden" name="coupon_id" value="<?php echo $coupon['id']; ?>">
<input type="hidden" name="current_status" value="<?php echo $coupon['is_active']; ?>">
<button type="submit" name="toggle_status" class="btn btn-sm <?php echo $coupon['is_active'] ? 'btn-warning' : 'btn-success'; ?>">
<?php echo $coupon['is_active'] ? 'Deactivate' : 'Activate'; ?>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include 'footer.php'; ?>

79
admin/cuisines.php Normal file
View File

@ -0,0 +1,79 @@
<?php
require_once '../db/config.php';
require_once 'header.php';
$db = db();
// Handle form submissions for adding, editing, and deleting cuisines
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_cuisine'])) {
$name = trim($_POST['name']);
if (!empty($name)) {
$stmt = $db->prepare("INSERT INTO cuisines (name) VALUES (:name) ON CONFLICT (name) DO NOTHING");
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->execute();
}
} elseif (isset($_POST['delete_cuisine'])) {
$id = $_POST['id'];
$stmt = $db->prepare("DELETE FROM cuisines WHERE id = :id");
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
}
}
// Fetch all cuisines
$cuisines = $db->query("SELECT * FROM cuisines ORDER BY name ASC")->fetchAll();
?>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">Manage Cuisines</h1>
<!-- Add Cuisine Form -->
<div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<h2 class="text-2xl mb-4">Add New Cuisine</h2>
<form action="cuisines.php" method="POST">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="name">
Cuisine Name
</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="name" name="name" type="text" placeholder="e.g., Italian, Mexican" required>
</div>
<div class="flex items-center justify-between">
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit" name="add_cuisine">
Add Cuisine
</button>
</div>
</form>
</div>
<!-- Cuisines Table -->
<div class="bg-white shadow-md rounded px-8 pt-6 pb-8">
<h2 class="text-2xl mb-4">Existing Cuisines</h2>
<table class="min-w-full table-auto">
<thead class="bg-gray-200">
<tr>
<th class="px-4 py-2">ID</th>
<th class="px-4 py-2">Name</th>
<th class="px-4 py-2">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($cuisines as $cuisine): ?>
<tr class="border-b">
<td class="px-4 py-2 text-center"><?php echo htmlspecialchars($cuisine['id']); ?></td>
<td class="px-4 py-2"><?php echo htmlspecialchars($cuisine['name']); ?></td>
<td class="px-4 py-2 text-center">
<form action="cuisines.php" method="POST" onsubmit="return confirm('Are you sure you want to delete this cuisine?');">
<input type="hidden" name="id" value="<?php echo $cuisine['id']; ?>">
<button type="submit" name="delete_cuisine" class="text-red-500 hover:text-red-700">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php require_once 'footer.php'; ?>

View File

@ -0,0 +1,26 @@
<?php
session_start();
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$id = $_GET['id'] ?? null;
$restaurant_id = $_GET['restaurant_id'] ?? null;
if ($id) {
$pdo = db();
$stmt = $pdo->prepare("DELETE FROM menu_items WHERE id = ?");
$stmt->execute([$id]);
}
if ($restaurant_id) {
header('Location: menu.php?restaurant_id=' . $restaurant_id);
} else {
header('Location: index.php');
}
exit;
?>

View File

@ -0,0 +1,21 @@
<?php
session_start();
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$id = $_GET['id'] ?? null;
if ($id) {
$pdo = db();
$stmt = $pdo->prepare("DELETE FROM restaurants WHERE id = ?");
$stmt->execute([$id]);
}
header('Location: index.php');
exit;
?>

63
admin/drivers.php Normal file
View File

@ -0,0 +1,63 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$pdo = db();
// Fetch all drivers with user information
$stmt = $pdo->query("
SELECT d.id, d.full_name, d.phone_number, d.vehicle_details, d.approval_status, u.email
FROM drivers d
JOIN users u ON d.user_id = u.id
ORDER BY d.created_at DESC
");
$drivers = $stmt->fetchAll();
$possible_statuses = ['pending', 'approved', 'rejected'];
?>
<div class="container mt-4">
<h2>Driver Management</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Driver ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Vehicle</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($drivers as $driver): ?>
<tr>
<td><?php echo $driver['id']; ?></td>
<td><?php echo htmlspecialchars($driver['full_name']); ?></td>
<td><?php echo htmlspecialchars($driver['email']); ?></td>
<td><?php echo htmlspecialchars($driver['phone_number']); ?></td>
<td><?php echo htmlspecialchars($driver['vehicle_details']); ?></td>
<td><?php echo htmlspecialchars($driver['approval_status']); ?></td>
<td>
<?php if ($driver['approval_status'] == 'pending'): ?>
<a href="update_driver_status.php?id=<?php echo $driver['id']; ?>&status=approved" class="btn btn-success btn-sm">Approve</a>
<a href="update_driver_status.php?id=<?php echo $driver['id']; ?>&status=rejected" class="btn btn-danger btn-sm">Reject</a>
<?php else: ?>
N/A
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include 'footer.php'; ?>

73
admin/edit_menu_item.php Normal file
View File

@ -0,0 +1,73 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$id = $_GET['id'] ?? null;
if (!$id) {
header('Location: index.php');
exit;
}
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$price = $_POST['price'] ?? '';
$id = $_POST['id'] ?? null;
$restaurant_id = $_POST['restaurant_id'] ?? null;
if ($name && $price && $id && $restaurant_id) {
$stmt = $pdo->prepare("UPDATE menu_items SET name = ?, description = ?, price = ? WHERE id = ?");
$stmt->execute([$name, $description, $price, $id]);
header('Location: menu.php?restaurant_id=' . $restaurant_id);
exit;
} else {
$error = "Name and price are required.";
}
}
$stmt = $pdo->prepare("SELECT * FROM menu_items WHERE id = ?");
$stmt->execute([$id]);
$item = $stmt->fetch();
if (!$item) {
header('Location: index.php');
exit;
}
?>
<div class="container mt-4">
<h2>Edit Menu Item</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="edit_menu_item.php?id=<?php echo $item['id']; ?>" method="POST">
<input type="hidden" name="id" value="<?php echo $item['id']; ?>">
<input type="hidden" name="restaurant_id" value="<?php echo $item['restaurant_id']; ?>">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($item['name']); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"><?php echo htmlspecialchars($item['description']); ?></textarea>
</div>
<div class="mb-3">
<label for="price" class="form-label">Price</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" value="<?php echo htmlspecialchars($item['price']); ?>" required>
</div>
<button type="submit" class="btn btn-primary">Update Item</button>
<a href="menu.php?restaurant_id=<?php echo $item['restaurant_id']; ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include 'footer.php'; ?>

118
admin/edit_restaurant.php Normal file
View File

@ -0,0 +1,118 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$id = $_GET['id'] ?? null;
if (!$id) {
header('Location: index.php');
exit;
}
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
$image_url = $_POST['image_url'] ?? '';
$selected_cuisines = $_POST['cuisines'] ?? [];
$id = $_POST['id'] ?? null;
if ($name && !empty($selected_cuisines) && $id) {
try {
$pdo->beginTransaction();
// Update restaurants table
$stmt = $pdo->prepare("UPDATE restaurants SET name = ?, description = ?, image_url = ? WHERE id = ?");
$stmt->execute([$name, $description, $image_url, $id]);
// Delete existing cuisine associations
$delete_stmt = $pdo->prepare("DELETE FROM restaurant_cuisines WHERE restaurant_id = ?");
$delete_stmt->execute([$id]);
// Insert new cuisine associations
$cuisine_stmt = $pdo->prepare("INSERT INTO restaurant_cuisines (restaurant_id, cuisine_id) VALUES (?, ?)");
foreach ($selected_cuisines as $cuisine_id) {
$cuisine_stmt->execute([$id, $cuisine_id]);
}
$pdo->commit();
header('Location: index.php');
exit;
} catch (Exception $e) {
$pdo->rollBack();
$error = "Error updating restaurant: " . $e->getMessage();
}
} else {
$error = "Name and at least one cuisine are required.";
}
}
// Fetch restaurant details
$stmt = $pdo->prepare("SELECT * FROM restaurants WHERE id = ?");
$stmt->execute([$id]);
$restaurant = $stmt->fetch();
if (!$restaurant) {
header('Location: index.php');
exit;
}
// Fetch all cuisines
$cuisines_stmt = $pdo->query("SELECT * FROM cuisines ORDER BY name ASC");
$cuisines = $cuisines_stmt->fetchAll();
// Fetch this restaurant's cuisines
$restaurant_cuisines_stmt = $pdo->prepare("SELECT cuisine_id FROM restaurant_cuisines WHERE restaurant_id = ?");
$restaurant_cuisines_stmt->execute([$id]);
$restaurant_cuisine_ids = $restaurant_cuisines_stmt->fetchAll(PDO::FETCH_COLUMN);
?>
<div class="container mt-4">
<h2>Edit Restaurant</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form action="edit_restaurant.php?id=<?php echo $restaurant['id']; ?>" method="POST">
<input type="hidden" name="id" value="<?php echo $restaurant['id']; ?>">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($restaurant['name']); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"><?php echo htmlspecialchars($restaurant['description']); ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">Cuisines</label>
<div class="row">
<?php foreach ($cuisines as $cuisine): ?>
<div class="col-md-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="cuisines[]" value="<?php echo $cuisine['id']; ?>" id="cuisine_<?php echo $cuisine['id']; ?>" <?php echo in_array($cuisine['id'], $restaurant_cuisine_ids) ? 'checked' : ''; ?>>
<label class="form-check-label" for="cuisine_<?php echo $cuisine['id']; ?>">
<?php echo htmlspecialchars($cuisine['name']); ?>
</label>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="mb-3">
<label for="image_url" class="form-label">Image URL</label>
<input type="text" class="form-control" id="image_url" name="image_url" value="<?php echo htmlspecialchars($restaurant['image_url']); ?>">
</div>
<button type="submit" class="btn btn-primary">Update Restaurant</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?php include 'footer.php'; ?>

62
admin/export_events.php Normal file
View 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;

17
admin/footer.php Normal file
View File

@ -0,0 +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>

66
admin/header.php Normal file
View File

@ -0,0 +1,66 @@
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<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">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">Admin Dashboard</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="index.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Orders</a>
</li>
<li class="nav-item">
<a class="nav-link" href="coupons.php">Coupons</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cuisines.php">Cuisines</a>
</li>
<li class="nav-item">
<a class="nav-link" href="drivers.php">Drivers</a>
</li>
<li class="nav-item">
<a class="nav-link" href="settings.php">Settings</a>
</li>
<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>
<li class="nav-item">
<a class="nav-link" href="pages.php">Pages</a>
</li>
<li class="nav-item">
<a class="nav-link" href="required_documents.php">Required Documents</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">

197
admin/index.php Normal file
View File

@ -0,0 +1,197 @@
<?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) {
header('Location: login.php');
exit;
}
$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 = ?");
$stmt_revenue->execute(['completed']);
$total_revenue = $stmt_revenue->fetchColumn();
// Total Orders
$total_orders = $pdo->query("SELECT COUNT(*) FROM orders")->fetchColumn();
// Total Customers
$total_customers = $pdo->query("SELECT COUNT(*) FROM users WHERE role = 'customer'")->fetchColumn();
// Most Popular Restaurants
$stmt_popular = $pdo->query("
SELECT r.name, COUNT(o.id) as order_count
FROM restaurants r
JOIN orders o ON r.id = o.restaurant_id
GROUP BY r.id
ORDER BY order_count DESC
LIMIT 5
");
$popular_restaurants = $stmt_popular->fetchAll();
// Fetch all restaurants for the management table
$stmt_restaurants = $pdo->query("SELECT id, name, cuisine FROM restaurants ORDER BY name");
$restaurants = $stmt_restaurants->fetchAll();
?>
<div class="container mt-4">
<h2>Admin Dashboard</h2>
<!-- Analytics Cards -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card text-white bg-primary mb-3">
<div class="card-header">Total Revenue</div>
<div class="card-body">
<h5 class="card-title">$<?php echo number_format($total_revenue, 2); ?></h5>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-white bg-info mb-3">
<div class="card-header">Total Orders</div>
<div class="card-body">
<h5 class="card-title"><?php echo $total_orders; ?></h5>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-white bg-success mb-3">
<div class="card-header">Total Customers</div>
<div class="card-body">
<h5 class="card-title"><?php echo $total_customers; ?></h5>
</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); ?> &deg;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">
<div class="card-header">
Most Popular Restaurants
</div>
<ul class="list-group list-group-flush">
<?php foreach ($popular_restaurants as $restaurant): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($restaurant['name']); ?>
<span class="badge bg-primary rounded-pill"><?php echo $restaurant['order_count']; ?> orders</span>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
<h2 class="mt-5">Restaurant Management</h2>
<p><a href="add_restaurant.php" class="btn btn-success">Add New Restaurant</a></p>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Cuisine</th>
<th>Location Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$stmt_restaurants = $pdo->query("SELECT id, name, cuisine, location_status FROM restaurants ORDER BY name");
$restaurants = $stmt_restaurants->fetchAll();
foreach ($restaurants as $restaurant): ?>
<tr>
<td><?php echo htmlspecialchars($restaurant['name']); ?></td>
<td><?php echo htmlspecialchars($restaurant['cuisine']); ?></td>
<td>
<?php
$status = $restaurant['location_status'];
$badge_class = 'badge bg-secondary';
if ($status == 'approved') {
$badge_class = 'badge bg-success';
} elseif ($status == 'rejected') {
$badge_class = 'badge bg-danger';
}
?>
<span class="<?php echo $badge_class; ?>"><?php echo htmlspecialchars(ucfirst($status)); ?></span>
</td>
<td>
<a href="edit_restaurant.php?id=<?php echo $restaurant['id']; ?>" class="btn btn-primary btn-sm">Edit</a>
<?php if ($status == 'pending_approval'): ?>
<a href="update_location_status.php?id=<?php echo $restaurant['id']; ?>&status=approved" class="btn btn-success btn-sm">Approve</a>
<a href="update_location_status.php?id=<?php echo $restaurant['id']; ?>&status=rejected" class="btn btn-warning btn-sm">Reject</a>
<?php endif; ?>
<a href="delete_restaurant.php?id=<?php echo $restaurant['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this restaurant?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include 'footer.php'; ?>

97
admin/login.php Normal file
View File

@ -0,0 +1,97 @@
<?php
session_start();
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
header('Location: index.php');
exit;
}
$error = $_SESSION['login_error'] ?? '';
unset($_SESSION['login_error']);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css">
<style>
body, html {
height: 100%;
background-color: #f8f9fa;
}
.main-container {
display: flex;
height: 100%;
width: 100%;
overflow: hidden;
}
.form-section {
display: flex;
align-items: center;
justify-content: center;
width: 50%;
padding: 40px;
}
.image-section {
width: 50%;
background: url('https://images.pexels.com/photos/376464/pexels-photo-376464.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2') no-repeat center center;
background-size: cover;
}
.form-container {
width: 100%;
max-width: 450px;
}
.form-container h1 {
font-weight: 700;
margin-bottom: 1rem;
}
.form-container .form-text {
margin-bottom: 2rem;
}
.form-control {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
.btn-primary {
padding: 0.75rem;
border-radius: 0.5rem;
font-weight: 600;
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
font-size: 1.2rem;
}
</style>
</head>
<body>
<a href="../index.php" class="btn btn-light back-button"><i class="bi bi-arrow-left"></i> Back to Site</a>
<div class="main-container">
<div class="form-section">
<div class="form-container">
<h1>Admin Login</h1>
<p class="text-muted">Please enter your credentials to access the admin dashboard.</p>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form action="login_process.php" method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
</div>
</div>
<div class="image-section"></div>
</div>
</body>
</html>

36
admin/login_process.php Normal file
View File

@ -0,0 +1,36 @@
<?php
session_start();
require_once '../db/config.php';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
$_SESSION['login_error'] = 'Email and password are required.';
header('Location: login.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password']) && $user['is_admin']) {
$_SESSION['admin_logged_in'] = true;
$_SESSION['admin_user_id'] = $user['id'];
$_SESSION['admin_email'] = $user['email'];
header('Location: index.php');
exit;
} else {
$_SESSION['login_error'] = 'Invalid credentials or not an admin.';
header('Location: login.php');
exit;
}
} catch (PDOException $e) {
$_SESSION['login_error'] = 'Database error: ' . $e->getMessage();
header('Location: login.php');
exit;
}
?>

6
admin/logout.php Normal file
View File

@ -0,0 +1,6 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php');
exit;

64
admin/menu.php Normal file
View File

@ -0,0 +1,64 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$restaurant_id = $_GET['restaurant_id'] ?? null;
if (!$restaurant_id) {
header('Location: index.php');
exit;
}
$pdo = db();
// Get restaurant details
$stmt = $pdo->prepare("SELECT name FROM restaurants WHERE id = ?");
$stmt->execute([$restaurant_id]);
$restaurant = $stmt->fetch();
if (!$restaurant) {
header('Location: index.php');
exit;
}
// Get menu items for the restaurant
$stmt = $pdo->prepare("SELECT * FROM menu_items WHERE restaurant_id = ? ORDER BY name");
$stmt->execute([$restaurant_id]);
$menu_items = $stmt->fetchAll();
?>
<div class="container mt-4">
<h2>Manage Menu for <?php echo htmlspecialchars($restaurant['name']); ?></h2>
<p><a href="add_menu_item.php?restaurant_id=<?php echo $restaurant_id; ?>" class="btn btn-success">Add New Menu Item</a></p>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($menu_items as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['name']); ?></td>
<td><?php echo htmlspecialchars($item['description']); ?></td>
<td>$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></td>
<td>
<a href="edit_menu_item.php?id=<?php echo $item['id']; ?>" class="btn btn-primary btn-sm">Edit</a>
<a href="delete_menu_item.php?id=<?php echo $item['id']; ?>&restaurant_id=<?php echo $restaurant_id; ?>" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this item?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<a href="index.php" class="btn btn-secondary">Back to Restaurants</a>
</div>
<?php include 'footer.php'; ?>

View File

@ -0,0 +1,118 @@
<?php
require_once '../db/config.php';
require_once 'header.php';
$db = db();
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_email'])) {
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$form_type = $_POST['form_type'];
if (filter_var($email, FILTER_VALIDATE_EMAIL) && in_array($form_type, ['help', 'driver_signup'])) {
try {
$stmt = $db->prepare("INSERT INTO email_recipients (email, form_type) VALUES (?, ?)");
$stmt->execute([$email, $form_type]);
} catch (PDOException $e) {
// Ignore unique constraint violations
if ($e->getCode() !== '23505') {
throw $e;
}
}
}
} elseif (isset($_POST['delete_email'])) {
$id = $_POST['id'];
$stmt = $db->prepare("DELETE FROM email_recipients WHERE id = ?");
$stmt->execute([$id]);
}
// Redirect to the same page to prevent form resubmission
header("Location: notification_emails.php");
exit;
}
// Fetch existing emails
$stmt = $db->query("SELECT * FROM email_recipients ORDER BY form_type, email");
$recipients = $stmt->fetchAll(PDO::FETCH_ASSOC);
$help_recipients = array_filter($recipients, function($r) { return $r['form_type'] === 'help'; });
$driver_recipients = array_filter($recipients, function($r) { return $r['form_type'] === 'driver_signup'; });
?>
<div class="container mt-4">
<h2>Notification Email Recipients</h2>
<p>Configure the email addresses that receive notifications when users submit the help or driver signup forms.</p>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
Help Form Recipients
</div>
<ul class="list-group list-group-flush">
<?php if (empty($help_recipients)): ?>
<li class="list-group-item text-muted">No recipients configured. Emails will be sent to the default address.</li>
<?php else: ?>
<?php foreach ($help_recipients as $recipient): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($recipient['email']); ?>
<form method="POST" action="notification_emails.php" class="m-0">
<input type="hidden" name="id" value="<?php echo $recipient['id']; ?>">
<button type="submit" name="delete_email" class="btn btn-danger btn-sm">&times;</button>
</form>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
Driver Signup Form Recipients
</div>
<ul class="list-group list-group-flush">
<?php if (empty($driver_recipients)): ?>
<li class="list-group-item text-muted">No recipients configured. Emails will be sent to the default address.</li>
<?php else: ?>
<?php foreach ($driver_recipients as $recipient): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($recipient['email']); ?>
<form method="POST" action="notification_emails.php" class="m-0">
<input type="hidden" name="id" value="<?php echo $recipient['id']; ?>">
<button type="submit" name="delete_email" class="btn btn-danger btn-sm">&times;</button>
</form>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
Add New Recipient
</div>
<div class="card-body">
<form method="POST" action="notification_emails.php" class="row g-3 align-items-end">
<div class="col-md-5">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="col-md-5">
<label for="form_type" class="form-label">Form Type</label>
<select class="form-select" id="form_type" name="form_type">
<option value="help">Help Form</option>
<option value="driver_signup">Driver Signup Form</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" name="add_email" class="btn btn-primary w-100">Add</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once 'footer.php'; ?>

101
admin/order_details.php Normal file
View File

@ -0,0 +1,101 @@
<?php
require_once 'header.php';
require_once '../db/config.php';
if (!isset($_GET['id'])) {
echo "<div class='alert alert-danger'>No order ID specified.</div>";
require_once 'footer.php';
exit;
}
$order_id = $_GET['id'];
$db = db();
// Handle status update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['status'])) {
$status = $_POST['status'];
$update_stmt = $db->prepare("UPDATE orders SET status = :status WHERE id = :order_id");
$update_stmt->bindParam(':status', $status);
$update_stmt->bindParam(':order_id', $order_id);
$update_stmt->execute();
}
// Fetch order details
$order_stmt = $db->prepare("SELECT o.*, u.name AS user_name, u.email AS user_email FROM orders o JOIN users u ON o.user_id = u.id WHERE o.id = :order_id");
$order_stmt->bindParam(':order_id', $order_id);
$order_stmt->execute();
$order = $order_stmt->fetch(PDO::FETCH_ASSOC);
if (!$order) {
echo "<div class='alert alert-danger'>Order not found.</div>";
require_once 'footer.php';
exit;
}
// Fetch order items
$items_stmt = $db->prepare("SELECT oi.*, p.name AS product_name FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = :order_id");
$items_stmt->bindParam(':order_id', $order_id);
$items_stmt->execute();
$items = $items_stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<h2>Order Details #<?php echo htmlspecialchars($order['id']); ?></h2>
<div class="card mb-4">
<div class="card-header">Customer & Order Info</div>
<div class="card-body">
<p><strong>Customer:</strong> <?php echo htmlspecialchars($order['user_name']); ?></p>
<p><strong>Email:</strong> <?php echo htmlspecialchars($order['user_email']); ?></p>
<p><strong>Address:</strong> <?php echo htmlspecialchars($order['delivery_address']); ?></p>
<p><strong>Total Price:</strong> $<?php echo htmlspecialchars(number_format($order['total_price'], 2)); ?></p>
<p><strong>Order Date:</strong> <?php echo htmlspecialchars($order['created_at']); ?></p>
<p><strong>Status:</strong> <?php echo htmlspecialchars($order['status']); ?></p>
</div>
</div>
<div class="card mb-4">
<div class="card-header">Order Items</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['product_name']); ?></td>
<td><?php echo htmlspecialchars($item['quantity']); ?></td>
<td>$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">Update Status</div>
<div class="card-body">
<form action="order_details.php?id=<?php echo $order['id']; ?>" method="POST">
<div class="input-group">
<select name="status" class="form-select">
<option value="Pending" <?php echo $order['status'] === 'Pending' ? 'selected' : ''; ?>>Pending</option>
<option value="Confirmed" <?php echo $order['status'] === 'Confirmed' ? 'selected' : ''; ?>>Confirmed</option>
<option value="In Progress" <?php echo $order['status'] === 'In Progress' ? 'selected' : ''; ?>>In Progress</option>
<option value="Out for Delivery" <?php echo $order['status'] === 'Out for Delivery' ? 'selected' : ''; ?>>Out for Delivery</option>
<option value="Completed" <?php echo $order['status'] === 'Completed' ? 'selected' : ''; ?>>Completed</option>
<option value="Cancelled" <?php echo $order['status'] === 'Cancelled' ? 'selected' : ''; ?>>Cancelled</option>
</select>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
<a href="index.php" class="btn btn-secondary mt-3">Back to Orders</a>
<?php require_once 'footer.php'; ?>

112
admin/orders.php Normal file
View File

@ -0,0 +1,112 @@
<?php
include 'header.php';
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
$pdo = db();
// Handle status update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['order_id']) && isset($_POST['status'])) {
$order_id = $_POST['order_id'];
$status = $_POST['status'];
$update_stmt = $pdo->prepare("UPDATE orders SET status = :status WHERE id = :order_id");
$update_stmt->execute(['status' => $status, 'order_id' => $order_id]);
// Redirect to the same page to prevent form resubmission
header('Location: orders.php');
exit;
}
// Fetch all orders with user information
$stmt = $pdo->query("
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
ORDER BY o.created_at DESC
");
$orders = $stmt->fetchAll();
// Fetch all approved drivers
$driver_stmt = $pdo->query("SELECT id, full_name FROM drivers WHERE approval_status = 'approved'");
$approved_drivers = $driver_stmt->fetchAll();
$possible_statuses = ['Pending', 'Confirmed', 'Preparing', 'Out for Delivery', 'Delivered', 'Cancelled'];
?>
<div class="container mt-4">
<h2>Order Management</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Order ID</th>
<th>Customer</th>
<th>Total Price</th>
<th>Order Date</th>
<th>Status</th>
<th>Update Status</th>
<th>Assign Driver</th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order): ?>
<tr>
<td><?php echo $order['id']; ?></td>
<td><?php echo htmlspecialchars($order['user_name']); ?></td>
<td>$<?php echo number_format($order['total_price'], 2); ?></td>
<td><?php echo $order['created_at']; ?></td>
<td><?php echo htmlspecialchars($order['status']); ?></td>
<td>
<form action="orders.php" method="POST" class="form-inline">
<input type="hidden" name="order_id" value="<?php echo $order['id']; ?>">
<div class="form-group">
<select name="status" class="form-control form-control-sm">
<?php foreach ($possible_statuses as $status): ?>
<option value="<?php echo $status; ?>" <?php echo ($order['status'] === $status) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($status); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary btn-sm ml-2">Update</button>
</form>
</td>
<td>
<?php
// Check if a driver is already assigned
$assignment_stmt = $pdo->prepare("SELECT d.full_name FROM driver_assignments da JOIN drivers d ON da.driver_id = d.id WHERE da.order_id = ?");
$assignment_stmt->execute([$order['id']]);
$assigned_driver = $assignment_stmt->fetch();
?>
<?php if ($assigned_driver): ?>
<?php echo htmlspecialchars($assigned_driver['full_name']); ?>
<?php else: ?>
<form action="assign_driver.php" method="POST" class="form-inline">
<input type="hidden" name="order_id" value="<?php echo $order['id']; ?>">
<div class="form-group">
<select name="driver_id" class="form-control form-control-sm">
<option value="">Select Driver</option>
<?php foreach ($approved_drivers as $driver): ?>
<option value="<?php echo $driver['id']; ?>">
<?php echo htmlspecialchars($driver['full_name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-success btn-sm ml-2">Assign</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include 'footer.php'; ?>

55
admin/pages.php Normal file
View File

@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once 'header.php';
$db = db();
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['pages']) && is_array($_POST['pages'])) {
foreach ($_POST['pages'] as $page_id => $page_data) {
$title = $page_data['title'] ?? '';
$content = $page_data['content'] ?? '';
$stmt = $db->prepare("UPDATE pages SET page_title = :title, content = :content WHERE id = :id");
$stmt->bindValue(':title', $title);
$stmt->bindValue(':content', $content);
$stmt->bindValue(':id', $page_id, PDO::PARAM_INT);
$stmt->execute();
}
$message = '<div class="alert alert-success">Pages updated successfully!</div>';
}
}
$stmt = $db->query("SELECT * FROM pages ORDER BY id");
$pages = $stmt->fetchAll();
?>
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Manage Pages</h1>
<?php echo $message; ?>
<form method="POST" action="pages.php">
<?php foreach ($pages as $page): ?>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Edit <?php echo htmlspecialchars($page['page_title']); ?></h6>
</div>
<div class="card-body">
<input type="hidden" name="pages[<?php echo $page['id']; ?>][id]" value="<?php echo $page['id']; ?>">
<div class="form-group">
<label for="page_title_<?php echo $page['id']; ?>">Page Title</label>
<input type="text" class="form-control" id="page_title_<?php echo $page['id']; ?>" name="pages[<?php echo $page['id']; ?>][title]" value="<?php echo htmlspecialchars($page['page_title']); ?>">
</div>
<div class="form-group">
<label for="page_content_<?php echo $page['id']; ?>">Content</label>
<textarea class="form-control" id="page_content_<?php echo $page['id']; ?>" name="pages[<?php echo $page['id']; ?>][content]" rows="10"><?php echo htmlspecialchars($page['content']); ?></textarea>
</div>
</div>
</div>
<?php endforeach; ?>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
<?php require_once 'footer.php'; ?>

View File

@ -0,0 +1,93 @@
<?php
require_once '../db/config.php';
require_once 'header.php';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_document'])) {
$document_name = trim($_POST['document_name']);
$applies_to = trim($_POST['applies_to']);
if (!empty($document_name) && !empty($applies_to)) {
$db = db();
$stmt = $db->prepare("INSERT INTO required_documents (document_name, applies_to) VALUES (?, ?)");
$stmt->execute([$document_name, $applies_to]);
}
} elseif (isset($_POST['delete_document'])) {
$document_id = $_POST['document_id'];
$db = db();
$stmt = $db->prepare("DELETE FROM required_documents WHERE id = ?");
$stmt->execute([$document_id]);
}
header("Location: required_documents.php");
exit;
}
// Fetch all required documents
$db = db();
$stmt = $db->query("SELECT * FROM required_documents ORDER BY applies_to, document_name");
$documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="container mt-4">
<h2>Manage Required Documents</h2>
<p>Define which documents are required for driver and restaurant sign-ups.</p>
<div class="row">
<div class="col-md-8">
<h4>Current Requirements</h4>
<table class="table table-striped">
<thead>
<tr>
<th>Document Name</th>
<th>Applies To</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($documents)): ?>
<tr>
<td colspan="3" class="text-center">No required documents have been defined yet.</td>
</tr>
<?php else: ?>
<?php foreach ($documents as $doc): ?>
<tr>
<td><?php echo htmlspecialchars($doc['document_name']); ?></td>
<td><?php echo ucfirst(htmlspecialchars($doc['applies_to'])); ?></td>
<td>
<form method="POST" action="required_documents.php" onsubmit="return confirm('Are you sure you want to delete this document requirement?');">
<input type="hidden" name="document_id" value="<?php echo $doc['id']; ?>">
<button type="submit" name="delete_document" class="btn btn-danger btn-sm">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="col-md-4">
<h4>Add New Requirement</h4>
<div class="card">
<div class="card-body">
<form method="POST" action="required_documents.php">
<div class="mb-3">
<label for="document_name" class="form-label">Document Name</label>
<input type="text" class="form-control" id="document_name" name="document_name" placeholder="e.g., Driver's License" required>
</div>
<div class="mb-3">
<label for="applies_to" class="form-label">Applies To</label>
<select class="form-select" id="applies_to" name="applies_to" required>
<option value="" disabled selected>Select one...</option>
<option value="driver">Driver</option>
<option value="restaurant">Restaurant</option>
</select>
</div>
<button type="submit" name="add_document" class="btn btn-primary">Add Requirement</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'footer.php'; ?>

119
admin/settings.php Normal file
View File

@ -0,0 +1,119 @@
<?php
session_start();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
require_once '../db/config.php';
$settings = [];
$stmt = db()->query('SELECT * FROM settings');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$settings[$row['name']] = $row['value'];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = db()->prepare('UPDATE settings SET value = :value WHERE name = :name');
foreach ($_POST as $name => $value) {
$stmt->execute(['value' => $value, 'name' => $name]);
}
header('Location: settings.php?success=1');
exit;
}
include 'header.php';
?>
<div class="container mt-4">
<h2>Settings</h2>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">Settings saved successfully!</div>
<?php endif; ?>
<form method="POST">
<div class="card mb-4">
<div class="card-header">
<h5>Fees</h5>
</div>
<div class="card-body">
<div class="form-group">
<label for="service_fee_percentage">Service Fee Percentage (%)</label>
<input type="number" class="form-control" id="service_fee_percentage" name="service_fee_percentage" value="<?php echo htmlspecialchars($settings['service_fee_percentage'] ?? ''); ?>" step="0.01">
</div>
<div class="form-group">
<label for="delivery_fee">Delivery Fee ($)</label>
<input type="number" class="form-control" id="delivery_fee" name="delivery_fee" value="<?php echo htmlspecialchars($settings['delivery_fee'] ?? ''); ?>" step="0.01">
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5>Driver Pay</h5>
</div>
<div class="card-body">
<h6>Base Pay</h6>
<div class="form-row">
<div class="form-group col-md-6">
<label for="driver_base_pay_tier1_miles">Base Pay Tier 1 (miles)</label>
<input type="number" class="form-control" id="driver_base_pay_tier1_miles" name="driver_base_pay_tier1_miles" value="<?php echo htmlspecialchars($settings['driver_base_pay_tier1_miles'] ?? ''); ?>" step="1">
</div>
<div class="form-group col-md-6">
<label for="driver_base_pay_tier1_amount">Base Pay Tier 1 Amount ($)</label>
<input type="number" class="form-control" id="driver_base_pay_tier1_amount" name="driver_base_pay_tier1_amount" value="<?php echo htmlspecialchars($settings['driver_base_pay_tier1_amount'] ?? ''); ?>" step="0.01">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="driver_base_pay_tier2_miles">Base Pay Tier 2 (miles)</label>
<input type="number" class="form-control" id="driver_base_pay_tier2_miles" name="driver_base_pay_tier2_miles" value="<?php echo htmlspecialchars($settings['driver_base_pay_tier2_miles'] ?? ''); ?>" step="1">
</div>
<div class="form-group col-md-6">
<label for="driver_base_pay_tier2_amount">Base Pay Tier 2 Amount ($)</label>
<input type="number" class="form-control" id="driver_base_pay_tier2_amount" name="driver_base_pay_tier2_amount" value="<?php echo htmlspecialchars($settings['driver_base_pay_tier2_amount'] ?? ''); ?>" step="0.01">
</div>
</div>
<div class="form-group">
<label for="driver_base_pay_tier3_amount">Base Pay Tier 3 Amount (> Tier 2 miles) ($)</label>
<input type="number" class="form-control" id="driver_base_pay_tier3_amount" name="driver_base_pay_tier3_amount" value="<?php echo htmlspecialchars($settings['driver_base_pay_tier3_amount'] ?? ''); ?>" step="0.01">
</div>
<hr>
<h6>Mileage Pay</h6>
<div class="form-group">
<label for="driver_mileage_pay_rate">Mileage Pay Rate ($ per mile)</label>
<input type="number" class="form-control" id="driver_mileage_pay_rate" name="driver_mileage_pay_rate" value="<?php echo htmlspecialchars($settings['driver_mileage_pay_rate'] ?? ''); ?>" step="0.01">
</div>
<hr>
<h6>Bonuses</h6>
<div class="form-group">
<label for="driver_busy_hour_bonus">Busy Hour Bonus ($ per delivery)</label>
<input type="number" class="form-control" id="driver_busy_hour_bonus" name="driver_busy_hour_bonus" value="<?php echo htmlspecialchars($settings['driver_busy_hour_bonus'] ?? ''); ?>" step="0.01">
</div>
<div class="form-row">
<div class="form-group col-md-4">
<label for="driver_quest_bonus_amount">Quest Bonus Amount ($)</label>
<input type="number" class="form-control" id="driver_quest_bonus_amount" name="driver_quest_bonus_amount" value="<?php echo htmlspecialchars($settings['driver_quest_bonus_amount'] ?? ''); ?>" step="0.01">
</div>
<div class="form-group col-md-4">
<label for="driver_quest_bonus_deliveries">Quest Bonus Deliveries</label>
<input type="number" class="form-control" id="driver_quest_bonus_deliveries" name="driver_quest_bonus_deliveries" value="<?php echo htmlspecialchars($settings['driver_quest_bonus_deliveries'] ?? ''); ?>" step="1">
</div>
<div class="form-group col-md-4">
<label for="driver_quest_bonus_hours">Quest Bonus Hours</label>
<input type="number" class="form-control" id="driver_quest_bonus_hours" name="driver_quest_bonus_hours" value="<?php echo htmlspecialchars($settings['driver_quest_bonus_hours'] ?? ''); ?>" step="1">
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</div>
<?php include 'footer.php'; ?>

124
admin/system_events.php Normal file
View 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'; ?>

View 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;

View File

@ -0,0 +1,26 @@
<?php
require_once '../db/config.php';
session_start();
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
if (isset($_GET['id']) && isset($_GET['status'])) {
$driver_id = $_GET['id'];
$status = $_GET['status'];
$possible_statuses = ['approved', 'rejected'];
if (in_array($status, $possible_statuses)) {
$pdo = db();
$update_stmt = $pdo->prepare("UPDATE drivers SET approval_status = :status WHERE id = :driver_id");
$update_stmt->execute(['status' => $status, 'driver_id' => $driver_id]);
}
}
header('Location: drivers.php');
exit;
?>

View File

@ -0,0 +1,60 @@
<?php
session_start();
require_once '../db/config.php';
// Check if the user is logged in as an admin
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
header('Location: login.php');
exit;
}
if (isset($_GET['id']) && isset($_GET['status'])) {
$restaurant_id = $_GET['id'];
$status = $_GET['status'];
// Validate status
$allowed_statuses = ['approved', 'rejected', 'pending_approval'];
if (!in_array($status, $allowed_statuses)) {
// Invalid status, redirect back
header('Location: index.php');
exit;
}
$pdo = db();
$sql = "UPDATE restaurants SET location_status = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$status, $restaurant_id]);
// Get restaurant owner's email
$stmt_owner = $pdo->prepare("SELECT u.email, r.name FROM users u JOIN restaurants r ON u.id = r.user_id WHERE r.id = ?");
$stmt_owner->execute([$restaurant_id]);
$owner_info = $stmt_owner->fetch();
if ($owner_info) {
require_once '../mail/MailService.php';
$owner_email = $owner_info['email'];
$restaurant_name = $owner_info['name'];
$subject = '';
$body = '';
if ($status == 'approved') {
$subject = "Your Restaurant Location has been Approved";
$body = "<p>Congratulations! The location for your restaurant, <strong>" . htmlspecialchars($restaurant_name) . "</strong>, has been approved.</p><p>Your restaurant is now fully visible to customers.</p>";
} elseif ($status == 'rejected') {
$subject = "Action Required: Your Restaurant Location has been Rejected";
$body = "<p>There was an issue with the location provided for your restaurant, <strong>" . htmlspecialchars($restaurant_name) . "</strong>.</p><p>Please log in to your account and update the location pin for our team to review again.</p>";
}
if (!empty($subject)) {
MailService::sendMail($owner_email, $subject, $body);
}
}
header('Location: index.php');
exit;
} else {
// If id or status is not provided, redirect
header('Location: index.php');
exit;
}
?>

View File

@ -0,0 +1,53 @@
<?php
session_start();
require_once '../db/config.php';
if (!isset($_GET['order_id'])) {
http_response_code(400);
echo json_encode(['error' => 'Order ID is required.']);
exit;
}
$order_id = $_GET['order_id'];
$user_id = $_SESSION['user_id'] ?? null;
// For guest users, we need a token
$token = $_GET['token'] ?? null;
$pdoconn = db();
// Verify the user or guest has permission to view this order
if ($user_id) {
$stmt = $pdoconn->prepare("SELECT id FROM orders WHERE id = :order_id AND user_id = :user_id");
$stmt->execute(['order_id' => $order_id, 'user_id' => $user_id]);
} else if ($token) {
$stmt = $pdoconn->prepare("SELECT id FROM orders WHERE id = :order_id AND token = :token");
$stmt->execute(['order_id' => $order_id, 'token' => $token]);
} else {
http_response_code(403);
echo json_encode(['error' => 'Authentication required.']);
exit;
}
if ($stmt->rowCount() == 0) {
http_response_code(404);
echo json_encode(['error' => 'Order not found or access denied.']);
exit;
}
// Fetch driver location
$stmt = $pdoconn->prepare("SELECT driver_lat, driver_lng FROM orders WHERE id = :order_id");
$stmt->execute(['order_id' => $order_id]);
$location = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$location || is_null($location['driver_lat']) || is_null($location['driver_lng'])) {
http_response_code(404);
echo json_encode(['error' => 'Driver location not available yet.']);
exit;
}
header('Content-Type: application/json');
echo json_encode([
'lat' => $location['driver_lat'],
'lng' => $location['driver_lng']
]);

29
api/get_new_orders.php Normal file
View 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);
?>

70
api/get_order_status.php Normal file
View File

@ -0,0 +1,70 @@
<?php
header('Content-Type: application/json');
session_start();
require_once __DIR__ . '/../db/config.php';
$order_id = $_GET['order_id'] ?? null;
$token = $_GET['token'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
if (!$order_id) {
http_response_code(400);
echo json_encode(['error' => 'Order ID not specified']);
exit;
}
try {
$pdo = db();
$query =
'SELECT ' .
'o.status, o.delivery_address, o.driver_lat, o.driver_lng, ' .
'r.name as restaurant_name, r.lat as restaurant_lat, r.lng as restaurant_lng ' .
'FROM orders o ' .
'JOIN restaurants r ON o.restaurant_id = r.id ' .
'WHERE o.id = ?';
$params = [$order_id];
if ($user_id) {
$query .= ' AND o.user_id = ?';
$params[] = $user_id;
} elseif ($token) {
$query .= ' AND o.guest_token = ?';
$params[] = $token;
} else {
http_response_code(403);
echo json_encode(['error' => 'Permission denied']);
exit;
}
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
if ($order) {
// For privacy, we won't return the user's exact address lat/lng.
// The frontend will have to geocode the delivery address.
// We will add a Google Maps API key for this in a later step.
echo json_encode([
'status' => ucwords($order['status']),
'delivery_address' => $order['delivery_address'],
'driver_location' => [
'lat' => $order['driver_lat'],
'lng' => $order['driver_lng']
],
'restaurant_location' => [
'name' => $order['restaurant_name'],
'lat' => $order['restaurant_lat'],
'lng' => $order['restaurant_lng']
]
]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Order not found or permission denied']);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}

19
api/pexels.php Normal file
View File

@ -0,0 +1,19 @@
<?php
header('Content-Type: application/json');
require_once __DIR__.'/../includes/pexels.php';
$q = isset($_GET['query']) ? $_GET['query'] : 'nature';
$orientation = isset($_GET['orientation']) ? $_GET['orientation'] : 'portrait';
$url = 'https://api.pexels.com/v1/search?query=' . urlencode($q) . '&orientation=' . urlencode($orientation) . '&per_page=1&page=1';
$data = pexels_get($url);
if (!$data || empty($data['photos'])) { echo json_encode(['error'=>'Failed to fetch image']); exit; }
$photo = $data['photos'][0];
$src = $photo['src']['large2x'] ?? ($photo['src']['large'] ?? $photo['src']['original']);
$target = __DIR__ . '/../assets/images/pexels/' . $photo['id'] . '.jpg';
download_to($src, $target);
// Return minimal info and local relative path
echo json_encode([
'id' => $photo['id'],
'local' => 'assets/images/pexels/' . $photo['id'] . '.jpg',
'photographer' => $photo['photographer'] ?? null,
'photographer_url' => $photo['photographer_url'] ?? null,
]);

49
api/save_location.php Normal file
View File

@ -0,0 +1,49 @@
<?php
session_start();
require_once '../db/config.php';
// Check if the user is a driver and is logged in
if (!isset($_SESSION['driver_id']) || !isset($_SESSION['role']) || $_SESSION['role'] !== 'driver') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$orderId = $data['order_id'] ?? null;
$lat = $data['lat'] ?? null;
$lng = $data['lng'] ?? null;
if (!$orderId || !$lat || !$lng) {
http_response_code(400);
echo json_encode(['error' => 'Missing required parameters.']);
exit;
}
try {
$pdo = db();
// First, verify the driver is assigned to this order
$stmt = $pdo->prepare("SELECT id FROM driver_assignments WHERE order_id = ? AND driver_id = ?");
$stmt->execute([$orderId, $_SESSION['driver_id']]);
$assignment = $stmt->fetch();
if (!$assignment) {
http_response_code(403);
echo json_encode(['error' => 'You are not assigned to this order.']);
exit;
}
// Update the order with the driver's location
$stmt = $pdo->prepare("UPDATE orders SET driver_lat = ?, driver_lng = ? WHERE id = ?");
$stmt->execute([$lat, $lng, $orderId]);
echo json_encode(['success' => true]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method Not Allowed']);
}

74
api/support.php Normal file
View 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()]);
}

29
apply_coupon.php Normal file
View File

@ -0,0 +1,29 @@
<?php
session_start();
require_once 'db/config.php';
if (isset($_POST['coupon_code']) && !empty($_POST['coupon_code'])) {
$coupon_code = trim($_POST['coupon_code']);
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM coupons WHERE code = ? AND is_active = 1");
$stmt->execute([$coupon_code]);
$coupon = $stmt->fetch(PDO::FETCH_ASSOC);
if ($coupon) {
$_SESSION['coupon_id'] = $coupon['id'];
$_SESSION['coupon_code'] = $coupon['code'];
$_SESSION['discount_percentage'] = $coupon['discount_percentage'];
unset($_SESSION['coupon_error']);
} else {
$_SESSION['coupon_error'] = "Invalid or expired coupon code.";
unset($_SESSION['coupon_id']);
unset($_SESSION['coupon_code']);
unset($_SESSION['discount_percentage']);
}
} else {
$_SESSION['coupon_error'] = "Please enter a coupon code.";
}
header("Location: cart.php");
exit();

214
assets/css/main.css Normal file
View File

@ -0,0 +1,214 @@
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #343a40;
--font-family-sans-serif: 'Inter', sans-serif;
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
body {
font-family: var(--font-family-sans-serif);
line-height: 1.6;
}
.main-header .navbar-brand {
font-weight: 700;
font-size: 1.5rem;
}
.main-header .nav-link {
font-weight: 600;
}
.hero-section {
background: url('https://images.pexels.com/photos/1640777/pexels-photo-1640-1.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2') no-repeat center center;
background-size: cover;
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: white;
position: relative;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.hero-content {
position: relative;
z-index: 1;
}
.hero-title {
font-size: 3.5rem;
font-weight: 700;
}
.hero-subtitle {
font-size: 1.25rem;
}
.section-title {
font-weight: 700;
margin-bottom: 3rem;
}
.feature-card {
border: none;
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
.feature-icon {
font-size: 3rem;
color: var(--primary-color);
}
.restaurant-card {
border: 1px solid #eee;
border-radius: .5rem;
transition: transform .2s, box-shadow .2s;
}
.restaurant-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
.cuisine-card {
text-align: center;
text-decoration: none;
color: var(--dark-color);
display: block;
transition: transform .2s;
}
.cuisine-card:hover {
transform: translateY(-5px);
}
.cuisine-card img {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
/* Restaurant Page Styles */
.restaurant-card-new {
border: none;
border-radius: .75rem;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.restaurant-card-new:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15) !important;
}
.restaurant-card-new .card-link {
text-decoration: none;
color: inherit;
display: block;
}
.restaurant-card-new .img-container {
height: 200px;
overflow: hidden;
}
.restaurant-card-new .card-img-top {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.restaurant-card-new:hover .card-img-top {
transform: scale(1.05);
}
.restaurant-card-new .card-body {
padding: 1.25rem;
}
.restaurant-card-new .card-footer {
padding-top: 0;
}
.restaurant-card-new .btn-sm {
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
}
/* Menu Page Styles */
.restaurant-hero-menu {
height: 50vh;
background-size: cover;
background-position: center;
position: relative;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: white;
}
.restaurant-hero-menu::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.restaurant-hero-menu-content {
position: relative;
z-index: 1;
}
.menu-item-card-new {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: none;
border-radius: .75rem;
overflow: hidden;
}
.menu-item-card-new:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
.menu-item-card-new .img-fluid {
height: 100%;
object-fit: cover;
}
.reviews-section .review-card:last-child {
margin-bottom: 0 !important;
}
.reviews-section hr:last-of-type {
display: none;
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#007bff" width="38px" height="38px"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>

After

Width:  |  Height:  |  Size: 274 B

BIN
assets/images/hero.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#dc3545" width="38px" height="38px"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>

After

Width:  |  Height:  |  Size: 274 B

1
assets/images/step1.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="#ff5a5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>

After

Width:  |  Height:  |  Size: 281 B

1
assets/images/step2.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="#ff5a5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>

After

Width:  |  Height:  |  Size: 332 B

1
assets/images/step3.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="#ff5a5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 17.5V14H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v1.5"></path><path d="M18 8h1a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-1"></path><path d="M22 12l-3 3-3-3"></path><path d="M19 15V3"></path></svg>

After

Width:  |  Height:  |  Size: 383 B

23
assets/js/main.js Normal file
View File

@ -0,0 +1,23 @@
document.addEventListener('DOMContentLoaded', () => {
const themeSwitch = document.querySelector('#checkbox');
const body = document.body;
const currentTheme = localStorage.getItem('theme');
if (currentTheme) {
body.classList.add(currentTheme);
if (currentTheme === 'dark-mode') {
themeSwitch.checked = true;
}
}
themeSwitch.addEventListener('change', () => {
if (themeSwitch.checked) {
body.classList.add('dark-mode');
localStorage.setItem('theme', 'dark-mode');
} else {
body.classList.remove('dark-mode');
localStorage.setItem('theme', 'light-mode');
}
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

15
auth_google.php Normal file
View File

@ -0,0 +1,15 @@
<?php
session_start();
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/includes/api_keys.php';
$client = new Google_Client();
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/auth_google_callback.php');
$client->addScope("email");
$client->addScope("profile");
header('Location: ' . $client->createAuthUrl());
exit();

53
auth_google_callback.php Normal file
View File

@ -0,0 +1,53 @@
<?php
session_start();
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/includes/api_keys.php';
require_once __DIR__ . '/db/config.php';
$client = new Google_Client();
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/auth_google_callback.php');
if (isset($_GET['code'])) {
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$client->setAccessToken($token['access_token']);
$google_oauth = new Google_Service_Oauth2($client);
$google_account_info = $google_oauth->userinfo->get();
$email = $google_account_info->email;
$name = $google_account_info->name;
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user) {
// User exists, log them in
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['is_admin'] = $user['is_admin'];
$_SESSION['role'] = $user['role'];
header('Location: index.php');
exit();
} else {
// User doesn't exist, create a new account
$stmt = $pdo->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)");
// We can't store an empty password, so we generate a random one.
$random_password = bin2hex(random_bytes(16));
$stmt->execute([$name, $email, password_hash($random_password, PASSWORD_DEFAULT), 'customer']);
$user_id = $pdo->lastInsertId();
$_SESSION['user_id'] = $user_id;
$_SESSION['user_name'] = $name;
$_SESSION['is_admin'] = 0;
$_SESSION['role'] = 'customer';
header('Location: index.php');
exit();
}
} else {
header('Location: login.php?error=Google login failed');
exit();
}

125
cart.php Normal file
View File

@ -0,0 +1,125 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'header.php';
// Fetch cart items
$cart_items = [];
$total_quantity = 0;
$subtotal = 0;
$total_bill = 0;
$user_id = $_SESSION['user_id'] ?? null;
$session_id = session_id();
if ($user_id) {
$stmt = db()->prepare("
SELECT c.menu_item_id, c.quantity, mi.name, mi.price, mi.image_url, r.name as restaurant_name, r.id as restaurant_id
FROM cart c
JOIN menu_items mi ON c.menu_item_id = mi.id
JOIN restaurants r ON c.restaurant_id = r.id
WHERE c.user_id = ?
");
$stmt->execute([$user_id]);
$cart_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$stmt = db()->prepare("
SELECT c.menu_item_id, c.quantity, mi.name, mi.price, mi.image_url, r.name as restaurant_name, r.id as restaurant_id
FROM cart c
JOIN menu_items mi ON c.menu_item_id = mi.id
JOIN restaurants r ON c.restaurant_id = r.id
WHERE c.session_id = ?
");
$stmt->execute([$session_id]);
$cart_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if ($cart_items) {
foreach ($cart_items as $item) {
$total_quantity += $item['quantity'];
$subtotal += $item['price'] * $item['quantity'];
}
$total_bill = $subtotal; // For now, total is same as subtotal. Can add taxes/fees later.
}
?>
<div class="container my-5">
<h1 class="display-5 fw-bold mb-4">Your Shopping Cart</h1>
<?php if (empty($cart_items)): ?>
<div class="text-center py-5">
<i class="fas fa-shopping-cart fa-4x text-muted mb-3"></i>
<h3 class="text-muted">Your cart is empty.</h3>
<p class="text-muted">Looks like you haven't added anything to your cart yet.</p>
<a href="restaurants.php" class="btn btn-primary mt-3">Continue Shopping</a>
</div>
<?php else: ?>
<div class="row">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-body">
<?php foreach ($cart_items as $item): ?>
<div class="row align-items-center mb-4">
<div class="col-md-2">
<img src="<?php echo htmlspecialchars($item['image_url'] ?: 'https://via.placeholder.com/150'); ?>" class="img-fluid rounded" alt="<?php echo htmlspecialchars($item['name']); ?>">
</div>
<div class="col-md-4">
<h5 class="mb-0"><?php echo htmlspecialchars($item['name']); ?></h5>
<small class="text-muted">From: <a href="menu.php?restaurant_id=<?php echo $item['restaurant_id']; ?>"><?php echo htmlspecialchars($item['restaurant_name']); ?></a></small>
</div>
<div class="col-md-3">
<form action="cart_actions.php" method="post" class="d-flex align-items-center">
<input type="hidden" name="action" value="update">
<input type="hidden" name="menu_item_id" value="<?php echo $item['menu_item_id']; ?>">
<button type="submit" name="quantity" value="<?php echo $item['quantity'] - 1; ?>" class="btn btn-outline-secondary btn-sm" <?php echo $item['quantity'] <= 1 ? 'disabled' : ''; ?>>-</button>
<input type="text" class="form-control form-control-sm text-center mx-2" value="<?php echo $item['quantity']; ?>" readonly style="width: 50px;">
<button type="submit" name="quantity" value="<?php echo $item['quantity'] + 1; ?>" class="btn btn-outline-secondary btn-sm">+</button>
</form>
</div>
<div class="col-md-2 text-end">
<span class="fw-bold">$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
</div>
<div class="col-md-1 text-end">
<form action="cart_actions.php" method="post">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="menu_item_id" value="<?php echo $item['menu_item_id']; ?>">
<button type="submit" class="btn btn-link text-danger p-0"><i class="fas fa-trash"></i></button>
</form>
</div>
</div>
<?php if (next($cart_items)): ?>
<hr>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="card-title mb-4">Order Summary</h4>
<div class="d-flex justify-content-between mb-2">
<span>Total Items:</span>
<span><?php echo $total_quantity; ?></span>
</div>
<div class="d-flex justify-content-between mb-3">
<span>Subtotal:</span>
<span>$<?php echo number_format($subtotal, 2); ?></span>
</div>
<hr>
<div class="d-flex justify-content-between fw-bold fs-5">
<span>Total Bill:</span>
<span>$<?php echo number_format($total_bill, 2); ?></span>
</div>
<div class="d-grid gap-2 mt-4">
<a href="checkout.php" class="btn btn-primary btn-lg">Proceed to Checkout</a>
<a href="restaurants.php" class="btn btn-outline-secondary">Continue Shopping</a>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php require_once 'footer.php'; ?>

81
cart_actions.php Normal file
View File

@ -0,0 +1,81 @@
<?php
session_start();
require_once 'db/config.php';
// Default redirect location
$redirect_location = 'cart.php';
// Determine action, prioritizing POST
$action = $_POST['action'] ?? $_GET['action'] ?? null;
if (!$action) {
header('Location: index.php');
exit;
}
$user_id = $_SESSION['user_id'] ?? null;
$session_id = session_id();
$pdoconnection = db();
// --- ADD TO CART ---
if ($action == 'add' && isset($_POST['menu_item_id'], $_POST['quantity'])) {
$menu_item_id = (int)$_POST['menu_item_id'];
$quantity = (int)$_POST['quantity'];
if ($quantity > 0 && $menu_item_id > 0) {
// Check if item is already in cart
$sql = "SELECT id, quantity FROM cart WHERE menu_item_id = :menu_item_id AND " . ($user_id ? "user_id = :identifier" : "session_id = :identifier");
$stmt = $pdoconnection->prepare($sql);
$identifier = $user_id ?: $session_id;
$stmt->execute([':menu_item_id' => $menu_item_id, ':identifier' => $identifier]);
$existing_item = $stmt->fetch();
if ($existing_item) {
// Update quantity
$new_quantity = $existing_item['quantity'] + $quantity;
$stmt = $pdoconnection->prepare("UPDATE cart SET quantity = :quantity WHERE id = :id");
$stmt->execute([':quantity' => $new_quantity, ':id' => $existing_item['id']]);
} else {
// Insert new item
$stmt = $pdoconnection->prepare(
"INSERT INTO cart (user_id, session_id, menu_item_id, quantity) VALUES (:user_id, :session_id, :menu_item_id, :quantity)"
);
$stmt->execute([
':user_id' => $user_id,
':session_id' => $user_id ? null : $session_id,
':menu_item_id' => $menu_item_id,
':quantity' => $quantity
]);
}
}
// Redirect back to the menu page after adding an item
if (isset($_POST['restaurant_id']) && is_numeric($_POST['restaurant_id'])) {
$redirect_location = 'menu.php?id=' . $_POST['restaurant_id'];
}
// --- UPDATE CART ---
} elseif ($action == 'update' && isset($_POST['cart_id'], $_POST['quantity'])) {
$cart_id = (int)$_POST['cart_id'];
$quantity = (int)$_POST['quantity'];
if ($quantity > 0 && $cart_id > 0) {
$sql = "UPDATE cart SET quantity = :quantity WHERE id = :cart_id AND " . ($user_id ? "user_id = :identifier" : "session_id = :identifier");
$stmt = $pdoconnection->prepare($sql);
$identifier = $user_id ?: $session_id;
$stmt->execute([':quantity' => $quantity, ':cart_id' => $cart_id, ':identifier' => $identifier]);
}
// --- REMOVE FROM CART ---
} elseif ($action == 'remove' && isset($_POST['cart_id'])) {
$cart_id = (int)$_POST['cart_id'];
if ($cart_id > 0) {
$sql = "DELETE FROM cart WHERE id = :cart_id AND " . ($user_id ? "user_id = :identifier" : "session_id = :identifier");
$stmt = $pdoconnection->prepare($sql);
$identifier = $user_id ?: $session_id;
$stmt->execute([':cart_id' => $cart_id, ':identifier' => $identifier]);
}
}
header('Location: ' . $redirect_location);
exit;

280
checkout.php Normal file
View File

@ -0,0 +1,280 @@
<?php
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();
$pdo = db();
$user = [];
if (!$is_guest) {
$userStmt = $pdo->prepare("SELECT name, email, address, phone FROM users WHERE id = ?");
$userStmt->execute([$user_id]);
$user = $userStmt->fetch(PDO::FETCH_ASSOC);
}
// Fetch cart items
if (!$is_guest) {
$stmt = $pdo->prepare("SELECT c.id, mi.name, mi.price, c.quantity, r.name as restaurant_name, r.id as restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id JOIN restaurants r ON mi.restaurant_id = r.id WHERE c.user_id = :user_id");
$stmt->bindParam(':user_id', $user_id);
} else {
$stmt = $pdo->prepare("SELECT c.id, mi.name, mi.price, c.quantity, r.name as restaurant_name, r.id as restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id JOIN restaurants r ON mi.restaurant_id = r.id WHERE c.session_id = :session_id");
$stmt->bindParam(':session_id', $session_id);
}
$stmt->execute();
$cartItems = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($cartItems)) {
header("Location: cart.php");
exit();
}
$subtotal = 0;
foreach ($cartItems as $item) {
$subtotal += $item['price'] * $item['quantity'];
}
$settingsStmt = $pdo->query("SELECT name, value FROM settings WHERE name IN ('delivery_fee', 'service_fee_percentage')");
$settings = $settingsStmt->fetchAll(PDO::FETCH_KEY_PAIR);
$delivery_fee = $settings['delivery_fee'] ?? 0;
$service_fee_percentage = $settings['service_fee_percentage'] ?? 0;
$service_fee = ($subtotal * $service_fee_percentage) / 100;
$discount_amount = $_SESSION['discount_amount'] ?? 0;
$totalPrice = $subtotal + $delivery_fee + $service_fee - $discount_amount;
$_SESSION['total_price'] = $totalPrice;
include 'header.php';
?>
<script src="https://js.stripe.com/v3/"></script>
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo $paypalClientId; ?>&currency=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">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" fill="currentColor"/><path d="M12 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z" fill="currentColor"/><path d="M12 14c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/></svg>
<span>Food Delivery</span>
</a>
</div>
<div id="delivery-step">
<h3 class="step-title">1. Delivery Details</h3>
<form id="delivery-form">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" class="form-control" value="<?php echo htmlspecialchars($user['name'] ?? ''); ?>" required>
</div>
<?php if ($is_guest): ?>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<?php endif; ?>
<div class="form-group">
<label for="address">Delivery Address</label>
<input type="text" id="address" name="address" class="form-control" value="<?php echo htmlspecialchars($user['address'] ?? ''); ?>" required>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone" class="form-control" value="<?php echo htmlspecialchars($user['phone'] ?? ''); ?>" required>
</div>
<button type="button" id="to-payment-btn" class="btn-primary">Continue to Payment</button>
</form>
</div>
<div id="payment-step" style="display: none;">
<h3 class="step-title">2. Payment Method</h3>
<div class="payment-methods">
<div class="payment-method-card" data-method="stripe">
<input type="radio" id="stripe-radio" name="payment_method" value="stripe" checked>
<label for="stripe-radio">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="M42,12H6A2,2,0,0,0,4,14V34a2,2,0,0,0,2,2H42a2,2,0,0,0,2-2V14A2,2,0,0,0,42,12ZM6,16H42v4H6Zm0,16V24H42v8Z" fill="#000"/><rect x="10" y="28" width="8" height="4" fill="#000"/></svg>
<span>Credit or Debit Card</span>
</label>
</div>
<div class="payment-method-card" data-method="paypal">
<input type="radio" id="paypal-radio" name="payment_method" value="paypal">
<label for="paypal-radio">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="M24,4A20,20,0,1,0,44,24,20,20,0,0,0,24,4Zm11.2,9.45a.7.7,0,0,1,.6,1l-4.6,17.5a.7.7,0,0,1-1.3.1L24,24.25l-5.9,7.8a.7.7,0,0,1-1.1-.9l4.6-17.5a.7.7,0,0,1,1.3-.1L24,19.75l5.9-7.8A.7.7,0,0,1,35.2,13.45Z" fill="#000"/></svg>
<span>PayPal</span>
</label>
</div>
</div>
<form id="payment-form" action="create_stripe_session.php" method="POST">
<input type="hidden" name="name" id="hidden_name">
<input type="hidden" name="email" id="hidden_email">
<input type="hidden" name="address" id="hidden_address">
<input type="hidden" name="phone" id="hidden_phone">
<button id="stripe-button" class="btn-primary">Pay with Stripe</button>
</form>
<div id="paypal-button-container" style="display: none;"></div>
<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>
<div class="summary-items">
<?php foreach ($cartItems as $item): ?>
<div class="summary-item">
<span class="item-name"><?php echo htmlspecialchars($item['name']); ?> (x<?php echo $item['quantity']; ?>)</span>
<span class="item-price">$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
</div>
<?php endforeach; ?>
</div>
<div class="summary-total">
<div class="summary-line">
<span>Subtotal</span>
<span>$<?php echo number_format($subtotal, 2); ?></span>
</div>
<div class="summary-line">
<span>Delivery Fee</span>
<span>$<?php echo number_format($delivery_fee, 2); ?></span>
</div>
<div class="summary-line">
<span>Service Fee</span>
<span>$<?php echo number_format($service_fee, 2); ?></span>
</div>
<?php if ($discount_amount > 0): ?>
<div class="summary-line discount">
<span>Discount</span>
<span>-$<?php echo number_format($discount_amount, 2); ?></span>
</div>
<?php endif; ?>
<div class="summary-line total">
<span>Total</span>
<span>$<?php echo number_format($totalPrice, 2); ?></span>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const deliveryStep = document.getElementById('delivery-step');
const paymentStep = document.getElementById('payment-step');
const toPaymentBtn = document.getElementById('to-payment-btn');
const backToDeliveryBtn = document.getElementById('back-to-delivery-btn');
const deliveryForm = document.getElementById('delivery-form');
const stripeRadio = document.getElementById('stripe-radio');
const paypalRadio = document.getElementById('paypal-radio');
const stripeButton = document.getElementById('stripe-button');
const paypalButtonContainer = document.getElementById('paypal-button-container');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const addressInput = document.getElementById('address');
const phoneInput = document.getElementById('phone');
const hiddenName = document.getElementById('hidden_name');
const hiddenEmail = document.getElementById('hidden_email');
const hiddenAddress = document.getElementById('hidden_address');
const hiddenPhone = document.getElementById('hidden_phone');
toPaymentBtn.addEventListener('click', () => {
if (deliveryForm.checkValidity()) {
// Copy values to hidden fields for Stripe form
hiddenName.value = nameInput.value;
if (emailInput) {
hiddenEmail.value = emailInput.value;
}
hiddenAddress.value = addressInput.value;
hiddenPhone.value = phoneInput.value;
deliveryStep.style.display = 'none';
paymentStep.style.display = 'block';
} else {
deliveryForm.reportValidity();
}
});
backToDeliveryBtn.addEventListener('click', () => {
paymentStep.style.display = 'none';
deliveryStep.style.display = 'block';
});
function togglePaymentButtons() {
if (stripeRadio.checked) {
stripeButton.style.display = 'block';
paypalButtonContainer.style.display = 'none';
} else {
stripeButton.style.display = 'none';
paypalButtonContainer.style.display = 'block';
}
}
stripeRadio.addEventListener('change', togglePaymentButtons);
paypalRadio.addEventListener('change', togglePaymentButtons);
document.querySelectorAll('.payment-method-card').forEach(card => {
card.addEventListener('click', () => {
card.querySelector('input[type="radio"]').checked = true;
togglePaymentButtons();
});
});
togglePaymentButtons();
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '<?php echo number_format($totalPrice, 2, '.', ''); ?>'
}
}]
});
},
onApprove: function(data, actions) {
const formData = new FormData();
formData.append('orderID', data.orderID);
formData.append('name', nameInput.value);
if (emailInput) {
formData.append('email', emailInput.value);
}
formData.append('address', addressInput.value);
formData.append('phone', phoneInput.value);
fetch('paypal-capture.php', {
method: 'POST',
body: formData
}).then(res => res.json())
.then(details => {
if (details.error) {
alert(details.error);
window.location.href = 'payment-cancel.php';
} else {
window.location.href = 'order_confirmation.php?order_id=' + details.order_id;
}
});
},
onError: function(err) {
console.error('PayPal Error:', err);
alert('An error occurred with your PayPal payment.');
}
}).render('#paypal-button-container');
});
</script>
<?php include 'footer.php'; ?>

7
composer.json Normal file
View File

@ -0,0 +1,7 @@
{
"require": {
"stripe/stripe-php": "^18.0",
"google/apiclient": "^2.0",
"aws/aws-sdk-php": "^2.8"
}
}

1573
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

61
create_stripe_session.php Normal file
View File

@ -0,0 +1,61 @@
<?php
session_start();
require_once 'vendor/autoload.php';
require_once 'db/config.php';
require_once 'includes/api_keys.php';
\Stripe\Stripe::setApiKey($stripeSecretKey);
$total_price = $_SESSION['total_price'] ?? 0;
$coupon_id = $_SESSION['coupon_id'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
$is_guest = !$user_id;
if ($total_price <= 0) {
header("Location: cart.php");
exit();
}
$metadata = [
'coupon_id' => $coupon_id
];
$customer_email = null;
if ($is_guest) {
$token = bin2hex(random_bytes(16));
$metadata['token'] = $token;
$metadata['guest_name'] = $_POST['name'] ?? '';
$metadata['guest_email'] = $_POST['email'] ?? '';
$metadata['guest_address'] = $_POST['address'] ?? '';
$metadata['guest_phone'] = $_POST['phone'] ?? '';
$customer_email = $_POST['email'] ?? null;
} else {
$metadata['user_id'] = $user_id;
}
$checkout_session_params = [
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'usd',
'product_data' => [
'name' => 'Total Order Amount',
],
'unit_amount' => $total_price * 100, // Amount in cents
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' => 'http://localhost:8080/payment-success.php?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'http://localhost:8080/payment-cancel.php',
'metadata' => $metadata
];
if ($customer_email) {
$checkout_session_params['customer_email'] = $customer_email;
}
$checkout_session = \Stripe\Checkout\Session::create($checkout_session_params);
header("Location: " . $checkout_session->url);

View File

@ -1,17 +1,20 @@
<?php <?php
// Generated by setup_mariadb_project.sh — edit as needed. define('DB_DRIVER', 'pgsql');
define('DB_HOST', '127.0.0.1'); define('DB_HOST', 'aws-1-ap-southeast-2.pooler.supabase.com');
define('DB_NAME', 'app_30953'); define('DB_PORT', '6543');
define('DB_USER', 'app_30953'); define('DB_NAME', 'postgres');
define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472'); define('DB_USER', 'postgres.ysctoqmvvyshlkkfxsgq');
define('DB_PASS', 'qokc12eVuQSlYbew');
function db() { function db() {
static $pdo; static $pdo;
if (!$pdo) { if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [ $dsn = DB_DRIVER . ':host=' . DB_HOST . ';port=' . DB_PORT . ';dbname=' . DB_NAME;
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]); ]);
} }
return $pdo; return $pdo;
} }

18
db/run_migration.php Normal file
View File

@ -0,0 +1,18 @@
<?php
require_once 'config.php';
$migration_file = end($argv);
if (!file_exists($migration_file)) {
die("Migration file not found: $migration_file");
}
$sql = file_get_contents($migration_file);
$db = db();
try {
$db->exec($sql);
echo "Migration successful: $migration_file\n";
} catch (PDOException $e) {
die("Migration failed: " . $e->getMessage() . "\n");
}

59
driver/accept_job.php Normal file
View File

@ -0,0 +1,59 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
if (!isset($_SESSION['driver_id'])) {
header('Location: /driver/login.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$order_id = $_POST['order_id'];
$driver_id = $_SESSION['driver_id'];
if (empty($order_id)) {
header('Location: index.php?error=Invalid order.');
exit;
}
$pdo = db();
try {
$pdo->beginTransaction();
// Check if the order is still available
$check_stmt = $pdo->prepare('SELECT driver_id FROM orders WHERE id = ? AND status = \'ready for pickup\'');
$check_stmt->execute([$order_id]);
$order = $check_stmt->fetch();
if (!$order || $order['driver_id'] !== null) {
header('Location: index.php?error=Order is no longer available.');
$pdo->rollBack();
exit;
}
// Assign driver and update status
$update_stmt = $pdo->prepare('UPDATE orders SET driver_id = ?, status = \'out for delivery\' WHERE id = ?');
$update_stmt->execute([$driver_id, $order_id]);
// Create driver assignment record
$assign_stmt = $pdo->prepare('INSERT INTO driver_assignments (order_id, driver_id) VALUES (?, ?)');
$assign_stmt->execute([$order_id, $driver_id]);
$pdo->commit();
header('Location: index.php?success=Order accepted successfully!');
exit;
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
// Log the error properly in a real application
header('Location: index.php?error=An error occurred. Please try again.');
exit;
}
} else {
header('Location: index.php');
exit;
}

2
driver/footer.php Normal file
View File

@ -0,0 +1,2 @@
</body>
</html>

27
driver/header.php Normal file
View File

@ -0,0 +1,27 @@
<?php
session_start();
// If driver is not logged in, redirect to login page
if (!isset($_SESSION['driver_id'])) {
header('Location: /driver/login.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Driver Dashboard - Majuro Eats</title>
<link rel="stylesheet" href="../assets/css/main.css?v=<?php echo time(); ?>">
</head>
<body>
<header>
<a href="/driver/" class="logo">Majuro Eats - Driver Portal</a>
<div class="user-actions">
<?php if (isset($_SESSION['driver_id'])): ?>
<span>Welcome, <?php echo htmlspecialchars($_SESSION['driver_name']); ?></span>
<a href="logout.php">Logout</a>
<?php endif; ?>
</div>
</header>

198
driver/index.php Normal file
View File

@ -0,0 +1,198 @@
<?php
include 'header.php';
require_once __DIR__ . '/../db/config.php';
$driver_id = $_SESSION['driver_id'];
$pdo = db();
$stmt = $pdo->prepare(
'SELECT ' .
'o.id as order_id, ' .
'o.status as order_status, ' .
'o.delivery_address, ' .
'u.name as customer_name, ' .
'r.name as restaurant_name, ' .
'r.address as restaurant_address ' .
'FROM orders o ' .
'JOIN driver_assignments da ON o.id = da.order_id ' .
'JOIN users u ON o.user_id = u.id ' .
'JOIN restaurants r ON o.restaurant_id = r.id ' .
'WHERE da.driver_id = ? ' .
'ORDER BY o.created_at DESC'
);
$stmt->execute([$driver_id]);
$assigned_orders = $stmt->fetchAll();
// Get available orders
$available_stmt = $pdo->prepare(
'SELECT ' .
'o.id as order_id, ' .
'o.delivery_address, ' .
'r.name as restaurant_name, ' .
'r.address as restaurant_address ' .
'FROM orders o ' .
'JOIN restaurants r ON o.restaurant_id = r.id ' .
'WHERE o.status = "ready for pickup" AND o.driver_id IS NULL ' .
'ORDER BY o.created_at ASC'
);
$available_stmt->execute();
$available_orders = $available_stmt->fetchAll();
$order_statuses = ['out for delivery', 'picked up', 'delivered'];
?>
<main class="container">
<div class="available-jobs">
<h2>Available Jobs</h2>
<?php if (empty($available_orders)): ?>
<p>No jobs available at the moment.</p>
<?php else: ?>
<div class="order-list">
<?php foreach ($available_orders as $order): ?>
<div class="order-card">
<h3>Order #<?php echo htmlspecialchars($order['order_id']); ?></h3>
<p><strong>Restaurant:</strong> <?php echo htmlspecialchars($order['restaurant_name']); ?></p>
<p><strong>Restaurant Address:</strong> <?php echo htmlspecialchars($order['restaurant_address']); ?></p>
<p><strong>Delivery Address:</strong> <?php echo htmlspecialchars($order['delivery_address']); ?></p>
<form action="accept_job.php" method="POST">
<input type="hidden" name="order_id" value="<?php echo $order['order_id']; ?>">
<button type="submit" class="btn-submit">Accept Job</button>
</form>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<hr>
<h1>My Assigned Deliveries</h1>
<?php if (isset($_GET['success'])): ?>
<p class="success-message"><?php echo htmlspecialchars($_GET['success']); ?></p>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<p class="error-message"><?php echo htmlspecialchars($_GET['error']); ?></p>
<?php endif; ?>
<div class="order-list">
<?php if (empty($assigned_orders)): ?>
<p>You have no assigned orders at the moment.</p>
<?php else: ?>
<?php foreach ($assigned_orders as $order): ?>
<div class="order-card">
<h3>Order #<?php echo htmlspecialchars($order['order_id']); ?></h3>
<p><strong>Status:</strong> <?php echo htmlspecialchars(ucwords($order['order_status'])); ?></p>
<hr>
<p><strong>Customer:</strong> <?php echo htmlspecialchars($order['customer_name']); ?></p>
<p><strong>Delivery Address:</strong> <?php echo htmlspecialchars($order['delivery_address']); ?></p>
<hr>
<p><strong>Restaurant:</strong> <?php echo htmlspecialchars($order['restaurant_name']); ?></p>
<p><strong>Restaurant Address:</strong> <?php echo htmlspecialchars($order['restaurant_address']); ?></p>
<form action="update_order_status.php" method="POST" class="status-update-form">
<input type="hidden" name="order_id" value="<?php echo $order['order_id']; ?>">
<div class="form-group">
<label for="status-<?php echo $order['order_id']; ?>">Update Status:</label>
<select name="status" id="status-<?php echo $order['order_id']; ?>">
<?php foreach ($order_statuses as $status): ?>
<option value="<?php echo $status; ?>" <?php echo ($order['order_status'] === $status) ? 'selected' : ''; ?>>
<?php echo ucwords($status); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn-submit">Update</button>
</form>
<div class="delivery-controls">
<button class="btn-start-delivery" data-order-id="<?php echo $order['order_id']; ?>">Start Delivery</button>
<button class="btn-stop-delivery" data-order-id="<?php echo $order['order_id']; ?>" style="display: none;">Stop Delivery</button>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', () => {
const locationWatchers = {};
document.querySelectorAll('.btn-start-delivery').forEach(button => {
button.addEventListener('click', () => {
const orderId = button.dataset.orderId;
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser.');
return;
}
button.disabled = true;
document.querySelector(`.btn-stop-delivery[data-order-id="${orderId}"]`).style.display = 'inline-block';
const watcherId = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude } = position.coords;
sendLocation(orderId, latitude, longitude);
},
(error) => {
console.error('Error getting location:', error);
alert('Error getting your location. Please ensure you have enabled location services.');
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
locationWatchers[orderId] = watcherId;
});
});
document.querySelectorAll('.btn-stop-delivery').forEach(button => {
button.addEventListener('click', () => {
const orderId = button.dataset.orderId;
if (locationWatchers[orderId]) {
navigator.geolocation.clearWatch(locationWatchers[orderId]);
delete locationWatchers[orderId];
button.style.display = 'none';
document.querySelector(`.btn-start-delivery[data-order-id="${orderId}"]`).disabled = false;
alert('Delivery stopped.');
}
});
});
function sendLocation(orderId, lat, lng) {
fetch('/api/save_location.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: orderId,
lat: lat,
lng: lng
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log(`Location updated for order ${orderId}`);
} else {
console.error('Failed to update location:', data.error);
}
})
.catch(error => {
console.error('Error sending location:', error);
});
}
});
</script>
<?php include 'footer.php'; ?>

30
driver/login.php Normal file
View File

@ -0,0 +1,30 @@
<?php
session_start();
if (isset($_SESSION['driver_id'])) {
header('Location: /driver/index.php');
exit;
}
include 'header.php';
?>
<main>
<div class="auth-container">
<h1>Driver Login</h1>
<?php if (isset($_GET['error'])): ?>
<p class="error-message"><?php echo htmlspecialchars($_GET['error']); ?></p>
<?php endif; ?>
<form action="login_process.php" method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn-submit">Login</button>
</form>
</div>
</main>
<?php include 'footer.php'; ?>

50
driver/login_process.php Normal file
View File

@ -0,0 +1,50 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$email = $_POST['email'];
$password = $_POST['password'];
if (empty($email) || empty($password)) {
header("Location: login.php?error=Please fill all fields");
exit;
}
try {
$pdo = db();
$sql = "SELECT id, full_name, email, password_hash, approval_status FROM drivers WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
$driver = $stmt->fetch();
if ($driver) {
if ($driver['approval_status'] === 'pending') {
header("Location: ../driver_pending_approval.php");
exit;
} elseif ($driver['approval_status'] === 'rejected') {
header("Location: ../driver_rejected.php");
exit;
}
if ($driver['approval_status'] === 'approved' && password_verify($password, $driver['password_hash'])) {
$_SESSION['driver_id'] = $driver['id'];
$_SESSION['driver_name'] = $driver['full_name'];
$_SESSION['role'] = 'driver';
header("Location: index.php");
exit;
} else {
header("Location: login.php?error=Invalid credentials");
exit;
}
} else {
header("Location: login.php?error=Invalid credentials");
exit;
}
} catch (PDOException $e) {
header("Location: login.php?error=A database error occurred");
exit;
}
}
?>

7
driver/logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php');
exit;
?>

View File

@ -0,0 +1,83 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
// Ensure driver is logged in
if (!isset($_SESSION['driver_id'])) {
header('Location: login.php');
exit;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$order_id = $_POST['order_id'];
$status = $_POST['status'];
$driver_id = $_SESSION['driver_id'];
$allowed_statuses = ['out for delivery', 'picked up', 'delivered'];
if (empty($order_id) || empty($status) || !in_array($status, $allowed_statuses)) {
header('Location: index.php?error=Invalid input.');
exit;
}
try {
$pdo = db();
// Security Check: Verify the order is assigned to this driver
$check_stmt = $pdo->prepare(
'SELECT o.id FROM orders o ' .
'JOIN driver_assignments da ON o.id = da.order_id ' .
'WHERE o.id = ? AND da.driver_id = ?'
);
$check_stmt->execute([$order_id, $driver_id]);
$assignment = $check_stmt->fetch();
if (!$assignment) {
header('Location: index.php?error=You are not authorized to update this order.');
exit;
}
// Update the order status
$update_stmt = $pdo->prepare('UPDATE orders SET status = ? WHERE id = ?');
if ($update_stmt->execute([$status, $order_id])) {
// Notify customer by email
require_once __DIR__ . '/../mail/MailService.php';
$user_stmt = $pdo->prepare('SELECT u.email, u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE o.id = ?');
$user_stmt->execute([$order_id]);
$customer = $user_stmt->fetch();
if ($customer) {
$subject = '';
$body = '';
if ($status === 'picked up') {
$subject = 'Your order is on its way!';
$body = 'Hi ' . $customer['name'] . ',<br><br>Good news! Your order #' . $order_id . ' has been picked up by your driver and is on its way to you.<br><br>Thanks for using Majuro Eats!';
} elseif ($status === 'delivered') {
$subject = 'Your order has been delivered!';
$body = 'Hi ' . $customer['name'] . ',<br><br>Your order #' . $order_id . ' has been delivered. We hope you enjoy your meal!<br><br>Thanks for using Majuro Eats!';
}
if ($subject && $body) {
MailService::sendMail($customer['email'], $subject, $body, $body);
}
}
header('Location: index.php?success=Order status updated successfully.');
exit;
}
} else {
header('Location: index.php?error=Failed to update order status.');
exit;
}
} catch (PDOException $e) {
header('Location: index.php?error=A database error occurred.');
exit;
}
} else {
header('Location: index.php');
exit;
}
?>

View File

@ -0,0 +1,12 @@
<?php include 'header.php'; ?>
<main>
<div class="container mt-4">
<h1>Application Pending Approval</h1>
<p>Thank you for signing up to be a driver. Your application is currently under review.</p>
<p>We will notify you by email once your application has been approved.</p>
<p><a href="logout.php">Logout</a></p>
</div>
</main>
<?php include 'footer.php'; ?>

12
driver_rejected.php Normal file
View File

@ -0,0 +1,12 @@
<?php include 'header.php'; ?>
<main>
<div class="container mt-4">
<h1>Application Rejected</h1>
<p>We regret to inform you that your application to become a driver has been rejected.</p>
<p>If you believe this is a mistake, please contact our support team.</p>
<p><a href="logout.php">Logout</a></p>
</div>
</main>
<?php include 'footer.php'; ?>

169
driver_signup.php Normal file
View File

@ -0,0 +1,169 @@
<?php
require_once __DIR__ . '/db/config.php';
$db = db();
$stmt = $db->prepare("SELECT id, document_name FROM required_documents WHERE applies_to = 'driver'");
$stmt->execute();
$required_documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
include 'header.php';
?>
<style>
.driver-signup-container {
display: flex;
min-height: 80vh;
align-items: stretch;
background-color: #f8f9fa;
}
.driver-signup-promo {
flex: 1;
background: url('assets/images/hero.jpg') no-repeat center center;
background-size: cover;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px;
}
.driver-signup-promo h1 {
font-size: 3rem;
font-weight: bold;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.7);
}
.driver-signup-promo p {
font-size: 1.25rem;
margin-top: 10px;
text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.7);
}
.driver-signup-promo .benefits {
margin-top: 30px;
list-style: none;
padding: 0;
}
.driver-signup-promo .benefits li {
font-size: 1.1rem;
margin-bottom: 10px;
background-color: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
}
.driver-signup-form-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
}
.driver-signup-form {
width: 100%;
max-width: 450px;
}
.driver-signup-form h2 {
font-size: 2rem;
margin-bottom: 20px;
color: #333;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.btn-submit {
width: 100%;
padding: 12px;
background-color: #ff8c00;
color: white;
border: none;
border-radius: 5px;
font-size: 1.1rem;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-submit:hover {
background-color: #e07b00;
}
.form-footer {
margin-top: 20px;
text-align: center;
}
</style>
<div class="driver-signup-container">
<div class="driver-signup-promo">
<h1>Drive with MajuroEats</h1>
<p>Earn money on your own schedule.</p>
<ul class="benefits">
<li> Flexible Hours</li>
<li> Weekly Payments</li>
<li> Be Your Own Boss</li>
</ul>
</div>
<div class="driver-signup-form-container">
<div class="driver-signup-form">
<h2>Create Your Driver Account</h2>
<form action="driver_signup_process.php" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="full_name">Full Name</label>
<input type="text" id="full_name" name="full_name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="phone_number">Phone Number</label>
<input type="text" id="phone_number" name="phone_number" required>
</div>
<div class="form-group">
<label for="vehicle_details">Vehicle Details (e.g., 2023 Toyota Camry, Blue)</label>
<input type="text" id="vehicle_details" name="vehicle_details" required>
</div>
<?php foreach ($required_documents as $doc): ?>
<div class="form-group">
<label for="doc_<?php echo htmlspecialchars($doc['id']); ?>"><?php echo htmlspecialchars($doc['document_name']); ?></label>
<input type="file" id="doc_<?php echo htmlspecialchars($doc['id']); ?>" name="documents[<?php echo htmlspecialchars($doc['id']); ?>]" class="form-control" required>
</div>
<?php endforeach; ?>
<button type="submit" class="btn-submit">Start Earning</button>
</form>
<div class="form-footer">
<p>Already have an account? <a href="login.php">Log in</a></p>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>

98
driver_signup_process.php Normal file
View File

@ -0,0 +1,98 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'includes/S3Service.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$full_name = $_POST['full_name'];
$email = $_POST['email'];
$password = $_POST['password'];
$phone_number = $_POST['phone_number'];
$vehicle_details = $_POST['vehicle_details'];
if (empty($full_name) || empty($email) || empty($password) || empty($phone_number) || empty($vehicle_details)) {
die('Please fill all required fields.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Invalid email format.');
}
$pdo = db();
try {
$pdo->beginTransaction();
// Check if user already exists
$sql = "SELECT id FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
if ($stmt->fetch()) {
throw new Exception('Email already exists.');
}
// Insert into users table
$password_hash = password_hash($password, PASSWORD_BCRYPT);
$sql = "INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, 'driver')";
$stmt = $pdo->prepare($sql);
$stmt->execute([$full_name, $email, $password_hash]);
$user_id = $pdo->lastInsertId();
// Insert into drivers table
$sql = "INSERT INTO drivers (user_id, full_name, phone_number, vehicle_details, approval_status) VALUES (?, ?, ?, ?, 'pending')";
$stmt = $pdo->prepare($sql);
$stmt->execute([$user_id, $full_name, $phone_number, $vehicle_details]);
// Handle file uploads
if (isset($_FILES['documents'])) {
foreach ($_FILES['documents']['tmp_name'] as $doc_id => $tmp_path) {
if (!empty($tmp_path) && is_uploaded_file($tmp_path)) {
$file_name = $_FILES['documents']['name'][$doc_id];
$file_error = $_FILES['documents']['error'][$doc_id];
if ($file_error !== UPLOAD_ERR_OK) {
throw new Exception("Failed to upload file: " . $file_name);
}
$extension = pathinfo($file_name, PATHINFO_EXTENSION);
$key = "documents/drivers/{$user_id}/{$doc_id}_" . time() . "." . $extension;
$s3_url = S3Service::uploadFile($tmp_path, $key);
if ($s3_url) {
$sql = "INSERT INTO user_documents (user_id, document_id, file_path) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$user_id, $doc_id, $s3_url]);
} else {
throw new Exception("Failed to upload document to S3.");
}
}
}
}
// Send email notification to admins
require_once __DIR__ . '/mail/MailService.php';
$stmt_emails = $pdo->prepare("SELECT email FROM email_recipients WHERE form_type = ?");
$stmt_emails->execute(['driver_signup']);
$recipients = $stmt_emails->fetchAll(PDO::FETCH_COLUMN);
if (!empty($recipients)) {
$subject = 'New Driver Signup: ' . $full_name;
$html_content = "<p>A new driver has signed up and is awaiting approval.</p><p><strong>Name:</strong> {$full_name}</p><p><strong>Email:</strong> {$email}</p><p>Please visit the admin panel to review and approve the application.</p>";
$text_content = "A new driver has signed up and is awaiting approval.\nName: {$full_name}\nEmail: {$email}\nPlease visit the admin panel to review and approve the application.";
MailService::sendMail($recipients, $subject, $html_content, $text_content);
}
$pdo->commit();
header("Location: driver_pending_approval.php");
exit;
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
die("Error: " . $e->getMessage());
}
}
?>

73
footer.php Normal file
View File

@ -0,0 +1,73 @@
</main>
<footer class="bg-light pt-5 pb-4">
<div class="container text-center text-md-start">
<div class="row">
<div class="col-md-3 col-lg-4 col-xl-3 mx-auto mb-4">
<h6 class="text-uppercase fw-bold">MajuroEats</h6>
<hr class="mb-4 mt-0 d-inline-block mx-auto" style="width: 60px; background-color: #7c4dff; height: 2px"/>
<p>
Fast, fresh, and reliable food delivery for the Marshall Islands. Your favorite local restaurants, delivered right to your door.
</p>
</div>
<div class="col-md-2 col-lg-2 col-xl-2 mx-auto mb-4">
<h6 class="text-uppercase fw-bold">Links</h6>
<hr class="mb-4 mt-0 d-inline-block mx-auto" style="width: 60px; background-color: #7c4dff; height: 2px"/>
<p><a href="about.php" class="text-dark">About Us</a></p>
<p><a href="restaurants.php" class="text-dark">Restaurants</a></p>
<p><a href="driver_signup.php" class="text-dark">Become a Driver</a></p>
<p><a href="help.php" class="text-dark">Help</a></p>
</div>
<div class="col-md-3 col-lg-2 col-xl-2 mx-auto mb-4">
<h6 class="text-uppercase fw-bold">Legal</h6>
<hr class="mb-4 mt-0 d-inline-block mx-auto" style="width: 60px; background-color: #7c4dff; height: 2px"/>
<p><a href="#!" class="text-dark">Terms of Service</a></p>
<p><a href="#!" class="text-dark">Privacy Policy</a></p>
</div>
<div class="col-md-4 col-lg-3 col-xl-3 mx-auto mb-md-0 mb-4">
<h6 class="text-uppercase fw-bold">Contact</h6>
<hr class="mb-4 mt-0 d-inline-block mx-auto" style="width: 60px; background-color: #7c4dff; height: 2px"/>
<p><i class="fas fa-home me-3"></i> Majuro, MH 96960</p>
<p><i class="fas fa-envelope me-3"></i> contact@majuroeats.com</p>
<p><i class="fas fa-phone me-3"></i> + 692 123 4567</p>
</div>
</div>
</div>
<div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.05);">
© <?php echo date("Y"); ?> Copyright:
<a class="text-dark" href="/">MajuroEats.com</a>
| <a class="text-dark" href="/admin/login.php">Admin Login</a>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<!--Start of Tawk.to Script-->
<script type="text/javascript">
var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();
(function(){
var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];
s1.async=true;
s1.src='https://embed.tawk.to/68ed54c73744141950704532/1j7fh6b6i';
s1.charset='UTF-8';
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 getenv('ONESIGNAL_APP_ID'); ?>",
});
});
</script>
<?php if (extension_loaded('newrelic')) { echo newrelic_get_browser_timing_footer(); } ?>
</body>
</html>

74
forgot_password.php Normal file
View File

@ -0,0 +1,74 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'mail/MailService.php';
$page_title = "Forgot Password";
$message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['email'])) {
$email = $_POST['email'];
$db = db();
// Check if user with that email exists
$stmt = $db->prepare("SELECT * FROM users WHERE email = :email");
$stmt->bindParam(':email', $email);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
// Generate a unique token
$token = bin2hex(random_bytes(50));
// Store the token in the password_resets table
$stmt = $db->prepare("DELETE FROM password_resets WHERE email = :email");
$stmt->bindParam(':email', $email);
$stmt->execute();
$stmt = $db->prepare("INSERT INTO password_resets (email, token) VALUES (:email, :token)");
$stmt->bindParam(':email', $email);
$stmt->bindParam(':token', $token);
$stmt->execute();
// Send the password reset email
$reset_link = "http://" . $_SERVER['HTTP_HOST'] . "/reset_password.php?token=" . $token;
$subject = "Password Reset Request";
$body = "Click on this link to reset your password: <a href=''' . $reset_link . '''>''' . $reset_link . '''</a>";
MailService::sendMail($email, $subject, $body, strip_tags($body));
$message = "If an account with that email exists, a password reset link has been sent.";
} else {
$message = "If an account with that email exists, a password reset link has been sent.";
}
}
include 'header.php';
?>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4>Forgot Password</h4>
</div>
<div class="card-body">
<?php if ($message): ?>
<div class="alert alert-info"><?php echo $message; ?></div>
<?php endif; ?>
<p>Please enter your email address. You will receive a link to create a new password via email.</p>
<form action="forgot_password.php" method="post">
<div class="form-group mb-3">
<label for="email">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<button type="submit" class="btn btn-primary">Send Password Reset Email</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>

76
header.php Normal file
View File

@ -0,0 +1,76 @@
<?php
session_start();
// Cart item count
$cart_item_count = 0;
if (!empty($_SESSION['user_id'])) {
$stmt = db()->prepare("SELECT SUM(quantity) FROM cart WHERE user_id = ?");
$stmt->execute([$_SESSION['user_id']]);
$cart_item_count = $stmt->fetchColumn();
} else {
$stmt = db()->prepare("SELECT SUM(quantity) FROM cart WHERE session_id = ?");
$stmt->execute([session_id()]);
$cart_item_count = $stmt->fetchColumn();
}
$cart_item_count = $cart_item_count ?: 0;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MajuroEats - Fast Island Delivery</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<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 uniqid(); ?>">
<script src="assets.js" defer></script>
<?php if (extension_loaded('newrelic')) { echo newrelic_get_browser_timing_header(); } ?>
</head>
<body>
<header class="main-header bg-light shadow-sm">
<div class="container">
<nav class="navbar navbar-expand-lg bg-light">
<a href="/" class="navbar-brand fw-bold">
<span style="font-size: 1.5rem;">🌊</span>
MajuroEats
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav mx-auto">
<li class="nav-item"><a class="nav-link" href="index.php">Home</a></li>
<li class="nav-item"><a class="nav-link" href="restaurants.php">Restaurants</a></li>
<li class="nav-item"><a class="nav-link" href="about.php">About Us</a></li>
<li class="nav-item"><a class="nav-link" href="how-it-works.php">How It Works</a></li>
<li class="nav-item"><a class="nav-link" href="rewards.php">Rewards</a></li>
<li class="nav-item"><a class="nav-link" href="driver_signup.php">Become a Driver</a></li>
<li class="nav-item"><a class="nav-link" href="help.php">Help</a></li>
</ul>
<div class="d-flex align-items-center">
<a href="cart.php" class="btn btn-outline-primary position-relative me-3">
<i class="fas fa-shopping-cart"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?php echo $cart_item_count; ?>
<span class="visually-hidden">items in cart</span>
</span>
</a>
<?php if (isset($_SESSION['user_id'])): ?>
<a href="profile.php" class="btn btn-outline-secondary me-2">Profile</a>
<a href="logout.php" class="btn btn-primary">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-outline-secondary me-2">Sign In</a>
<a href="signup.php" class="btn btn-primary">Sign Up</a>
<?php endif; ?>
</div>
</div>
</nav>
</div>
</header>
<main class="container mt-4">

243
help.php Normal file
View File

@ -0,0 +1,243 @@
<?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>
.hero-section {
background: linear-gradient(to bottom, #40E0D0, #FFFFFF);
padding: 60px 0;
text-align: center;
color: #fff;
}
.hero-section h1 {
font-weight: bold;
font-size: 3rem;
color: #343a40;
}
.hero-section p {
font-size: 1.25rem;
color: #555;
}
.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;
}
</style>
<div class="container-fluid hero-section">
<div class="container">
<h1>Were here to help</h1>
<p>Whether you have a question, concern, or just want to say Iakwe! 🌴</p>
</div>
</div>
<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">
<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">
<div class="faq-section">
<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>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
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'; ?>

Some files were not shown because too many files have changed in this diff Show More