From 7a0a2165fc2553b5304a76568f47f55dbffab39e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 15 Oct 2025 14:58:19 +0000 Subject: [PATCH] V12 --- admin/assign_driver.php | 41 +++ admin/drivers.php | 63 +++++ admin/header.php | 6 + admin/orders.php | 31 +++ admin/settings.php | 119 +++++++++ admin/update_driver_status.php | 26 ++ api/get_order_status.php | 36 ++- api/save_location.php | 44 +++ assets/css/main.css | 92 +++++++ cart.php | 25 +- checkout.php | 21 +- driver/footer.php | 2 + driver/header.php | 27 ++ driver/index.php | 75 ++++++ driver/login.php | 30 +++ driver/login_process.php | 49 ++++ driver/logout.php | 7 + driver/update_order_status.php | 58 ++++ driver_pending_approval.php | 12 + driver_rejected.php | 12 + driver_signup.php | 35 +++ driver_signup_process.php | 47 ++++ header.php | 3 + index.php | 252 +++++++++++++----- leave_review.php | 87 ++++++ login_process.php | 35 ++- menu.php | 130 ++++----- ...0251015_add_promotion_id_to_menu_items.sql | 6 + ...251015_create_driver_assignments_table.sql | 10 + migrations/20251015_create_drivers_table.sql | 11 + ...1015_create_favorite_restaurants_table.sql | 8 + .../20251015_create_ratings_table_final.sql | 12 + migrations/20251015_create_settings_table.sql | 19 ++ ...251015_create_special_promotions_table.sql | 12 + migrations/20251015_update_drivers_table.sql | 4 + order_details.php | 10 + order_history.php | 32 ++- order_status.php | 11 +- process_review.php | 77 ++++++ profile.php | 39 +++ restaurant/add_menu_item.php | 19 +- restaurant/add_promotion.php | 61 +++++ restaurant/delete_promotion.php | 16 ++ restaurant/edit_menu_item.php | 19 +- restaurant/edit_promotion.php | 67 +++++ restaurant/header.php | 3 + restaurant/promotions.php | 47 ++++ toggle_favorite.php | 57 ++++ 48 files changed, 1723 insertions(+), 182 deletions(-) create mode 100644 admin/assign_driver.php create mode 100644 admin/drivers.php create mode 100644 admin/settings.php create mode 100644 admin/update_driver_status.php create mode 100644 api/save_location.php create mode 100644 driver/footer.php create mode 100644 driver/header.php create mode 100644 driver/index.php create mode 100644 driver/login.php create mode 100644 driver/login_process.php create mode 100644 driver/logout.php create mode 100644 driver/update_order_status.php create mode 100644 driver_pending_approval.php create mode 100644 driver_rejected.php create mode 100644 driver_signup.php create mode 100644 driver_signup_process.php create mode 100644 leave_review.php create mode 100644 migrations/20251015_add_promotion_id_to_menu_items.sql create mode 100644 migrations/20251015_create_driver_assignments_table.sql create mode 100644 migrations/20251015_create_drivers_table.sql create mode 100644 migrations/20251015_create_favorite_restaurants_table.sql create mode 100644 migrations/20251015_create_ratings_table_final.sql create mode 100644 migrations/20251015_create_settings_table.sql create mode 100644 migrations/20251015_create_special_promotions_table.sql create mode 100644 migrations/20251015_update_drivers_table.sql create mode 100644 process_review.php create mode 100644 restaurant/add_promotion.php create mode 100644 restaurant/delete_promotion.php create mode 100644 restaurant/edit_promotion.php create mode 100644 restaurant/promotions.php create mode 100644 toggle_favorite.php diff --git a/admin/assign_driver.php b/admin/assign_driver.php new file mode 100644 index 00000000..d2c965c3 --- /dev/null +++ b/admin/assign_driver.php @@ -0,0 +1,41 @@ +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; +?> \ No newline at end of file diff --git a/admin/drivers.php b/admin/drivers.php new file mode 100644 index 00000000..a50980a1 --- /dev/null +++ b/admin/drivers.php @@ -0,0 +1,63 @@ +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']; +?> + +
+

Driver Management

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Driver IDNameEmailPhoneVehicleStatusAction
+ + Approve + Reject + + N/A + +
+
+ + diff --git a/admin/header.php b/admin/header.php index 1deb2361..6c1e8f7c 100644 --- a/admin/header.php +++ b/admin/header.php @@ -34,6 +34,12 @@ if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== tru + + @@ -130,7 +143,7 @@ document.addEventListener('DOMContentLoaded', function () { return actions.order.create({ purchase_units: [{ amount: { - value: '' + value: '' } }] }); diff --git a/driver/footer.php b/driver/footer.php new file mode 100644 index 00000000..691287b6 --- /dev/null +++ b/driver/footer.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/driver/header.php b/driver/header.php new file mode 100644 index 00000000..e95cf12f --- /dev/null +++ b/driver/header.php @@ -0,0 +1,27 @@ + + + + + + + Driver Dashboard - Majuro Eats + + + + +
+ + +
\ No newline at end of file diff --git a/driver/index.php b/driver/index.php new file mode 100644 index 00000000..6129c756 --- /dev/null +++ b/driver/index.php @@ -0,0 +1,75 @@ +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(); + +$order_statuses = ['preparing', 'out for delivery', 'delivered', 'cancelled']; + +?> + +
+

My Assigned Deliveries

+ + +

+ + +

+ + +
+ +

You have no assigned orders at the moment.

+ + +
+

Order #

+

Status:

+
+

Customer:

+

Delivery Address:

+
+

Restaurant:

+

Restaurant Address:

+ +
+ +
+ + +
+ +
+
+ + +
+
+ + \ No newline at end of file diff --git a/driver/login.php b/driver/login.php new file mode 100644 index 00000000..bc0469c5 --- /dev/null +++ b/driver/login.php @@ -0,0 +1,30 @@ + + +
+
+

Driver Login

+ +

+ +
+
+ + +
+
+ + +
+ +
+
+
+ + diff --git a/driver/login_process.php b/driver/login_process.php new file mode 100644 index 00000000..d84d2c51 --- /dev/null +++ b/driver/login_process.php @@ -0,0 +1,49 @@ +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']; + 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; + } +} +?> \ No newline at end of file diff --git a/driver/logout.php b/driver/logout.php new file mode 100644 index 00000000..4e973046 --- /dev/null +++ b/driver/logout.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/driver/update_order_status.php b/driver/update_order_status.php new file mode 100644 index 00000000..04c61914 --- /dev/null +++ b/driver/update_order_status.php @@ -0,0 +1,58 @@ +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])) { + 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; +} +?> \ No newline at end of file diff --git a/driver_pending_approval.php b/driver_pending_approval.php new file mode 100644 index 00000000..3b410546 --- /dev/null +++ b/driver_pending_approval.php @@ -0,0 +1,12 @@ + + +
+
+

Application Pending Approval

+

Thank you for signing up to be a driver. Your application is currently under review.

+

We will notify you by email once your application has been approved.

+

Logout

+
+
+ + diff --git a/driver_rejected.php b/driver_rejected.php new file mode 100644 index 00000000..c2d436df --- /dev/null +++ b/driver_rejected.php @@ -0,0 +1,12 @@ + + +
+
+

Application Rejected

+

We regret to inform you that your application to become a driver has been rejected.

+

If you believe this is a mistake, please contact our support team.

+

Logout

+
+
+ + diff --git a/driver_signup.php b/driver_signup.php new file mode 100644 index 00000000..fddc10aa --- /dev/null +++ b/driver_signup.php @@ -0,0 +1,35 @@ + + +
+
+

Become a Driver

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ + diff --git a/driver_signup_process.php b/driver_signup_process.php new file mode 100644 index 00000000..06800846 --- /dev/null +++ b/driver_signup_process.php @@ -0,0 +1,47 @@ +prepare($sql); + $stmt->execute([$email]); + if ($stmt->fetch()) { + die('Email already exists.'); + } + + // Insert into drivers table + $password_hash = password_hash($password, PASSWORD_BCRYPT); + $sql = "INSERT INTO drivers (full_name, email, password_hash, phone_number, vehicle_details, approval_status) VALUES (?, ?, ?, ?, ?, 'pending')"; + $stmt = $pdo->prepare($sql); + + if ($stmt->execute([$full_name, $email, $password_hash, $phone_number, $vehicle_details])) { + // Redirect to a pending approval page + header("Location: driver_pending_approval.php"); + exit; + } else { + die("Error: Could not execute the query."); + } + } catch (PDOException $e) { + die("Could not connect to the database: " . $e->getMessage()); + } +} +?> \ No newline at end of file diff --git a/header.php b/header.php index d64c92fd..7ab00b7e 100644 --- a/header.php +++ b/header.php @@ -11,6 +11,7 @@ session_start(); + @@ -67,4 +68,6 @@ session_start(); + + diff --git a/index.php b/index.php index a8de7eb3..2d3499d8 100644 --- a/index.php +++ b/index.php @@ -8,6 +8,10 @@ +
+ + +
@@ -15,15 +19,16 @@
-

Filter by Cuisine

+

Filter Results

- + "> +
By Cuisine
query("SELECT * FROM cuisines ORDER BY name"); + $cuisine_stmt = db()->query("SELECT * FROM cuisines ORDER BY name"); $all_cuisines = $cuisine_stmt->fetchAll(PDO::FETCH_ASSOC); $selected_cuisines = isset($_GET['cuisines']) && is_array($_GET['cuisines']) ? $_GET['cuisines'] : []; @@ -35,8 +40,20 @@
- - Clear Filter + +
By Rating
+
+ +
+ + + Clear Filters
@@ -76,9 +93,18 @@ $sql .= " WHERE " . implode(' AND ', $where_clauses); } - $sql .= " GROUP BY r.id, r.name, r.image_url ORDER BY r.name"; + $sql .= " GROUP BY r.id, r.name, r.image_url"; - $stmt = $pdo->prepare($sql); + // Add rating filter (HAVING clause) + $selected_min_rating = isset($_GET['min_rating']) && is_numeric($_GET['min_rating']) ? (int)$_GET['min_rating'] : 0; + if ($selected_min_rating > 0) { + $sql .= " HAVING AVG(rt.rating) >= ?"; + $params[] = $selected_min_rating; + } + + $sql .= " ORDER BY r.name"; + + $stmt = db()->prepare($sql); $stmt->execute($params); $restaurants = $stmt->fetchAll(); @@ -88,11 +114,11 @@ foreach ($restaurants as $restaurant) { // Get cuisines for this restaurant $cuisine_sql = "SELECT c.name FROM cuisines c JOIN restaurant_cuisines rc ON c.id = rc.cuisine_id WHERE rc.restaurant_id = ?"; - $cuisine_stmt = $pdo->prepare($cuisine_sql); + $cuisine_stmt = db()->prepare($cuisine_sql); $cuisine_stmt->execute([$restaurant['id']]); $restaurant_cuisines_list = $cuisine_stmt->fetchAll(PDO::FETCH_COLUMN); - echo ''; + echo ''; echo '' . htmlspecialchars($restaurant['name']) . ''; echo '
'; echo '

' . htmlspecialchars($restaurant['name']) . '

'; @@ -118,55 +144,161 @@
- - + + + + + \ No newline at end of file diff --git a/leave_review.php b/leave_review.php new file mode 100644 index 00000000..7ce6af73 --- /dev/null +++ b/leave_review.php @@ -0,0 +1,87 @@ + 0) { + // 1. Verify the order exists, belongs to the user, and is delivered + $stmt = $db->prepare("SELECT * FROM orders WHERE id = ? AND user_id = ?"); + $stmt->execute([$order_id, $user_id]); + $order = $stmt->fetch(); + + if (!$order) { + $error_message = "This order could not be found or does not belong to you."; + } elseif ($order['status'] !== 'Delivered') { + $error_message = "You can only review orders that have been delivered."; + } else { + // 2. Check if a review already exists for this order + $stmt_rating = $db->prepare("SELECT id FROM ratings WHERE order_id = ?"); + $stmt_rating->execute([$order_id]); + if ($stmt_rating->fetch()) { + $error_message = "You have already submitted a review for this order."; + } + } +} else { + $error_message = "No order was specified."; +} +?> + +
+

Leave a Review

+
+ + ' . $_SESSION['success_message'] . '
'; + unset($_SESSION['success_message']); + } + if (isset($_SESSION['error_message'])) { + echo '
' . $_SESSION['error_message'] . '
'; + unset($_SESSION['error_message']); + } + ?> + + +
+ +

You are reviewing Order #

+ +
+ + + +
+ +
+ + + + + +
+
+ +
+ + +
+ + +
+ + + + diff --git a/login_process.php b/login_process.php index 69dc54f6..7f7989bd 100644 --- a/login_process.php +++ b/login_process.php @@ -23,17 +23,40 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { - $user_id = $user['id']; - $session_id = session_id(); + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_name'] = $user['name']; + $_SESSION['user_role'] = $user['role']; // Merge guest cart with user cart + $session_id = session_id(); $merge_sql = "UPDATE cart SET user_id = ?, session_id = NULL WHERE session_id = ?"; $merge_stmt = $pdo->prepare($merge_sql); - $merge_stmt->execute([$user_id, $session_id]); + $merge_stmt->execute([$user['id'], $session_id]); - $_SESSION['user_id'] = $user_id; - $_SESSION['user_name'] = $user['name']; - header("Location: index.php"); + if ($user['role'] == 'admin') { + $_SESSION['admin_logged_in'] = true; + header("Location: admin/index.php"); + } elseif ($user['role'] == 'driver') { + $driver_sql = "SELECT approval_status FROM drivers WHERE user_id = ?"; + $driver_stmt = $pdo->prepare($driver_sql); + $driver_stmt->execute([$user['id']]); + $driver = $driver_stmt->fetch(); + + if ($driver) { + if ($driver['approval_status'] == 'approved') { + header("Location: driver/index.php"); + } elseif ($driver['approval_status'] == 'pending') { + header("Location: driver_pending_approval.php"); + } else { // rejected + header("Location: driver_rejected.php"); + } + } else { + // This case should ideally not happen if data is consistent + die('Driver profile not found.'); + } + } else { // customer + header("Location: index.php"); + } exit; } else { die('Invalid email or password.'); diff --git a/menu.php b/menu.php index 441b7183..257f6bd7 100644 --- a/menu.php +++ b/menu.php @@ -2,53 +2,36 @@ session_start(); require_once 'db/config.php'; -$restaurant_id = isset($_GET['id']) ? (int)$_GET['id'] : 0; +$restaurant_id = isset($_GET['restaurant_id']) ? (int)$_GET['restaurant_id'] : 0; if ($restaurant_id === 0) { header('Location: index.php'); exit; } -// Handle review submission -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_review'])) { - if (!isset($_SESSION['user_id'])) { - // Redirect to login if not logged in - header('Location: login.php'); - exit; - } - - $rating = isset($_POST['rating']) ? (int)$_POST['rating'] : 0; - $review = isset($_POST['review']) ? trim($_POST['review']) : ''; - $user_id = $_SESSION['user_id']; - - if ($rating >= 1 && $rating <= 5) { - try { - $insert_stmt = db()->prepare("INSERT INTO ratings (restaurant_id, user_id, rating, review) VALUES (:restaurant_id, :user_id, :rating, :review)"); - $insert_stmt->bindParam(':restaurant_id', $restaurant_id, PDO::PARAM_INT); - $insert_stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT); - $insert_stmt->bindParam(':rating', $rating, PDO::PARAM_INT); - $insert_stmt->bindParam(':review', $review, PDO::PARAM_STR); - $insert_stmt->execute(); - // Redirect to the same page to prevent form resubmission - header("Location: menu.php?id=$restaurant_id&rated=true"); - exit; - } catch (PDOException $e) { - $submit_error = "Error submitting your review. Please try again."; - // In a real app, you'd log this error. - } - } else { - $submit_error = "Please select a rating between 1 and 5."; - } -} - require_once 'header.php'; try { // Fetch restaurant details - $stmt = db()->prepare("SELECT name, image_url, cuisine FROM restaurants WHERE id = :id"); + $stmt = db()->prepare("SELECT r.id, r.name, r.image_url, c.name as cuisine_name + FROM restaurants r + LEFT JOIN restaurant_cuisines rc ON r.id = rc.restaurant_id + LEFT JOIN cuisines c ON rc.cuisine_id = c.id + WHERE r.id = :id"); $stmt->bindParam(':id', $restaurant_id, PDO::PARAM_INT); $stmt->execute(); - $restaurant = $stmt->fetch(PDO::FETCH_ASSOC); + $restaurant_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (!$restaurant_data) { + throw new Exception("Restaurant not found."); + } + + $restaurant = [ + 'id' => $restaurant_data[0]['id'], + 'name' => $restaurant_data[0]['name'], + 'image_url' => $restaurant_data[0]['image_url'], + 'cuisines' => array_column($restaurant_data, 'cuisine_name') + ]; // Fetch menu items $menu_stmt = db()->prepare("SELECT id, name, description, price, image_url FROM menu_items WHERE restaurant_id = :restaurant_id"); @@ -63,28 +46,45 @@ try { $ratings = $ratings_stmt->fetchAll(PDO::FETCH_ASSOC); $average_rating = 0; - if (count($ratings) > 0) { - $total_rating = 0; - foreach ($ratings as $r) { - $total_rating += $r['rating']; - } - $average_rating = round($total_rating / count($ratings), 1); + $rating_count = count($ratings); + if ($rating_count > 0) { + $total_rating = array_sum(array_column($ratings, 'rating')); + $average_rating = round($total_rating / $rating_count, 1); } -} catch (PDOException $e) { - echo "

Error fetching restaurant data.

"; + // Check if this restaurant is a favorite for the current user + $is_favorite = false; + if (isset($_SESSION['user_id'])) { + $fav_stmt = db()->prepare("SELECT COUNT(*) FROM favorite_restaurants WHERE user_id = :user_id AND restaurant_id = :restaurant_id"); + $fav_stmt->bindParam(':user_id', $_SESSION['user_id'], PDO::PARAM_INT); + $fav_stmt->bindParam(':restaurant_id', $restaurant_id, PDO::PARAM_INT); + $fav_stmt->execute(); + $is_favorite = $fav_stmt->fetchColumn() > 0; + } + +} catch (Exception $e) { + echo "

" . $e->getMessage() . "

"; require_once 'footer.php'; exit; } - ?>
-

-

+
+

+ +
+ + +
+ +
+

( reviews) @@ -138,39 +138,17 @@ try {

Reviews & Ratings

- -
-
Leave a Review
-
- -
- - -
Thank you for your review!
- -
-
- - -
-
- - -
- -
-
+ 0): ?> +
+ You have already reviewed this restaurant. +
+ +
+ Leave a review for a completed order.
- Log in to leave a review. + Log in to see reviews or leave your own.
diff --git a/migrations/20251015_add_promotion_id_to_menu_items.sql b/migrations/20251015_add_promotion_id_to_menu_items.sql new file mode 100644 index 00000000..b7de5144 --- /dev/null +++ b/migrations/20251015_add_promotion_id_to_menu_items.sql @@ -0,0 +1,6 @@ +ALTER TABLE menu_items +ADD COLUMN promotion_id INT, +ADD CONSTRAINT fk_promotion + FOREIGN KEY(promotion_id) + REFERENCES special_promotions(id) + ON DELETE SET NULL; diff --git a/migrations/20251015_create_driver_assignments_table.sql b/migrations/20251015_create_driver_assignments_table.sql new file mode 100644 index 00000000..1243f1fe --- /dev/null +++ b/migrations/20251015_create_driver_assignments_table.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS driver_assignments ( + id SERIAL PRIMARY KEY, + order_id INT NOT NULL, + driver_id INT NOT NULL, + assignment_status VARCHAR(50) NOT NULL DEFAULT 'pending', -- pending, accepted, completed + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, + FOREIGN KEY (driver_id) REFERENCES drivers(id) ON DELETE CASCADE +); diff --git a/migrations/20251015_create_drivers_table.sql b/migrations/20251015_create_drivers_table.sql new file mode 100644 index 00000000..ddcdc3f1 --- /dev/null +++ b/migrations/20251015_create_drivers_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS drivers ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL, + full_name VARCHAR(255) NOT NULL, + phone_number VARCHAR(255) NOT NULL, + vehicle_details TEXT, + approval_status VARCHAR(50) NOT NULL DEFAULT 'pending', -- pending, approved, rejected + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); diff --git a/migrations/20251015_create_favorite_restaurants_table.sql b/migrations/20251015_create_favorite_restaurants_table.sql new file mode 100644 index 00000000..e0980fc2 --- /dev/null +++ b/migrations/20251015_create_favorite_restaurants_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS favorite_restaurants ( + user_id INT NOT NULL, + restaurant_id INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, restaurant_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) ON DELETE CASCADE +); diff --git a/migrations/20251015_create_ratings_table_final.sql b/migrations/20251015_create_ratings_table_final.sql new file mode 100644 index 00000000..04994a99 --- /dev/null +++ b/migrations/20251015_create_ratings_table_final.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS ratings ( + id SERIAL PRIMARY KEY, + order_id INT NOT NULL UNIQUE, + restaurant_id INT NOT NULL, + user_id INT NOT NULL, + rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), + review TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, + FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); diff --git a/migrations/20251015_create_settings_table.sql b/migrations/20251015_create_settings_table.sql new file mode 100644 index 00000000..23397752 --- /dev/null +++ b/migrations/20251015_create_settings_table.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS settings ( + name VARCHAR(255) PRIMARY KEY, + value VARCHAR(255) +); + +INSERT INTO settings (name, value) VALUES +('service_fee_percentage', '7'), +('delivery_fee', '1.50'), +('driver_base_pay_tier1_miles', '3'), +('driver_base_pay_tier1_amount', '3'), +('driver_base_pay_tier2_miles', '5'), +('driver_base_pay_tier2_amount', '4'), +('driver_base_pay_tier3_amount', '5'), +('driver_mileage_pay_rate', '0.65'), +('driver_busy_hour_bonus', '2'), +('driver_quest_bonus_amount', '10'), +('driver_quest_bonus_deliveries', '10'), +('driver_quest_bonus_hours', '2') +ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value; \ No newline at end of file diff --git a/migrations/20251015_create_special_promotions_table.sql b/migrations/20251015_create_special_promotions_table.sql new file mode 100644 index 00000000..734f240a --- /dev/null +++ b/migrations/20251015_create_special_promotions_table.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS special_promotions ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + discount_type VARCHAR(10) NOT NULL, + discount_value DECIMAL(10, 2) NOT NULL, + start_date TIMESTAMP NOT NULL, + end_date TIMESTAMP NOT NULL, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/migrations/20251015_update_drivers_table.sql b/migrations/20251015_update_drivers_table.sql new file mode 100644 index 00000000..5575cad9 --- /dev/null +++ b/migrations/20251015_update_drivers_table.sql @@ -0,0 +1,4 @@ +ALTER TABLE drivers DROP CONSTRAINT IF EXISTS drivers_user_id_fkey; +ALTER TABLE drivers DROP COLUMN IF EXISTS user_id; +ALTER TABLE drivers ADD COLUMN IF NOT EXISTS email VARCHAR(255) NOT NULL UNIQUE; +ALTER TABLE drivers ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255) NOT NULL; diff --git a/order_details.php b/order_details.php index 148a1992..3bd79b2d 100644 --- a/order_details.php +++ b/order_details.php @@ -3,6 +3,8 @@ session_start(); require_once 'db/config.php'; include 'header.php'; +echo ''; + if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit(); @@ -31,6 +33,11 @@ if (!$order) { $p_items = $db->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?"); $p_items->execute([$order_id]); $items = $p_items->fetchAll(PDO::FETCH_ASSOC); + +// Fetch assigned driver +$p_driver = $db->prepare("SELECT d.full_name FROM drivers d JOIN driver_assignments da ON d.id = da.driver_id WHERE da.order_id = ?"); +$p_driver->execute([$order_id]); +$driver = $p_driver->fetch(PDO::FETCH_ASSOC); ?>
@@ -43,6 +50,9 @@ $items = $p_items->fetchAll(PDO::FETCH_ASSOC);

Order Date:

Total: $

Status:

+ +

Driver:

+
diff --git a/order_history.php b/order_history.php index a53ee001..edd4729c 100644 --- a/order_history.php +++ b/order_history.php @@ -12,7 +12,8 @@ include 'header.php'; $user_id = $_SESSION['user_id']; $db = db(); -$stmt = $db->prepare("SELECT * FROM orders WHERE user_id = ? ORDER BY order_date DESC"); +// Fetch user's orders +$stmt = $db->prepare("SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC"); $stmt->execute([$user_id]); $orders = $stmt->fetchAll(); @@ -22,6 +23,17 @@ $orders = $stmt->fetchAll();

My Order History


+ ' . $_SESSION['success_message'] . '
'; + unset($_SESSION['success_message']); + } + if (isset($_SESSION['error_message'])) { + echo '
' . $_SESSION['error_message'] . '
'; + unset($_SESSION['error_message']); + } + ?> +
You have not placed any orders yet.
@@ -39,12 +51,22 @@ $orders = $stmt->fetchAll(); - + $ - View Details - Track Order + View Details / Track + prepare("SELECT id FROM ratings WHERE order_id = ?"); + $stmt_rating->execute([$order['id']]); + $existing_rating = $stmt_rating->fetch(); + + // If order is delivered and no review exists, show the 'Leave a Review' link + if ($order['status'] === 'Delivered' && !$existing_rating): + ?> + Leave a Review + @@ -53,4 +75,4 @@ $orders = $stmt->fetchAll();
- \ No newline at end of file + diff --git a/order_status.php b/order_status.php index 33642766..87b595b4 100644 --- a/order_status.php +++ b/order_status.php @@ -16,9 +16,10 @@ if (!isset($_GET['order_id'])) { $order_id = $_GET['order_id']; $user_id = $_SESSION['user_id']; +$pdo = db(); // Fetch order details to ensure the user owns this order -$p_order = $db->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?"); +$p_order = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?"); $p_order->execute([$order_id, $user_id]); $order = $p_order->fetch(PDO::FETCH_ASSOC); @@ -29,7 +30,7 @@ if (!$order) { } // Fetch order items -$p_items = $db->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?"); +$p_items = $pdo->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?"); $p_items->execute([$order_id]); $items = $p_items->fetchAll(PDO::FETCH_ASSOC); @@ -44,7 +45,7 @@ $items = $p_items->fetchAll(PDO::FETCH_ASSOC);

Restaurant:

Order Date:

-

Status:

+

Status:

@@ -80,8 +81,8 @@ document.addEventListener('DOMContentLoaded', function() { const statusToProgress = { 'Pending': 10, - 'In Progress': 40, - 'Out for Delivery': 75, + 'Preparing': 40, + 'Out For Delivery': 75, 'Delivered': 100, 'Cancelled': 0 }; diff --git a/process_review.php b/process_review.php new file mode 100644 index 00000000..16a4c97b --- /dev/null +++ b/process_review.php @@ -0,0 +1,77 @@ + 5) { + $_SESSION['error_message'] = "Invalid data provided. Please try again."; + header("Location: leave_review.php?order_id=" . $order_id); + exit(); +} + +// 2. Security and integrity validation +$stmt = $db->prepare("SELECT id, status FROM orders WHERE id = ? AND user_id = ? AND restaurant_id = ?"); +$stmt->execute([$order_id, $user_id, $restaurant_id]); +$order = $stmt->fetch(); + +if (!$order) { + $_SESSION['error_message'] = "You cannot review this order."; + header("Location: order_history.php"); + exit(); +} elseif ($order['status'] !== 'Delivered') { + $_SESSION['error_message'] = "You can only review delivered orders."; + header("Location: leave_review.php?order_id=" . $order_id); + exit(); +} + +// 3. Check if a review already exists +$stmt_rating = $db->prepare("SELECT id FROM ratings WHERE order_id = ?"); +$stmt_rating->execute([$order_id]); +if ($stmt_rating->fetch()) { + $_SESSION['error_message'] = "You have already reviewed this order."; + header("Location: order_history.php"); + exit(); +} + +// --- All checks passed, insert into database --- +try { + $stmt_insert = $db->prepare( + "INSERT INTO ratings (order_id, restaurant_id, user_id, rating, review) VALUES (?, ?, ?, ?, ?)" + ); + $stmt_insert->execute([$order_id, $restaurant_id, $user_id, $rating, $review]); + + $_SESSION['success_message'] = "Thank you for your review!"; + header("Location: order_history.php"); + exit(); + +} catch (PDOException $e) { + // Log error properly in a real application + // error_log($e->getMessage()); + $_SESSION['error_message'] = "A database error occurred. Please try again later."; + header("Location: leave_review.php?order_id=" . $order_id); + exit(); +} diff --git a/profile.php b/profile.php index 702f5989..62323163 100644 --- a/profile.php +++ b/profile.php @@ -56,6 +56,18 @@ $p_user = $db->prepare("SELECT * FROM users WHERE id = ?"); $p_user->execute([$user_id]); $user = $p_user->fetch(); +// Fetch favorite restaurants +$fav_stmt = $db->prepare(" + SELECT r.id, r.name, r.image_url, c.name as cuisine_name + FROM favorite_restaurants fr + JOIN restaurants r ON fr.restaurant_id = r.id + LEFT JOIN restaurant_cuisines rc ON r.id = rc.restaurant_id + LEFT JOIN cuisines c ON rc.cuisine_id = c.id + WHERE fr.user_id = ? + GROUP BY r.id +"); +$fav_stmt->execute([$user_id]); +$favorite_restaurants = $fav_stmt->fetchAll(); ?> @@ -121,6 +133,33 @@ $user = $p_user->fetch(); + +
+
+

My Favorite Restaurants

+
+ +
+ +
+
+ + <?php echo htmlspecialchars($fav_restaurant['name']); ?> + +
+
+

+ View Menu +
+
+
+ +
+ +

You haven't added any favorite restaurants yet. Explore restaurants to find some!

+ +
+
\ No newline at end of file diff --git a/restaurant/add_menu_item.php b/restaurant/add_menu_item.php index 6cd1e03b..6cd20687 100644 --- a/restaurant/add_menu_item.php +++ b/restaurant/add_menu_item.php @@ -26,16 +26,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $_POST['name'] ?? ''; $description = $_POST['description'] ?? ''; $price = $_POST['price'] ?? ''; + $promotion_id = $_POST['promotion_id'] ?? null; if ($name && $price) { - $stmt = $pdo->prepare("INSERT INTO menu_items (restaurant_id, name, description, price) VALUES (?, ?, ?, ?)"); - $stmt->execute([$restaurant_id, $name, $description, $price]); + $stmt = $pdo->prepare("INSERT INTO menu_items (restaurant_id, name, description, price, promotion_id) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$restaurant_id, $name, $description, $price, $promotion_id]); header('Location: menu.php'); exit; } else { $error = "Name and price are required."; } } + +$stmt = $pdo->prepare("SELECT * FROM special_promotions"); +$stmt->execute(); +$promotions = $stmt->fetchAll(); + ?>
@@ -58,6 +64,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+
+ + +
Cancel diff --git a/restaurant/add_promotion.php b/restaurant/add_promotion.php new file mode 100644 index 00000000..13025b8d --- /dev/null +++ b/restaurant/add_promotion.php @@ -0,0 +1,61 @@ +prepare("INSERT INTO special_promotions (name, description, discount_type, discount_value, start_date, end_date, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$name, $description, $discount_type, $discount_value, $start_date, $end_date, $is_active]); + + header('Location: promotions.php'); + exit; +} + +?> + +
+

Add Promotion

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/restaurant/delete_promotion.php b/restaurant/delete_promotion.php new file mode 100644 index 00000000..c03b00df --- /dev/null +++ b/restaurant/delete_promotion.php @@ -0,0 +1,16 @@ +prepare("UPDATE menu_items SET promotion_id = NULL WHERE promotion_id = ?"); +$stmt->execute([$promotion_id]); + +// Then, delete the promotion +$stmt = db()->prepare("DELETE FROM special_promotions WHERE id = ?"); +$stmt->execute([$promotion_id]); + +header('Location: promotions.php'); +exit; +?> \ No newline at end of file diff --git a/restaurant/edit_menu_item.php b/restaurant/edit_menu_item.php index e422a1c2..1d03bd96 100644 --- a/restaurant/edit_menu_item.php +++ b/restaurant/edit_menu_item.php @@ -42,16 +42,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $_POST['name'] ?? ''; $description = $_POST['description'] ?? ''; $price = $_POST['price'] ?? ''; + $promotion_id = $_POST['promotion_id'] ?? null; if ($name && $price) { - $stmt = $pdo->prepare("UPDATE menu_items SET name = ?, description = ?, price = ? WHERE id = ? AND restaurant_id = ?"); - $stmt->execute([$name, $description, $price, $menu_item_id, $restaurant_id]); + $stmt = $pdo->prepare("UPDATE menu_items SET name = ?, description = ?, price = ?, promotion_id = ? WHERE id = ? AND restaurant_id = ?"); + $stmt->execute([$name, $description, $price, $promotion_id, $menu_item_id, $restaurant_id]); header('Location: menu.php'); exit; } else { $error = "Name and price are required."; } } + +$stmt = $pdo->prepare("SELECT * FROM special_promotions"); +$stmt->execute(); +$promotions = $stmt->fetchAll(); + ?>
@@ -74,6 +80,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+
+ + +
Cancel diff --git a/restaurant/edit_promotion.php b/restaurant/edit_promotion.php new file mode 100644 index 00000000..34a96dcd --- /dev/null +++ b/restaurant/edit_promotion.php @@ -0,0 +1,67 @@ +prepare("UPDATE special_promotions SET name = ?, description = ?, discount_type = ?, discount_value = ?, start_date = ?, end_date = ?, is_active = ? WHERE id = ?"); + $stmt->execute([$name, $description, $discount_type, $discount_value, $start_date, $end_date, $is_active, $promotion_id]); + + header('Location: promotions.php'); + exit; +} + +$stmt = db()->prepare("SELECT * FROM special_promotions WHERE id = ?"); +$stmt->execute([$promotion_id]); +$promotion = $stmt->fetch(); + +?> + +
+

Edit Promotion

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ > + +
+ +
+
+ + \ No newline at end of file diff --git a/restaurant/header.php b/restaurant/header.php index 11fb95ff..03c61190 100644 --- a/restaurant/header.php +++ b/restaurant/header.php @@ -31,6 +31,9 @@ if (!isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'restaurant_own +