This commit is contained in:
Flatlogic Bot 2025-10-16 06:28:46 +00:00
parent 5fc6fe4ad4
commit adf8c9c972
27 changed files with 1264 additions and 411 deletions

View File

@ -3,35 +3,43 @@ header('Content-Type: application/json');
session_start();
require_once __DIR__ . '/../db/config.php';
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
echo json_encode(['error' => 'User not authenticated']);
exit;
}
$order_id = $_GET['order_id'] ?? null;
$token = $_GET['token'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
// Check if order_id is provided
if (!isset($_GET['order_id'])) {
if (!$order_id) {
echo json_encode(['error' => 'Order ID not specified']);
exit;
}
$order_id = $_GET['order_id'];
$user_id = $_SESSION['user_id'];
$status = null;
try {
$pdo = db();
// Fetch the order status, ensuring the order belongs to the logged-in user
if ($user_id) {
$stmt = $pdo->prepare("SELECT status FROM orders WHERE id = ? AND user_id = ?");
$stmt->execute([$order_id, $user_id]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
$status = $result['status'];
}
} elseif ($token) {
$stmt = $pdo->prepare("SELECT status FROM orders WHERE id = ? AND guest_token = ?");
$stmt->execute([$order_id, $token]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
$status = $result['status'];
}
}
if ($order) {
echo json_encode(['status' => ucwords($order['status'])]);
if ($status) {
echo json_encode(['status' => ucwords($status)]);
} else {
echo json_encode(['error' => 'Order not found or permission denied']);
}
} catch (PDOException $e) {
echo json_encode(['error' => 'Database error']);
http_response_code(500);
echo json_encode(['error' => 'Database connection error']);
}
?>

View File

@ -264,17 +264,17 @@ footer {
/* Restaurant Menu Page */
.restaurant-hero-menu {
position: relative;
height: 300px;
height: 350px;
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 2rem;
padding: 3rem;
color: var(--white);
border-radius: var(--border-radius);
border-radius: 0 0 var(--border-radius) var(--border-radius);
overflow: hidden;
margin-bottom: 2rem;
margin-bottom: 3rem;
}
.restaurant-hero-menu::after {
@ -284,7 +284,7 @@ footer {
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 100%);
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.1) 100%);
}
.restaurant-hero-menu-content {
@ -293,23 +293,23 @@ footer {
}
.restaurant-hero-menu h1 {
font-size: 2.5rem;
font-size: 3rem;
font-weight: 700;
margin: 0 0 0.5rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.6);
text-shadow: 0 2px 8px rgba(0,0,0,0.7);
}
.restaurant-hero-menu p {
font-size: 1rem;
font-size: 1.1rem;
margin: 0.25rem 0;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
text-shadow: 0 1px 5px rgba(0,0,0,0.6);
}
/* Menu Grid */
.menu-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.menu-item-card {
@ -319,7 +319,8 @@ footer {
overflow: hidden;
transition: all 0.3s ease-in-out;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 1.5rem;
}
.menu-item-card:hover {
@ -327,21 +328,23 @@ footer {
box-shadow: 0 8px 20px rgba(0,0,0,0.12);
}
.menu-item-card img {
width: 100%;
height: 180px;
.menu-item-image {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 12px;
margin-left: 1.5rem;
}
.menu-item-card-content {
padding: 1.5rem;
padding: 0;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.menu-item-card-content h3 {
font-size: 1.15rem;
font-size: 1.2rem;
font-weight: 700;
margin: 0 0 0.5rem;
}
@ -357,9 +360,50 @@ footer {
font-size: 1.1rem;
font-weight: 700;
color: var(--text-color);
align-self: flex-end;
}
/* Reviews Section */
.reviews-section {
background-color: var(--white);
padding: 2rem;
border-radius: var(--border-radius);
box-shadow: var(--shadow-soft);
position: sticky;
top: 120px; /* Adjust based on header height */
}
.review-card {
border-bottom: 1px solid var(--light-gray);
padding: 1.5rem 0;
}
.review-card:last-child {
border-bottom: none;
padding-bottom: 0;
}
.review-card:first-child {
padding-top: 0;
}
.review-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.review-header strong {
font-size: 1rem;
}
.review-text {
font-style: italic;
color: var(--dark-gray);
margin-bottom: 0.5rem;
}
/* Rating Section on Menu Page */
.rating-form-container {
background-color: var(--white);
@ -1030,3 +1074,475 @@ footer {
width: 100%;
padding: 0;
}
/* Categories Section */
.categories-section {
margin-bottom: 3rem;
}
.category-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
}
.category-card {
position: relative;
border-radius: var(--border-radius);
overflow: hidden;
height: 150px;
display: flex;
align-items: flex-end;
justify-content: center;
text-decoration: none;
color: var(--white);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.category-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.category-card img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
transition: transform 0.3s ease;
}
.category-card:hover img {
transform: scale(1.05);
}
.category-card-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 60%);
z-index: 2;
}
.category-card h3 {
position: relative;
z-index: 3;
margin: 0;
padding: 1rem;
font-size: 1.2rem;
font-weight: 700;
text-align: center;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
/* New Checkout Styles */
.checkout-container {
display: flex;
min-height: 100vh;
background-color: var(--white);
}
.checkout-main {
flex-grow: 1;
padding: 2rem 4rem;
display: flex;
flex-direction: column;
}
.checkout-header {
margin-bottom: 3rem;
}
.checkout-logo {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-color);
}
.step-title {
font-size: 1.75rem;
margin-bottom: 2rem;
}
#delivery-form .form-group {
margin-bottom: 1.5rem;
}
#delivery-form .form-control {
padding: 1rem;
font-size: 1rem;
border-radius: 8px;
border: 1px solid var(--medium-gray);
}
.btn-primary {
width: 100%;
padding: 1rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 8px;
background-color: var(--coral);
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-primary:hover {
background-color: #ff4f4f;
}
.payment-methods {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin-bottom: 2rem;
}
.payment-method-card {
border: 1px solid var(--medium-gray);
border-radius: 12px;
cursor: pointer;
transition: border-color 0.3s, box-shadow 0.3s;
}
.payment-method-card:has(input:checked) {
border-color: var(--coral);
box-shadow: 0 0 0 2px var(--coral);
}
.payment-method-card label {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem;
font-size: 1.1rem;
font-weight: 600;
}
.payment-method-card input[type="radio"] {
display: none;
}
.payment-method-card svg {
width: 32px;
height: 32px;
}
#paypal-button-container {
margin-top: 1rem;
}
.btn-secondary {
width: 100%;
padding: 1rem;
font-size: 1.1rem;
font-weight: 600;
border-radius: 8px;
background-color: transparent;
color: var(--dark-gray);
border: 1px solid var(--medium-gray);
cursor: pointer;
margin-top: 1rem;
transition: background-color 0.3s, border-color 0.3s;
}
.btn-secondary:hover {
background-color: var(--light-gray);
border-color: var(--dark-gray);
}
.checkout-summary {
width: 450px;
background-color: var(--off-white);
padding: 2rem;
border-left: 1px solid var(--light-gray);
display: flex;
flex-direction: column;
}
.checkout-summary h4 {
font-size: 1.5rem;
margin-bottom: 2rem;
}
.summary-items {
flex-grow: 1;
}
.summary-item {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
font-size: 1rem;
}
.item-name {
color: var(--dark-gray);
}
.item-price {
font-weight: 600;
}
.summary-total {
border-top: 1px solid var(--medium-gray);
padding-top: 1.5rem;
}
.summary-line {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
font-size: 1rem;
}
.summary-line.discount {
color: #28a745;
}
.summary-line.total {
font-size: 1.25rem;
font-weight: 700;
margin-top: 1rem;
}
/* Order Confirmation Page Refined */
.card.shadow-sm {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--shadow-soft);
}
.card-header.bg-success {
background-color: var(--coral) !important;
border-bottom: none;
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.card-header h2 {
font-size: 1.75rem;
color: var(--white);
}
.card-body .lead {
font-size: 1.2rem;
font-weight: 500;
color: var(--dark-gray);
}
.card-body h5 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text-color);
border-bottom: 2px solid var(--light-gray);
padding-bottom: 0.5rem;
}
.badge.bg-warning {
font-size: 0.9rem;
padding: 0.5em 0.75em;
background-color: var(--turquoise) !important;
color: var(--text-color) !important;
}
.list-group-item {
border-color: var(--light-gray);
}
.list-group-item span {
font-weight: 600;
}
.list-group-item.fw-bold {
font-size: 1.1rem;
}
.order-confirmation-actions .btn {
margin: 0 0.5rem;
padding: 0.8rem 1.5rem;
font-weight: 600;
text-transform: uppercase;
font-size: 0.9rem;
border-radius: 50px;
transition: all 0.3s ease;
}
.order-confirmation-actions .btn-primary {
background-color: var(--coral);
border-color: var(--coral);
}
.order-confirmation-actions .btn-primary:hover {
background-color: #ff4f4f;
border-color: #ff4f4f;
transform: translateY(-2px);
}
.order-confirmation-actions .btn-secondary {
background-color: var(--off-white);
border-color: var(--medium-gray);
color: var(--dark-gray);
}
.order-confirmation-actions .btn-secondary:hover {
background-color: var(--light-gray);
border-color: var(--dark-gray);
transform: translateY(-2px);
}
/* Order Status Page */
.order-status-container {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background-color: var(--white);
border-radius: var(--border-radius);
box-shadow: var(--shadow-soft);
}
.order-status-header {
text-align: center;
margin-bottom: 2.5rem;
border-bottom: 1px solid var(--light-gray);
padding-bottom: 1.5rem;
}
.order-status-header h1 {
font-size: 2.2rem;
margin-bottom: 0.5rem;
}
.order-status-header p {
font-size: 1.1rem;
color: var(--dark-gray);
}
#order-status-timeline {
position: relative;
padding: 1rem 0;
}
.timeline-item {
display: flex;
position: relative;
padding-left: 60px; /* Space for icon and line */
padding-bottom: 3rem;
}
.timeline-item:last-child {
padding-bottom: 0;
}
.timeline-item::before {
content: '';
position: absolute;
left: 24px; /* Center the line */
top: 50px;
width: 2px;
height: calc(100% - 50px);
background-color: var(--light-gray);
transition: background-color 0.5s ease;
}
.timeline-item:last-child::before {
display: none;
}
.timeline-icon {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: var(--light-gray);
color: var(--medium-gray);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
z-index: 1;
border: 4px solid var(--off-white);
transition: all 0.5s ease;
}
.timeline-content {
padding-top: 10px;
}
.timeline-content h5 {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 0.25rem;
transition: color 0.5s ease;
border-bottom: none;
padding-bottom: 0;
}
.timeline-content p {
font-size: 0.95rem;
color: var(--dark-gray);
margin: 0;
}
/* --- Timeline States --- */
/* Default (Not yet active) */
.timeline-item:not(.timeline-complete):not(.timeline-active) .timeline-icon {
background-color: var(--light-gray);
color: var(--medium-gray);
}
/* Active State */
.timeline-active .timeline-icon {
background-color: var(--turquoise);
color: var(--white);
transform: scale(1.1);
box-shadow: 0 0 15px rgba(64, 224, 208, 0.7);
}
.timeline-active h5 {
color: var(--turquoise);
}
/* Complete State */
.timeline-complete::before {
background-color: var(--coral);
}
.timeline-complete .timeline-icon {
background-color: var(--coral);
color: var(--white);
}
/* Cancelled State */
.timeline-cancelled .timeline-icon {
background-color: var(--dark-gray);
color: var(--white);
}
.timeline-cancelled h5 {
color: var(--dark-gray);
text-decoration: line-through;
}
.order-status-footer {
text-align: center;
margin-top: 2.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--light-gray);
}
.order-status-footer p {
margin-bottom: 1rem;
}

View File

@ -3,16 +3,26 @@ session_start();
require_once 'db/config.php';
require_once 'includes/api_keys.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
$userId = $_SESSION['user_id'];
$is_guest = !isset($_SESSION['user_id']);
$user_id = $_SESSION['user_id'] ?? null;
$session_id = session_id();
$pdo = db();
$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', $userId);
$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);
@ -21,144 +31,216 @@ if (empty($cartItems)) {
exit();
}
$totalPrice = 0;
$subtotal = 0;
foreach ($cartItems as $item) {
$totalPrice += $item['price'] * $item['quantity'];
$subtotal += $item['price'] * $item['quantity'];
}
// Fetch settings from the database
$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;
$service_fee = ($totalPrice * $service_fee_percentage) / 100;
$totalPriceWithFees = $totalPrice + $delivery_fee + $service_fee;
$discount_amount = $_SESSION['discount_amount'] ?? 0;
$totalPrice = $subtotal + $delivery_fee + $service_fee - $discount_amount;
$_SESSION['total_price'] = $totalPrice;
include 'header.php';
?>
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo $paypalClientId; ?>&currency=USD"></script>
<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="container mt-5">
<h2 class="text-center mb-4">Checkout</h2>
<div class="row">
<div class="col-md-7">
<h4>Delivery Information</h4>
<form id="payment-form" action="create_stripe_session.php" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Full Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="address" class="form-label">Address</label>
<input type="text" class="form-control" id="address" name="address" required>
</div>
<div class="mb-3">
<label for="phone" class="form-label">Phone Number</label>
<input type="text" class="form-control" id="phone" name="phone" required>
<div class="checkout-container">
<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>
<h4 class="mt-4">Payment Method</h4>
<div class="form-check">
<input class="form-check-input" type="radio" name="payment_method" id="stripe-radio" value="stripe" checked>
<label class="form-check-label" for="stripe-radio">
Pay with Credit Card (Stripe)
</label>
<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>
<div class="form-check">
<input class="form-check-input" type="radio" name="payment_method" id="paypal-radio" value="paypal">
<label class="form-check-label" for="paypal-radio">
Pay with PayPal
</label>
<?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>
<button id="stripe-button" type="submit" class="btn btn-primary mt-3">Proceed to Payment</button>
<?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 id="paypal-button-container" class="mt-3" style="display: none;"></div>
</div>
<div class="col-md-5">
<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>
<div class="checkout-summary">
<h4>Order Summary</h4>
<ul class="list-group mb-3">
<div class="summary-items">
<?php foreach ($cartItems as $item): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($item['name']); ?> (x<?php echo $item['quantity']; ?>)
<span>$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
</li>
<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; ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
Delivery Fee
</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>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Service Fee (<?php echo htmlspecialchars($service_fee_percentage); ?>%)
</div>
<div class="summary-line">
<span>Service Fee</span>
<span>$<?php echo number_format($service_fee, 2); ?></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center fw-bold">
Total
<span>$<?php echo number_format($totalPriceWithFees, 2); ?></span>
</li>
</ul>
</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 form = document.getElementById('payment-form');
const stripeButton = document.getElementById('stripe-button');
const paypalButtonContainer = document.getElementById('paypal-button-container');
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');
function togglePaymentMethod() {
if (paypalRadio.checked) {
stripeButton.style.display = 'none';
paypalButtonContainer.style.display = 'block';
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', togglePaymentMethod);
paypalRadio.addEventListener('change', togglePaymentMethod);
stripeRadio.addEventListener('change', togglePaymentButtons);
paypalRadio.addEventListener('change', togglePaymentButtons);
// Initial check
togglePaymentMethod();
document.querySelectorAll('.payment-method-card').forEach(card => {
card.addEventListener('click', () => {
card.querySelector('input[type="radio"]').checked = true;
togglePaymentButtons();
});
});
togglePaymentButtons();
// PayPal integration
paypal.Buttons({
createOrder: function(data, actions) {
// Basic validation
if (!document.getElementById('name').value || !document.getElementById('address').value || !document.getElementById('phone').value) {
alert('Please fill out the delivery information before proceeding.');
return false;
}
return actions.order.create({
purchase_units: [{
amount: {
value: '<?php echo number_format($totalPriceWithFees, 2, '.', ''); ?>'
value: '<?php echo number_format($totalPrice, 2, '.', ''); ?>'
}
}]
});
},
onApprove: function(data, actions) {
// Capture delivery info and submit
const name = document.getElementById('name').value;
const address = document.getElementById('address').value;
const phone = document.getElementById('phone').value;
const formData = new FormData();
formData.append('orderID', data.orderID);
formData.append('name', name);
formData.append('address', address);
formData.append('phone', phone);
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',
@ -169,7 +251,7 @@ document.addEventListener('DOMContentLoaded', function () {
alert(details.error);
window.location.href = 'payment-cancel.php';
} else {
window.location.href = 'order_confirmation.php';
window.location.href = 'order_confirmation.php?order_id=' + details.order_id;
}
});
},

View File

@ -9,13 +9,32 @@ require_once 'includes/api_keys.php';
$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();
}
$checkout_session = \Stripe\Checkout\Session::create([
$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' => [
@ -30,10 +49,13 @@ $checkout_session = \Stripe\Checkout\Session::create([
'mode' => 'payment',
'success_url' => 'http://localhost:8080/payment-success.php?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'http://localhost:8080/payment-cancel.php',
'metadata' => [
'user_id' => $user_id,
'coupon_id' => $coupon_id
]
]);
'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

@ -13,6 +13,7 @@ function db() {
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return $pdo;

View File

@ -2,26 +2,47 @@
<?php include 'hero.php'; ?>
<div class="container">
<form action="index.php" method="get" id="filter-form">
<!-- Hidden search field to persist search query -->
<input type="hidden" name="search" value="<?= isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '' ?>">
<h2 class="page-title">Explore Cuisines</h2>
<div class="cuisine-carousel">
<section class="categories-section">
<h2 class="page-title">Discover by Category</h2>
<div class="category-grid">
<?php
$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'] : [];
$cuisines = $cuisine_stmt->fetchAll(PDO::FETCH_ASSOC);
// Placeholder images - we'll make these dynamic later
$cuisine_images = [
'American' => 'assets/images/pexels/1639557.jpg',
'Asian' => 'assets/images/pexels/2347311.jpg',
'BBQ' => 'assets/images/pexels/161963.jpg',
'Breakfast' => 'assets/images/pexels/376464.jpg',
'Cafe' => 'assets/images/pexels/312418.jpg',
'Dessert' => 'assets/images/pexels/1099680.jpg',
'Fast Food' => 'assets/images/pexels/1633578.jpg',
'Healthy' => 'assets/images/pexels/1640777.jpg',
'Indian' => 'assets/images/pexels/958545.jpg',
'Italian' => 'assets/images/pexels/1260968.jpg',
'Mexican' => 'assets/images/pexels/461198.jpg',
'Pizza' => 'assets/images/pexels/1146760.jpg',
'Seafood' => 'assets/images/pexels/1639565.jpg',
'Vegetarian' => 'assets/images/pexels/1143754.jpg',
];
foreach ($all_cuisines as $cuisine): ?>
<div class="cuisine-card">
<input type="checkbox" name="cuisines[]" value="<?= $cuisine['id'] ?>" id="cuisine-<?= $cuisine['id'] ?>" <?= in_array($cuisine['id'], $selected_cuisines) ? 'checked' : '' ?> onchange="this.form.submit()">
<label for="cuisine-<?= $cuisine['id'] ?>">
<?= htmlspecialchars($cuisine['name']) ?>
</label>
</div>
foreach ($cuisines as $cuisine):
$image_url = $cuisine_images[$cuisine['name']] ?? 'https://picsum.photos/600?random=' . $cuisine['id'];
?>
<a href="index.php?cuisine=<?= $cuisine['id'] ?>" class="category-card">
<img src="<?= htmlspecialchars($image_url) ?>" alt="<?= htmlspecialchars($cuisine['name']) ?>">
<div class="category-card-overlay"></div>
<h3><?= htmlspecialchars($cuisine['name']) ?></h3>
</a>
<?php endforeach; ?>
</div>
</section>
<form action="index.php" method="get" id="filter-form">
<input type="hidden" name="search" value="<?= isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '' ?>">
<?php if (isset($_GET['cuisine'])): ?>
<input type="hidden" name="cuisine" value="<?= htmlspecialchars($_GET['cuisine']) ?>">
<?php endif; ?>
<div class="filter-bar-new">
<div class="form-group">
@ -44,7 +65,19 @@
</div>
</form>
<h2 class="page-title mt-4">All Restaurants</h2>
<?php
$page_title = "All Restaurants";
if (isset($_GET['cuisine']) && !empty($_GET['cuisine'])) {
$cuisine_id = $_GET['cuisine'];
$cuisine_stmt = db()->prepare("SELECT name FROM cuisines WHERE id = ?");
$cuisine_stmt->execute([$cuisine_id]);
$cuisine_name = $cuisine_stmt->fetchColumn();
if ($cuisine_name) {
$page_title = htmlspecialchars($cuisine_name) . " Restaurants";
}
}
?>
<h2 class="page-title mt-4"><?= $page_title ?></h2>
<section class="restaurant-list">
<div class="restaurant-grid" id="restaurant-grid">
<?php
@ -55,7 +88,7 @@
$params = [];
// Join with restaurant_cuisines if filtering by cuisine
if (!empty($selected_cuisines)) {
if (isset($_GET['cuisine']) && !empty($_GET['cuisine'])) {
$sql .= " JOIN restaurant_cuisines rc ON r.id = rc.restaurant_id";
}
@ -68,10 +101,9 @@
}
// Append cuisine filter
if (!empty($selected_cuisines)) {
$placeholders = implode(',', array_fill(0, count($selected_cuisines), '?'));
$where_clauses[] = "rc.cuisine_id IN ($placeholders)";
$params = array_merge($params, $selected_cuisines);
if (isset($_GET['cuisine']) && !empty($_GET['cuisine'])) {
$where_clauses[] = "rc.cuisine_id = ?";
$params[] = $_GET['cuisine'];
}
// Append "Open Now" filter
@ -291,8 +323,8 @@ document.addEventListener('DOMContentLoaded', function() {
// On page load, check if location is in session
<?php if (isset($_SESSION['delivery_location'])):
updateLocationUI(<?php echo $_SESSION['delivery_location']['lat']; ?>, <?php echo $_SESSION['delivery_location']['lng']; ?>);
<?php endif; ?>
echo "updateLocationUI(" . $_SESSION['delivery_location']['lat'] . ", " . $_SESSION['delivery_location']['lng'] . ");";
endif; ?>
});
</script>

108
menu.php
View File

@ -69,111 +69,85 @@ try {
}
?>
<div class="container mt-5">
<?php if ($restaurant): ?>
<div class="row mb-4 align-items-center">
<div class="col-md-8">
<div class="restaurant-hero-menu" style="background-image: url('<?php echo htmlspecialchars($restaurant['image_url']); ?>');">
<div class="restaurant-hero-menu-content">
<h1><?php echo htmlspecialchars($restaurant['name']); ?></h1>
<p><?php echo htmlspecialchars(implode(', ', $restaurant['cuisines'])); ?></p>
<div class="d-flex align-items-center">
<h1 class="display-4 mb-0"><?php echo htmlspecialchars($restaurant['name']); ?></h1>
<span class="h4 text-warning me-2"><?php echo $average_rating; ?> ★</span>
<span class="text-white">(<?php echo count($ratings); ?> reviews)</span>
<?php if (isset($_SESSION['user_id'])) : ?>
<form action="toggle_favorite.php" method="post" class="ms-4">
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
<button type="submit" class="btn <?php echo $is_favorite ? 'btn-danger' : 'btn-outline-danger'; ?>">
<?php echo $is_favorite ? '♥ Remove from Favorites' : '♡ Add to Favorites'; ?>
<button type="submit" class="btn <?php echo $is_favorite ? 'btn-danger' : 'btn-outline-light'; ?>">
<?php echo $is_favorite ? '♥ Favorited' : '♡ Add to Favorites'; ?>
</button>
</form>
<?php endif; ?>
</div>
<p class="lead text-muted"><?php echo htmlspecialchars(implode(', ', $restaurant['cuisines'])); ?></p>
<div class="d-flex align-items-center">
<span class="h4 text-warning me-2"><?php echo $average_rating; ?> ★</span>
<span class="text-muted">(<?php echo count($ratings); ?> reviews)</span>
</div>
</div>
<div class="col-md-4">
<?php if (!empty($restaurant['image_url'])): ?>
<img src="<?php echo htmlspecialchars($restaurant['image_url']); ?>" class="img-fluid rounded shadow-sm" alt="Image of <?php echo htmlspecialchars($restaurant['name']); ?>">
<?php endif; ?>
</div>
</div>
</div>
<hr>
<h2 class="mt-5 mb-4">Menu</h2>
<div class="container mt-5">
<div class="row">
<div class="col-lg-8">
<h2 class="mb-4">Menu</h2>
<div class="menu-grid">
<?php if ($menu_items): ?>
<?php foreach ($menu_items as $item): ?>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100 shadow-sm border-light">
<?php if (!empty($item['image_url'])): ?>
<img src="<?php echo htmlspecialchars($item['image_url']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($item['name']); ?>" style="height: 200px; object-fit: cover;">
<?php endif; ?>
<div class="card-body d-flex flex-column">
<h5 class="card-title"><?php echo htmlspecialchars($item['name']); ?></h5>
<p class="card-text text-muted flex-grow-1"><?php echo htmlspecialchars($item['description']); ?></p>
<p class="card-text h4 text-success">$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></p>
<form action="cart_actions.php" method="post" class="mt-auto">
<div class="menu-item-card">
<div class="menu-item-card-content">
<h3><?php echo htmlspecialchars($item['name']); ?></h3>
<p class="description"><?php echo htmlspecialchars($item['description']); ?></p>
<div class="d-flex justify-content-between align-items-center">
<span class="price">$<?php echo htmlspecialchars(number_format($item['price'], 2)); ?></span>
<form action="cart_actions.php" method="post" class="add-to-cart-form">
<input type="hidden" name="action" value="add">
<input type="hidden" name="restaurant_id" value="<?php echo $restaurant_id; ?>">
<input type="hidden" name="menu_item_id" value="<?php echo $item['id']; ?>">
<div class="input-group">
<input type="number" name="quantity" class="form-control" value="1" min="1">
<button type="submit" class="btn btn-primary">Add to Cart</button>
<input type="number" name="quantity" class="form-control quantity-input" value="1" min="1">
<button type="submit" class="btn btn-primary add-to-cart-btn">Add</button>
</div>
</form>
</div>
</div>
<?php if (!empty($item['image_url'])): ?>
<img src="<?php echo htmlspecialchars($item['image_url']); ?>" class="menu-item-image" alt="<?php echo htmlspecialchars($item['name']); ?>">
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="col">
<p class="alert alert-info">This restaurant has no menu items yet.</p>
</div>
<?php endif; ?>
</div>
<hr class="my-5">
<div class="row">
<div class="col-lg-8 mx-auto">
<h2 class="mb-4">Reviews & Ratings</h2>
<?php if (isset($_SESSION['user_id']) && $rating_count > 0): ?>
<div class="alert alert-light">
You have already reviewed this restaurant.
</div>
<?php elseif (isset($_SESSION['user_id'])): ?>
<div class="alert alert-info">
<a href="leave_review.php?order_id=ORDER_ID_PLACEHOLDER">Leave a review</a> for a completed order.
</div>
<?php else: ?>
<div class="alert alert-info">
<a href="login.php?redirect_url=<?php echo urlencode($_SERVER['REQUEST_URI']); ?>">Log in</a> to see reviews or leave your own.
</div>
<?php endif; ?>
<div class="col-lg-4">
<div class="reviews-section">
<h2 class="mb-4">Reviews</h2>
<?php if ($ratings): ?>
<?php foreach ($ratings as $rating): ?>
<div class="card mb-3">
<div class="card-body">
<div class="d-flex justify-content-between">
<h5 class="card-title"><?php echo htmlspecialchars($rating['user_name']); ?></h5>
<?php foreach (array_slice($ratings, 0, 3) as $rating): ?>
<div class="review-card">
<div class="review-header">
<strong><?php echo htmlspecialchars($rating['user_name']); ?></strong>
<span class="text-warning"><?php echo str_repeat('★', $rating['rating']) . str_repeat('☆', 5 - $rating['rating']); ?></span>
</div>
<p class="card-text"><?php echo nl2br(htmlspecialchars($rating['review'])); ?></p>
<p class="card-text"><small class="text-muted"><?php echo date('F j, Y, g:i a', strtotime($rating['created_at'])); ?></small></p>
</div>
<p class="review-text">"<?php echo nl2br(htmlspecialchars($rating['review'])); ?>"</p>
<small class="text-muted"><?php echo date('F Y', strtotime($rating['created_at'])); ?></small>
</div>
<?php endforeach; ?>
<?php else: ?>
<p>This restaurant has no reviews yet. Be the first!</p>
<p>This restaurant has no reviews yet.</p>
<?php endif; ?>
</div>
</div>
<?php if (isset($_SESSION['user_id'])): ?>
<a href="leave_review.php?restaurant_id=<?php echo $restaurant_id; ?>" class="btn btn-outline-primary mt-3">Leave a Review</a>
<?php else: ?>
<p class="alert alert-warning">Restaurant not found.</p>
<p class="mt-3"><a href="login.php?redirect_url=<?php echo urlencode($_SERVER['REQUEST_URI']); ?>">Log in</a> to leave a review.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php require_once 'footer.php'; ?>

View File

@ -0,0 +1,15 @@
DROP TABLE IF EXISTS cart CASCADE;
DROP TABLE IF EXISTS ratings CASCADE;
DROP TABLE IF EXISTS favorite_restaurants CASCADE;
DROP TABLE IF EXISTS driver_assignments CASCADE;
DROP TABLE IF EXISTS order_items CASCADE;
DROP TABLE IF EXISTS orders CASCADE;
DROP TABLE IF EXISTS menu_items CASCADE;
DROP TABLE IF EXISTS special_promotions CASCADE;
DROP TABLE IF EXISTS coupons CASCADE;
DROP TABLE IF EXISTS restaurants CASCADE;
DROP TABLE IF EXISTS cuisines CASCADE;
DROP TABLE IF EXISTS drivers CASCADE;
DROP TABLE IF EXISTS password_resets CASCADE;
DROP TABLE IF EXISTS settings CASCADE;
DROP TABLE IF EXISTS users CASCADE;

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS users;

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS cart (
id SERIAL PRIMARY KEY,
user_id INT,
menu_item_id INT NOT NULL,
quantity INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS ratings (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
order_id INT NOT NULL,
rating INT NOT NULL,
comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -1,18 +1,17 @@
CREATE TABLE IF NOT EXISTS `orders` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`restaurant_id` INT NOT NULL,
`total_price` DECIMAL(10, 2) NOT NULL,
`status` VARCHAR(50) NOT NULL DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
restaurant_id INT NOT NULL,
total_price DECIMAL(10, 2) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `order_items` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`order_id` INT NOT NULL,
`menu_item_id` INT NOT NULL,
`quantity` INT NOT NULL,
`price` DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`)
CREATE TABLE IF NOT EXISTS order_items (
id SERIAL PRIMARY KEY,
order_id INT NOT NULL,
menu_item_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id)
);

View File

@ -1 +0,0 @@
ALTER TABLE ratings ADD COLUMN order_id INT;

View File

@ -1 +0,0 @@
ALTER TABLE users ADD COLUMN password VARCHAR(255);

View File

@ -1 +0,0 @@
ALTER TABLE orders ADD COLUMN status VARCHAR(50) NOT NULL DEFAULT 'Pending';

View File

@ -0,0 +1 @@
ALTER TABLE orders ADD COLUMN token VARCHAR(255) UNIQUE;

View File

@ -0,0 +1,2 @@
ALTER TABLE "orders" ALTER COLUMN "user_id" DROP NOT NULL;
ALTER TABLE "orders" ADD COLUMN "guest_name" VARCHAR(255) NULL, ADD COLUMN "guest_email" VARCHAR(255) NULL;

View File

@ -1 +1 @@
INSERT INTO users (email, role, password, is_admin) VALUES ('admin@example.com', 'owner', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', TRUE) ON CONFLICT (email) DO UPDATE SET role = 'owner', password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', is_admin = TRUE;
INSERT INTO users (name, email, role, password, is_admin) VALUES ('Admin User', 'admin@example.com', 'owner', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', TRUE) ON CONFLICT (email) DO UPDATE SET name = 'Admin User', role = 'owner', password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', is_admin = TRUE;

View File

@ -1,7 +0,0 @@
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -1,2 +0,0 @@
ALTER TABLE `cart` ADD `session_id` VARCHAR(255) NULL AFTER `user_id`;
ALTER TABLE `cart` MODIFY `user_id` INT NULL;

View File

@ -0,0 +1,2 @@
ALTER TABLE cart ADD COLUMN session_id VARCHAR(255) NULL;
ALTER TABLE cart ALTER COLUMN user_id DROP NOT NULL;

View File

@ -2,31 +2,128 @@
session_start();
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
$order_id = $_SESSION['order_id'] ?? $_GET['order_id'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
$guest_token = $_SESSION['token'] ?? $_GET['token'] ?? null;
if (!isset($_GET['id'])) {
if (!$order_id) {
header("Location: index.php");
exit();
}
$orderId = $_GET['id'];
$pdo = db();
$order = null;
if ($user_id) {
$stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ? AND user_id = ?");
$stmt->execute([$order_id, $user_id]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
} elseif ($guest_token) {
$stmt = $pdo->prepare("SELECT * FROM orders WHERE id = ? AND token = ?");
$stmt->execute([$order_id, $guest_token]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$order) {
// If the order is not found or doesn't belong to the user/guest, redirect.
header("Location: index.php");
exit();
}
// Fetch order items
$itemsStmt = $pdo->prepare("
SELECT oi.quantity, mi.name, mi.price
FROM order_items oi
JOIN menu_items mi ON oi.menu_item_id = mi.id
WHERE oi.order_id = :order_id
");
$itemsStmt->bindParam(':order_id', $order_id);
$itemsStmt->execute();
$orderItems = $itemsStmt->fetchAll(PDO::FETCH_ASSOC);
// Determine the correct tracking URL
$tracking_url = "order_status.php?order_id=" . $order_id;
if (!$user_id && $guest_token) {
$tracking_url .= "&token=" . $guest_token;
}
include 'header.php';
?>
<div class="container mt-5">
<div class="container mt-5 mb-5">
<div class="row d-flex justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-header bg-success text-white text-center">
<h2 class="mb-0">Thank You for Your Order!</h2>
</div>
<div class="card-body">
<div class="text-center mb-4">
<p class="lead">Your order has been placed successfully.</p>
<p>Your Order ID is: <strong><?php echo htmlspecialchars($order['id']); ?></strong></p>
</div>
<div class="row">
<div class="col-md-8 offset-md-2 text-center">
<h2 class="mb-4">Thank You for Your Order!</h2>
<p>Your order has been placed successfully.</p>
<p>Your Order ID is: <strong><?php echo $orderId; ?></strong></p>
<p>We have received your order and will begin processing it shortly.</p>
<a href="index.php" class="btn btn-primary">Continue Shopping</a>
<div class="col-md-6">
<h5>Delivery Details</h5>
<p>
<strong>Name:</strong> <?php echo htmlspecialchars($order['guest_name'] ?? 'N/A'); ?><br>
<strong>Address:</strong> <?php echo htmlspecialchars($order['delivery_address']); ?><br>
<strong>Phone:</strong> <?php echo htmlspecialchars($order['phone_number']); ?>
</p>
</div>
<div class="col-md-6 text-md-end">
<h5>Order Summary</h5>
<p>
<strong>Date:</strong> <?php echo date("F j, Y, g:i a", strtotime($order['created_at'])); ?><br>
<strong>Status:</strong> <span class="badge bg-warning text-dark"><?php echo htmlspecialchars(ucfirst($order['status'])); ?></span>
</p>
</div>
</div>
<h5 class="mt-4">Items Ordered</h5>
<ul class="list-group mb-3">
<?php foreach ($orderItems as $item): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($item['name']); ?> (x<?php echo $item['quantity']; ?>)
<span>$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
</li>
<?php endforeach; ?>
</ul>
<ul class="list-group mb-4">
<li class="list-group-item d-flex justify-content-between">
<span>Subtotal</span>
<span>$<?php echo number_format($order['total_price'] - ($order['discount_amount'] ?? 0), 2); ?></span>
</li>
<?php if (isset($order['discount_amount']) && $order['discount_amount'] > 0): ?>
<li class="list-group-item d-flex justify-content-between">
<span>Discount</span>
<span class="text-success">-$<?php echo number_format($order['discount_amount'], 2); ?></span>
</li>
<?php endif; ?>
<li class="list-group-item d-flex justify-content-between fw-bold">
<span>Total</span>
<span>$<?php echo number_format($order['total_price'], 2); ?></span>
</li>
</ul>
<div class="text-center order-confirmation-actions">
<p>We've received your order and will begin processing it shortly. You can track the progress of your order using the button below.</p>
<a href="index.php" class="btn btn-secondary">Continue Shopping</a>
<a href="<?php echo $tracking_url; ?>" class="btn btn-primary">Track Order</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include 'footer.php'; ?>
<?php
include 'footer.php';
// Clear session variables for the next order
unset($_SESSION['order_id']);
if(isset($_SESSION['token'])){
unset($_SESSION['token']);
}
?>

View File

@ -1,59 +1,68 @@
<?php
session_start();
require_once 'db/config.php';
include 'header.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
// Allow guest access with a token
$order_id = $_GET['order_id'] ?? null;
$token = $_GET['token'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
if (!$order_id) {
header("Location: index.php");
exit();
}
if (!isset($_GET['order_id'])) {
echo "<div class='container mt-5'><p>No order specified.</p></div>";
include 'footer.php';
exit();
}
$order_id = $_GET['order_id'];
$user_id = $_SESSION['user_id'];
$pdo = db();
$order = null;
// Fetch order details to ensure the user owns this order
$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);
if ($user_id) {
// User is logged in, verify order belongs to them
$stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.user_id = ?");
$stmt->execute([$order_id, $user_id]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
} elseif ($token) {
// Guest access, verify token
$stmt = $pdo->prepare("SELECT o.*, r.name as restaurant_name FROM orders o JOIN restaurants r ON o.restaurant_id = r.id WHERE o.id = ? AND o.guest_token = ?");
$stmt->execute([$order_id, $token]);
$order = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$order) {
echo "<div class='container mt-5'><p>Order not found or you do not have permission to view it.</p></div>";
include 'header.php';
echo "<div class='container mt-5'><div class='alert alert-danger'>Order not found or you do not have permission to view it.</div></div>";
include 'footer.php';
exit();
}
// Fetch order items
$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);
$stmt = $pdo->prepare("SELECT oi.*, mi.name as item_name FROM order_items oi JOIN menu_items mi ON oi.menu_item_id = mi.id WHERE oi.order_id = ?");
$stmt->execute([$order_id]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
include 'header.php';
?>
<div class="container mt-5">
<h2>Order Status for #<?php echo $order['id']; ?></h2>
<hr>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Live Status</h5>
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card shadow-sm mb-4">
<div class="card-body text-center">
<h1 class="card-title fw-bold">Thank You For Your Order!</h1>
<p class="text-muted">Order #<?php echo $order['id']; ?></p>
<p><strong>Restaurant:</strong> <?php echo htmlspecialchars($order['restaurant_name']); ?></p>
<p><strong>Order Date:</strong> <?php echo date("F j, Y, g:i a", strtotime($order['created_at'])); ?></p>
<div id="order-status-container">
<p><strong>Status:</strong> <span class="badge bg-primary fs-6" id="order-status"><?php echo htmlspecialchars(ucwords($order['status'])); ?></span></p>
</div>
<div class="progress" style="height: 25px;">
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<h4 class="card-title text-center mb-4">Order Status</h4>
<div id="order-status-timeline">
<!-- Timeline will be dynamically generated by JavaScript -->
</div>
</div>
</div>
<div class="card">
<div class="card shadow-sm mt-4">
<div class="card-body">
<h5 class="card-title">Order Summary</h5>
<ul class="list-group list-group-flush">
@ -63,60 +72,102 @@ $items = $p_items->fetchAll(PDO::FETCH_ASSOC);
<span>$<?php echo number_format($item['price'] * $item['quantity'], 2); ?></span>
</li>
<?php endforeach; ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<strong>Total</strong>
<strong>$<?php echo number_format($order['total_price'], 2); ?></strong>
<li class="list-group-item d-flex justify-content-between align-items-center fw-bold">
Total
<span>$<?php echo number_format($order['total_price'], 2); ?></span>
</li>
</ul>
</div>
</div>
<a href="order_history.php" class="btn btn-secondary mt-3">Back to Order History</a>
<div class="text-center mt-4">
<a href="index.php" class="btn btn-primary">Back to Home</a>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const orderId = <?php echo $order_id; ?>;
const statusElement = document.getElementById('order-status');
const progressBar = document.getElementById('progress-bar');
const token = '<?php echo $token; ?>';
const timelineContainer = document.getElementById('order-status-timeline');
const currentStatus = '<?php echo $order['status']; ?>';
const statusToProgress = {
'Pending': 10,
'Preparing': 40,
'Out For Delivery': 75,
'Delivered': 100,
'Cancelled': 0
};
const statuses = [
{ name: 'Pending', desc: 'Your order has been placed and is waiting for the restaurant to accept it.' },
{ name: 'Preparing', desc: 'The restaurant is preparing your food.' },
{ name: 'Out For Delivery', desc: 'A driver is on their way to you.' },
{ name: 'Delivered', desc: 'Your order has been delivered. Enjoy!' }
];
function updateProgress(status) {
const progress = statusToProgress[status] || 0;
progressBar.style.width = progress + '%';
progressBar.textContent = status;
const cancelledStatus = { name: 'Cancelled', desc: 'This order has been cancelled.' };
if (status === 'Delivered') {
progressBar.classList.remove('progress-bar-animated', 'bg-primary');
progressBar.classList.add('bg-success');
} else if (status === 'Cancelled') {
progressBar.classList.remove('progress-bar-animated', 'bg-primary');
progressBar.classList.add('bg-danger');
} else {
progressBar.classList.remove('bg-success', 'bg-danger');
progressBar.classList.add('bg-primary', 'progress-bar-animated');
function renderTimeline(status) {
timelineContainer.innerHTML = '';
let activeIndex = statuses.findIndex(s => s.name.toLowerCase() === status.toLowerCase());
if (status.toLowerCase() === 'cancelled') {
const item = document.createElement('div');
item.className = 'timeline-item timeline-cancelled';
item.innerHTML = `
<div class="timeline-icon"><i class="fas fa-times-circle"></i></div>
<div class="timeline-content">
<h5 class="fw-bold">${cancelledStatus.name}</h5>
<p class="text-muted">${cancelledStatus.desc}</p>
</div>
`;
timelineContainer.appendChild(item);
return;
}
statuses.forEach((s, index) => {
const item = document.createElement('div');
let itemClass = 'timeline-item';
let icon = '<i class="far fa-circle"></i>';
if (index < activeIndex) {
itemClass += ' timeline-complete';
icon = '<i class="fas fa-check-circle"></i>';
} else if (index === activeIndex) {
itemClass += ' timeline-active';
icon = '<i class="fas fa-dot-circle"></i>';
}
item.className = itemClass;
item.innerHTML = `
<div class="timeline-icon">${icon}</div>
<div class="timeline-content">
<h5 class="fw-bold">${s.name}</h5>
<p class="text-muted">${s.desc}</p>
</div>
`;
timelineContainer.appendChild(item);
});
}
function fetchStatus() {
fetch(`api/get_order_status.php?order_id=${orderId}`)
let url = `api/get_order_status.php?order_id=${orderId}`;
if (token) {
url += `&token=${token}`;
}
fetch(url)
.then(response => response.json())
.then(data => {
if (data.status && data.status !== statusElement.textContent) {
statusElement.textContent = data.status;
updateProgress(data.status);
if (data.status) {
renderTimeline(data.status);
} else if(data.error) {
console.error('Error fetching status:', data.error);
timelineContainer.innerHTML = '<div class="alert alert-warning">Could not retrieve order status. Please try again later.</div>';
}
})
.catch(error => console.error('Error fetching status:', error));
.catch(error => {
console.error('Fetch error:', error);
timelineContainer.innerHTML = '<div class="alert alert-danger">An error occurred while trying to update the order status.</div>';
});
}
updateProgress(statusElement.textContent);
renderTimeline(currentStatus);
setInterval(fetchStatus, 10000); // Poll every 10 seconds
});
</script>

View File

@ -11,25 +11,44 @@ if (!isset($_GET['session_id'])) {
$stripe_session_id = $_GET['session_id'];
$pdo = db();
$session_id = session_id();
\Stripe\Stripe::setApiKey($stripeSecretKey);
try {
$checkout_session = \Stripe\Checkout\Session::retrieve($stripe_session_id);
$metadata = $checkout_session->metadata;
$user_id = $metadata->user_id;
$coupon_id = $metadata->coupon_id;
$user_id = $metadata->user_id ?? null;
$is_guest = !$user_id;
if ($checkout_session->payment_status == 'paid') {
// Fetch user's address
$stmt = $pdo->prepare("SELECT address FROM users WHERE id = ?");
$delivery_address = null;
$phone_number = null;
$guest_name = null;
$guest_email = null;
$guest_token = null;
if ($is_guest) {
$guest_name = $metadata->guest_name ?? '';
$guest_email = $metadata->guest_email ?? '';
$delivery_address = $metadata->guest_address ?? 'N/A';
$phone_number = $metadata->guest_phone ?? 'N/A';
$cart_identifier = $session_id;
$cart_column = 'session_id';
$guest_token = $metadata->token ?? null; // Use token from metadata
} else {
$stmt = $pdo->prepare("SELECT address, phone FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
$delivery_address = $user ? $user['address'] : 'N/A';
$phone_number = $user ? $user['phone'] : 'N/A';
$cart_identifier = $user_id;
$cart_column = 'user_id';
}
// Fetch cart items
$stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.user_id = ?");
$stmt->execute([$user_id]);
$stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.$cart_column = ?");
$stmt->execute([$cart_identifier]);
$cart_items = $stmt->fetchAll();
if (empty($cart_items)) {
@ -39,11 +58,12 @@ try {
$total_price = $_SESSION['total_price'] ?? 0;
$discount_amount = $_SESSION['discount_amount'] ?? 0;
$coupon_id = $metadata->coupon_id ?? null;
$restaurant_id = $cart_items[0]['restaurant_id']; // Assuming order from one restaurant
// Create order
$stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, coupon_id, discount_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $stripe_session_id, $delivery_address, $coupon_id, $discount_amount]);
$stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, phone_number, coupon_id, discount_amount, guest_name, guest_email, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $stripe_session_id, $delivery_address, $phone_number, $coupon_id, $discount_amount, $guest_name, $guest_email, $guest_token]);
$order_id = $pdo->lastInsertId();
// Insert order items
@ -53,8 +73,8 @@ try {
}
// Clear cart
$stmt = $pdo->prepare("DELETE FROM cart WHERE user_id = ?");
$stmt->execute([$user_id]);
$stmt = $pdo->prepare("DELETE FROM cart WHERE $cart_column = ?");
$stmt->execute([$cart_identifier]);
// Clear coupon session variables
unset($_SESSION['coupon_id']);
@ -64,8 +84,11 @@ try {
unset($_SESSION['discount_amount']);
unset($_SESSION['subtotal']);
$_SESSION['order_id'] = $order_id;
if ($is_guest) {
$_SESSION['token'] = $guest_token;
}
header("Location: order_confirmation.php");
exit();

View File

@ -5,13 +5,15 @@ require_once 'includes/api_keys.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id']) || !isset($_POST['orderID'])) {
if (!isset($_POST['orderID'])) {
echo json_encode(['error' => 'Invalid request.']);
exit();
}
$orderID = $_POST['orderID'];
$user_id = $_SESSION['user_id'];
$user_id = $_SESSION['user_id'] ?? null;
$is_guest = !$user_id;
$session_id = session_id();
// Helper function to get PayPal access token
function get_paypal_access_token($clientId, $secret, $apiBase) {
@ -56,15 +58,26 @@ $details = json_decode($result);
if (isset($details->status) && $details->status == 'COMPLETED') {
$pdo = db();
// Fetch user's address
$stmt = $pdo->prepare("SELECT address FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
$delivery_address = $user ? $user['address'] : 'N/A';
$delivery_address = $_POST['address'] ?? 'N/A';
$phone_number = $_POST['phone'] ?? 'N/A';
$guest_name = null;
$guest_email = null;
$guest_token = null;
if ($is_guest) {
$guest_name = $_POST['name'] ?? '';
$guest_email = $_POST['email'] ?? '';
$cart_identifier = $session_id;
$cart_column = 'session_id';
$guest_token = bin2hex(random_bytes(16)); // Generate a unique token for guest orders
} else {
$cart_identifier = $user_id;
$cart_column = 'user_id';
}
// Fetch cart items
$stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.user_id = ?");
$stmt->execute([$user_id]);
$stmt = $pdo->prepare("SELECT c.*, mi.price, mi.restaurant_id FROM cart c JOIN menu_items mi ON c.menu_item_id = mi.id WHERE c.$cart_column = ?");
$stmt->execute([$cart_identifier]);
$cart_items = $stmt->fetchAll();
if (empty($cart_items)) {
@ -78,8 +91,8 @@ if (isset($details->status) && $details->status == 'COMPLETED') {
$restaurant_id = $cart_items[0]['restaurant_id']; // Assuming order from one restaurant
// Create order
$stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, coupon_id, discount_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $orderID, $delivery_address, $coupon_id, $discount_amount]);
$stmt = $pdo->prepare("INSERT INTO orders (user_id, restaurant_id, total_price, status, stripe_session_id, delivery_address, phone_number, coupon_id, discount_amount, guest_name, guest_email, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $restaurant_id, $total_price, 'paid', $orderID, $delivery_address, $phone_number, $coupon_id, $discount_amount, $guest_name, $guest_email, $guest_token]);
$order_id = $pdo->lastInsertId();
// Insert order items
@ -89,8 +102,8 @@ if (isset($details->status) && $details->status == 'COMPLETED') {
}
// Clear cart
$stmt = $pdo->prepare("DELETE FROM cart WHERE user_id = ?");
$stmt->execute([$user_id]);
$stmt = $pdo->prepare("DELETE FROM cart WHERE $cart_column = ?");
$stmt->execute([$cart_identifier]);
// Clear coupon session variables
unset($_SESSION['coupon_id']);
@ -101,6 +114,10 @@ if (isset($details->status) && $details->status == 'COMPLETED') {
unset($_SESSION['subtotal']);
$_SESSION['order_id'] = $order_id;
if ($is_guest) {
$_SESSION['token'] = $guest_token;
}
echo json_encode(['success' => true, 'order_id' => $order_id]);
} else {