This commit is contained in:
Flatlogic Bot 2025-12-15 09:49:58 +00:00
parent fdcc78312c
commit ac9362bcab
11 changed files with 487 additions and 125 deletions

56
api/favorites.php Normal file
View File

@ -0,0 +1,56 @@
<?php
session_start();
header('Content-Type: application/json');
require_once '../db/config.php';
$response = ['success' => false, 'loggedIn' => false, 'isFavorite' => false, 'message' => ''];
if (!isset($_SESSION['user_id'])) {
$response['message'] = 'You must be logged in to favorite a restaurant.';
echo json_encode($response);
exit;
}
$response['loggedIn'] = true;
$user_id = $_SESSION['user_id'];
$data = json_decode(file_get_contents('php://input'), true);
$restaurant_id = $data['restaurant_id'] ?? null;
if (!$restaurant_id || !is_numeric($restaurant_id)) {
$response['message'] = 'Invalid restaurant ID.';
echo json_encode($response);
exit;
}
$pdo = db();
// Check if it's already a favorite
$stmt = $pdo->prepare("SELECT id FROM favorite_restaurants WHERE user_id = ? AND restaurant_id = ?");
$stmt->execute([$user_id, $restaurant_id]);
$existing_favorite = $stmt->fetch();
if ($existing_favorite) {
// Remove from favorites
$stmt = $pdo->prepare("DELETE FROM favorite_restaurants WHERE id = ?");
if ($stmt->execute([$existing_favorite['id']])) {
$response['success'] = true;
$response['isFavorite'] = false;
$response['message'] = 'Restaurant removed from favorites.';
} else {
$response['message'] = 'Failed to remove from favorites.';
}
} else {
// Add to favorites
$stmt = $pdo->prepare("INSERT INTO favorite_restaurants (user_id, restaurant_id) VALUES (?, ?)");
if ($stmt->execute([$user_id, $restaurant_id])) {
$response['success'] = true;
$response['isFavorite'] = true;
$response['message'] = 'Restaurant added to favorites.';
} else {
$response['message'] = 'Failed to add to favorites.';
}
}
echo json_encode($response);

View File

@ -0,0 +1,8 @@
-- 003_create_users_table.sql
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL UNIQUE,
`password_hash` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,10 @@
-- 004_create_favorite_restaurants_table.sql
CREATE TABLE IF NOT EXISTS `favorite_restaurants` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`restaurant_id` INT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`restaurant_id`) REFERENCES `restaurants`(`id`) ON DELETE CASCADE,
UNIQUE KEY `user_restaurant_unique` (`user_id`, `restaurant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

59
favorites.php Normal file
View File

@ -0,0 +1,59 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
$user_id = $_SESSION['user_id'];
$favorite_restaurants = [];
try {
$pdo = db();
$stmt = $pdo->prepare("
SELECT r.id, r.name, r.cuisine, r.address
FROM restaurants r
JOIN favorite_restaurants fr ON r.id = fr.restaurant_id
WHERE fr.user_id = ?
ORDER BY r.name ASC
");
$stmt->execute([$user_id]);
$favorite_restaurants = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Database error fetching favorites: " . $e->getMessage());
// Optionally, show a friendly error to the user
}
?>
<div class="container my-5">
<h1 class="mb-4">My Favorite Restaurants</h1>
<div class="row">
<?php if (empty($favorite_restaurants)): ?>
<div class="col">
<p class="text-center text-muted">You haven't added any favorite restaurants yet.</p>
<div class="text-center">
<a href="index.php" class="btn btn-primary">Find some restaurants</a>
</div>
</div>
<?php else: ?>
<?php foreach ($favorite_restaurants as $restaurant): ?>
<div class="col-md-4 mb-4">
<div class="card h-100 restaurant-card">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><?= htmlspecialchars($restaurant['name']) ?></h5>
<p class="card-text"><span class="badge bg-secondary"><?= htmlspecialchars($restaurant['cuisine']) ?></span></p>
<p class="card-text text-muted flex-grow-1"><?= htmlspecialchars($restaurant['address']) ?></p>
<a href="menu.php?restaurant_id=<?= $restaurant['id'] ?>" class="btn btn-primary mt-auto">View Menu</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

5
includes/footer.php Normal file
View File

@ -0,0 +1,5 @@
</div> <!-- close container -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

45
includes/header.php Normal file
View File

@ -0,0 +1,45 @@
<?php
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Restaurant Marketplace</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="index.php"><i class="fas fa-utensils"></i> Restaurant Marketplace</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item">
<a class="nav-link" href="favorites.php">My Favorites</a>
</li>
<li class="nav-item">
<span class="nav-link">Welcome, <?php echo htmlspecialchars($_SESSION['user_name']); ?>!</span>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="register.php">Register</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">

119
index.php
View File

@ -1,4 +1,5 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
$restaurants = [];
@ -15,52 +16,14 @@ try {
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Find a Restaurant</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Poppins', sans-serif;
background-color: #F8F9FA;
}
.hero {
background-color: #343A40;
color: #FFFFFF;
padding: 4rem 0;
text-align: center;
}
.restaurant-card {
transition: transform 0.2s, box-shadow 0.2s;
}
.restaurant-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
}
.btn-primary {
background-color: #FF6347;
border-color: #FF6347;
}
.btn-primary:hover {
background-color: #E5533D;
border-color: #E5533D;
}
</style>
</head>
<body>
<header class="hero">
<div class="container">
<h1 class="display-4">Find Your Next Meal</h1>
<p class="lead">Browse through our collection of partner restaurants.</p>
</div>
</header>
<header class="hero">
<div class="container">
<h1 class="display-4">Find Your Next Meal</h1>
<p class="lead">Browse through our collection of partner restaurants.</p>
</div>
</header>
<main class="container my-5">
<main class="container my-5">
<div class="row mb-4">
<div class="col-md-8">
<input type="text" id="searchInput" class="form-control" placeholder="Search by restaurant name...">
@ -99,45 +62,39 @@ try {
<p>No restaurants match your search.</p>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
const searchInput = document.getElementById('searchInput');
const cuisineFilter = document.getElementById('cuisineFilter');
const restaurantList = document.getElementById('restaurantList');
const restaurantItems = restaurantList.querySelectorAll('.restaurant-item');
const noResults = document.getElementById('noResults');
<footer class="text-center text-muted py-4">
<p>&copy; <?= date('Y') ?> Food Marketplace</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const searchInput = document.getElementById('searchInput');
const cuisineFilter = document.getElementById('cuisineFilter');
const restaurantList = document.getElementById('restaurantList');
const restaurantItems = restaurantList.querySelectorAll('.restaurant-item');
const noResults = document.getElementById('noResults');
function filterRestaurants() {
const searchTerm = searchInput.value.toLowerCase();
const cuisineTerm = cuisineFilter.value.toLowerCase();
let resultsFound = false;
function filterRestaurants() {
const searchTerm = searchInput.value.toLowerCase();
const cuisineTerm = cuisineFilter.value.toLowerCase();
let resultsFound = false;
restaurantItems.forEach(item => {
const name = item.dataset.name;
const cuisine = item.dataset.cuisine;
restaurantItems.forEach(item => {
const name = item.dataset.name;
const cuisine = item.dataset.cuisine;
const nameMatch = name.includes(searchTerm);
const cuisineMatch = cuisineTerm === '' || cuisine.includes(cuisineTerm);
const nameMatch = name.includes(searchTerm);
const cuisineMatch = cuisineTerm === '' || cuisine.includes(cuisineTerm);
if (nameMatch && cuisineMatch) {
item.style.display = '';
resultsFound = true;
} else {
item.style.display = 'none';
}
});
if (nameMatch && cuisineMatch) {
item.style.display = '';
resultsFound = true;
} else {
item.style.display = 'none';
}
});
noResults.style.display = resultsFound ? 'none' : '';
}
noResults.style.display = resultsFound ? 'none' : '';
}
searchInput.addEventListener('input', filterRestaurants);
cuisineFilter.addEventListener('change', filterRestaurants);
});
</script>
</body>
</html>
searchInput.addEventListener('input', filterRestaurants);
cuisineFilter.addEventListener('change', filterRestaurants);
});
</script>
<?php require_once 'includes/footer.php'; ?>

84
login.php Normal file
View File

@ -0,0 +1,84 @@
<?php
session_start();
require_once 'db/config.php';
$error_message = '';
if (isset($_SESSION['user_id'])) {
header("Location: index.php");
exit;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$email = trim($_POST['email']);
$password = $_POST['password'];
if (empty($email) || empty($password)) {
$error_message = "Please enter both email and password.";
} else {
$pdo = db();
$stmt = $pdo->prepare("SELECT id, name, password_hash FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
header("Location: index.php");
exit;
} else {
$error_message = "Invalid email or password.";
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="index.php"><i class="fas fa-utensils"></i> Restaurant Marketplace</a>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>Login</h3>
</div>
<div class="card-body">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo $error_message; ?></div>
<?php endif; ?>
<form action="login.php" method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
<div class="card-footer text-center">
Don't have an account? <a href="register.php">Register here</a>.
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

6
logout.php Normal file
View File

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

121
menu.php
View File

@ -1,8 +1,11 @@
<?php
require_once 'includes/header.php';
require_once 'db/config.php';
if (!isset($_GET['restaurant_id']) || !is_numeric($_GET['restaurant_id'])) {
die("A valid restaurant ID is required.");
// Redirect or show a generic error page
header("Location: index.php?error=invalid_restaurant");
exit;
}
$restaurant_id = intval($_GET['restaurant_id']);
@ -15,10 +18,23 @@ try {
$restaurant = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("DB error fetching restaurant: " . $e->getMessage());
// Show a generic error page to the user
die("Error: Could not load restaurant information.");
}
if (!$restaurant) {
die("Restaurant not found.");
// Redirect or show a 404 page
header("Location: index.php?error=not_found");
exit;
}
$is_favorite = false;
if (isset($_SESSION['user_id'])) {
$stmt = $pdo->prepare("SELECT id FROM favorite_restaurants WHERE user_id = ? AND restaurant_id = ?");
$stmt->execute([$_SESSION['user_id'], $restaurant_id]);
if ($stmt->fetch()) {
$is_favorite = true;
}
}
// Fetch menu items
@ -29,6 +45,7 @@ try {
$menu_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("DB error fetching menu items: " . $e->getMessage());
// It's okay to show the restaurant info even if menu fails to load
}
// Group menu items by category
@ -39,44 +56,27 @@ foreach ($menu_items as $item) {
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menu for <?= htmlspecialchars($restaurant['name']) ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Poppins', sans-serif; background-color: #F8F9FA; }
.menu-header {
background: #343A40;
color: white;
padding: 3rem 0;
}
.menu-item {
border-bottom: 1px dashed #E0E0E0;
padding: 1rem 0;
}
.menu-item:last-child {
border-bottom: none;
}
</style>
</head>
<body>
<div class="menu-header text-center">
<div class="container">
<h1 class="display-5"><?= htmlspecialchars($restaurant['name']) ?></h1>
<p class="lead"><?= htmlspecialchars($restaurant['address']) ?></p>
<?php if ($restaurant['phone']): ?>
<p class="text-white-50">Call us at: <?= htmlspecialchars($restaurant['phone']) ?></p>
<?php endif; ?>
<div class="menu-header text-center">
<div class="container">
<h1 class="display-5"><?= htmlspecialchars($restaurant['name']) ?></h1>
<p class="lead"><?= htmlspecialchars($restaurant['address']) ?></p>
<?php if ($restaurant['phone']): ?>
<p class="text-white-50">Call us at: <?= htmlspecialchars($restaurant['phone']) ?></p>
<?php endif; ?>
<a href="index.php" class="btn btn-sm btn-outline-light mt-3"><i class="bi bi-arrow-left"></i> Back to all restaurants</a>
</div>
<?php if (isset($_SESSION['user_id'])):
$btn_class = $is_favorite ? 'btn-danger' : 'btn-outline-warning';
$btn_text = $is_favorite ? '<i class="fas fa-heart-broken"></i> Unfavorite' : '<i class="fas fa-heart"></i> Favorite';
?>
<button id="favoriteBtn" class="btn btn-lg <?= $btn_class ?> mt-3" data-restaurant-id="<?= $restaurant_id ?>">
<?= $btn_text ?>
</button>
<?php endif; ?>
<a href="index.php" class="btn btn-sm btn-outline-light mt-3"><i class="bi bi-arrow-left"></i> Back to all restaurants</a>
</div>
<main class="container my-5">
</div>
<main class="container my-5">
<?php if (empty($menu_by_category)): ?>
<div class="text-center">
<p class="text-muted fs-4">This restaurant hasn't added any menu items yet.</p>
@ -100,10 +100,43 @@ foreach ($menu_items as $item) {
<?php endforeach; ?>
<?php endif; ?>
</main>
<?php require_once 'includes/footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const favoriteBtn = document.getElementById('favoriteBtn');
if (favoriteBtn) {
favoriteBtn.addEventListener('click', function() {
const restaurantId = this.dataset.restaurantId;
fetch('api/favorites.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ restaurant_id: restaurantId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.isFavorite) {
this.classList.remove('btn-outline-warning');
this.classList.add('btn-danger');
this.innerHTML = '<i class="fas fa-heart-broken"></i> Unfavorite';
} else {
this.classList.remove('btn-danger');
this.classList.add('btn-outline-warning');
this.innerHTML = '<i class="fas fa-heart"></i> Favorite';
}
} else {
alert(data.message || 'An error occurred.');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred. Please try again.');
});
});
}
});
</script>
<footer class="text-center text-muted py-4">
<p>&copy; <?= date('Y') ?> Food Marketplace</p>
</footer>
</body>
</html>

99
register.php Normal file
View File

@ -0,0 +1,99 @@
<?php
session_start();
require_once 'db/config.php';
$error_message = '';
$success_message = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$password = $_POST['password'];
$password_confirm = $_POST['password_confirm'];
if (empty($name) || empty($email) || empty($password)) {
$error_message = "Please fill in all fields.";
} elseif ($password !== $password_confirm) {
$error_message = "Passwords do not match.";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error_message = "Invalid email format.";
} else {
$pdo = db();
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
if ($stmt->fetch()) {
$error_message = "An account with this email already exists.";
} else {
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (name, email, password_hash) VALUES (?, ?, ?)");
if ($stmt->execute([$name, $email, $password_hash])) {
$success_message = "Registration successful! You can now <a href='login.php'>log in</a>.";
} else {
$error_message = "An error occurred. Please try again.";
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="index.php"><i class="fas fa-utensils"></i> Restaurant Marketplace</a>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3>Register</h3>
</div>
<div class="card-body">
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo $error_message; ?></div>
<?php endif; ?>
<?php if ($success_message): ?>
<div class="alert alert-success"><?php echo $success_message; ?></div>
<?php else: ?>
<form action="register.php" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="password_confirm" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="password_confirm" name="password_confirm" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
<?php endif; ?>
</div>
<div class="card-footer text-center">
Already have an account? <a href="login.php">Login here</a>.
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>