14
This commit is contained in:
parent
71ee90fe50
commit
7368c83e9a
@ -2,62 +2,93 @@
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
// The request is now multipart/form-data, so we use $_POST and $_FILES
|
||||||
|
$data = $_POST;
|
||||||
|
$files = $_FILES;
|
||||||
|
|
||||||
if (!$data || !isset($data['name']) || !isset($data['guests']) || !isset($data['ingredients'])) {
|
if (!isset($data['name']) || !isset($data['guests']) || !isset($data['ingredients'])) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid input.']);
|
echo json_encode(['success' => false, 'error' => 'Invalid input.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ingredients = json_decode($data['ingredients'], true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid ingredients format.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
$imageUrl = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Handle file upload
|
||||||
|
if (isset($files['image']) && $files['image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$uploadDir = __DIR__ . '/../assets/images/recipes/';
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0777, true);
|
||||||
|
}
|
||||||
|
$filename = uniqid() . '-' . basename($files['image']['name']);
|
||||||
|
$uploadFile = $uploadDir . $filename;
|
||||||
|
|
||||||
|
if (move_uploaded_file($files['image']['tmp_name'], $uploadFile)) {
|
||||||
|
$imageUrl = 'assets/images/recipes/' . $filename;
|
||||||
|
} else {
|
||||||
|
throw new Exception('Failed to move uploaded file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
if (isset($data['id']) && !empty($data['id'])) {
|
if (isset($data['id']) && !empty($data['id'])) {
|
||||||
// Update existing recipe
|
// Update existing recipe
|
||||||
$recipeId = $data['id'];
|
$recipeId = $data['id'];
|
||||||
$category = !empty($data['category']) ? $data['category'] : 'No category';
|
$category = !empty($data['category']) ? $data['category'] : 'No category';
|
||||||
$stmt = $pdo->prepare("UPDATE recipes SET name = ?, guests = ?, category = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$data['name'], $data['guests'], $category, $recipeId]);
|
// Fetch existing image URL if a new one isn't uploaded
|
||||||
|
if ($imageUrl === null) {
|
||||||
|
$stmt = $pdo->prepare("SELECT image_url FROM recipes WHERE id = ?");
|
||||||
|
$stmt->execute([$recipeId]);
|
||||||
|
$existing = $stmt->fetch();
|
||||||
|
$imageUrl = $existing ? $existing['image_url'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE recipes SET name = ?, guests = ?, category = ?, image_url = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$data['name'], $data['guests'], $category, $imageUrl, $recipeId]);
|
||||||
|
|
||||||
// Easiest way to handle ingredients is to delete old ones and insert new ones
|
// Easiest way to handle ingredients is to delete old ones and insert new ones
|
||||||
$stmt = $pdo->prepare("DELETE FROM ingredients WHERE recipe_id = ?");
|
$stmt = $pdo->prepare("DELETE FROM ingredients WHERE recipe_id = ?");
|
||||||
$stmt->execute([$recipeId]);
|
$stmt->execute([$recipeId]);
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit) VALUES (?, ?, ?, ?)");
|
|
||||||
foreach ($data['ingredients'] as $ing) {
|
|
||||||
$stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Insert new recipe
|
// Insert new recipe
|
||||||
$category = !empty($data['category']) ? $data['category'] : 'No category';
|
$category = !empty($data['category']) ? $data['category'] : 'No category';
|
||||||
$stmt = $pdo->prepare("INSERT INTO recipes (name, guests, category) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO recipes (name, guests, category, image_url) VALUES (?, ?, ?, ?)");
|
||||||
$stmt->execute([$data['name'], $data['guests'], $category]);
|
$stmt->execute([$data['name'], $data['guests'], $category, $imageUrl]);
|
||||||
$recipeId = $pdo->lastInsertId();
|
$recipeId = $pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit) VALUES (?, ?, ?, ?)");
|
// Insert ingredients
|
||||||
foreach ($data['ingredients'] as $ing) {
|
$stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit) VALUES (?, ?, ?, ?)");
|
||||||
$stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit']]);
|
foreach ($ingredients as $ing) {
|
||||||
}
|
$stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
// Fetch the newly created recipe to return it to the client
|
// Fetch the newly created/updated recipe to return it to the client
|
||||||
$stmt = $pdo->prepare("SELECT * FROM recipes WHERE id = ?");
|
$stmt = $pdo->prepare("SELECT * FROM recipes WHERE id = ?");
|
||||||
$stmt->execute([$recipeId]);
|
$stmt->execute([$recipeId]);
|
||||||
$recipe = $stmt->fetch();
|
$recipe = $stmt->fetch();
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM ingredients WHERE recipe_id = ?");
|
$stmt = $pdo->prepare("SELECT * FROM ingredients WHERE recipe_id = ?");
|
||||||
$stmt->execute([$recipeId]);
|
$stmt->execute([$recipeId]);
|
||||||
$ingredients = $stmt->fetchAll();
|
$recipe['ingredients'] = $stmt->fetchAll();
|
||||||
$recipe['ingredients'] = $ingredients;
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'recipe' => $recipe]);
|
echo json_encode(['success' => true, 'recipe' => $recipe]);
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (Exception $e) {
|
||||||
$pdo->rollBack();
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ body {
|
|||||||
background-color: #142E35; /* Dark green background */
|
background-color: #142E35; /* Dark green background */
|
||||||
color: #ffffff; /* White text */
|
color: #ffffff; /* White text */
|
||||||
font-family: 'Poppins', sans-serif;
|
font-family: 'Poppins', sans-serif;
|
||||||
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Headings */
|
/* Headings */
|
||||||
@ -27,7 +28,7 @@ body::before {
|
|||||||
width: 110%;
|
width: 110%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle, #C83434 4px, transparent 5px),
|
radial-gradient(circle, #de4950 4px, transparent 5px),
|
||||||
radial-gradient(circle, #142E35 4px, transparent 5px),
|
radial-gradient(circle, #142E35 4px, transparent 5px),
|
||||||
radial-gradient(circle, #FFAFCA 4px, transparent 5px),
|
radial-gradient(circle, #FFAFCA 4px, transparent 5px),
|
||||||
radial-gradient(circle, #ffff24 4px, transparent 5px),
|
radial-gradient(circle, #ffff24 4px, transparent 5px),
|
||||||
@ -103,8 +104,8 @@ body::before {
|
|||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.btn-primary, .btn-danger {
|
.btn-primary, .btn-danger {
|
||||||
background-color: #C83434 !important; /* Coral red */
|
background-color: #de4950 !important; /* Coral red */
|
||||||
border-color: #C83434 !important;
|
border-color: #de4950 !important;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 12px 30px;
|
padding: 12px 30px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
@ -114,7 +115,7 @@ body::before {
|
|||||||
background-color: #a02929 !important;
|
background-color: #a02929 !important;
|
||||||
border-color: #a02929 !important;
|
border-color: #a02929 !important;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 15px rgba(200, 52, 52, 0.2);
|
box-shadow: 0 4px 15px rgba(222, 73, 80, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
@ -253,8 +254,8 @@ animation: fall linear infinite;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-check-input:checked {
|
.form-check-input:checked {
|
||||||
background-color: #C83434;
|
background-color: #de4950;
|
||||||
border-color: #C83434;
|
border-color: #de4950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check-input:focus {
|
.form-check-input:focus {
|
||||||
@ -386,5 +387,49 @@ animation: fall linear infinite;
|
|||||||
padding: .25rem .5rem;
|
padding: .25rem .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#christmas-decorations-right {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
right: 250px;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 1033;
|
||||||
|
}
|
||||||
|
|
||||||
|
#christmas-decorations-right img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recipe Card Image */
|
||||||
|
.card .card-img-top {
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay category label */
|
||||||
|
.card .recipe-category-label {
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
background-color: rgba(20, 46, 53, 0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recipe Card Animation */
|
||||||
|
.recipe-card-enter {
|
||||||
|
animation: fade-in 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,12 +20,11 @@ const app = {
|
|||||||
app.dom.recipeCardsContainer.innerHTML = '<div class="col-12"><p class="text-center text-danger">Could not connect to the server.</p></div>';
|
app.dom.recipeCardsContainer.innerHTML = '<div class="col-12"><p class="text-center text-danger">Could not connect to the server.</p></div>';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async saveRecipe(recipeData) {
|
async saveRecipe(formData) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('api/save_recipe.php', {
|
const response = await fetch('api/save_recipe.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: formData
|
||||||
body: JSON.stringify(recipeData)
|
|
||||||
});
|
});
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -61,14 +60,23 @@ const app = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
recipes.forEach(recipe => {
|
recipes.forEach((recipe, index) => {
|
||||||
const cardCol = document.createElement('div');
|
const cardCol = document.createElement('div');
|
||||||
cardCol.className = 'col-12 mb-3';
|
cardCol.className = 'col-12 mb-3 recipe-card-enter';
|
||||||
cardCol.setAttribute('data-id', recipe.id);
|
cardCol.setAttribute('data-id', recipe.id);
|
||||||
|
cardCol.style.animationDelay = `${index * 0.1}s`;
|
||||||
|
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'card h-100';
|
card.className = 'card h-100';
|
||||||
|
|
||||||
|
if (recipe.image_url) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = recipe.image_url;
|
||||||
|
img.className = 'card-img-top';
|
||||||
|
img.alt = recipe.name;
|
||||||
|
card.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
if (recipe.category) {
|
if (recipe.category) {
|
||||||
const categoryLabel = document.createElement('div');
|
const categoryLabel = document.createElement('div');
|
||||||
categoryLabel.className = 'recipe-category-label';
|
categoryLabel.className = 'recipe-category-label';
|
||||||
@ -90,6 +98,7 @@ const app = {
|
|||||||
const buttonGroup = document.createElement('div');
|
const buttonGroup = document.createElement('div');
|
||||||
buttonGroup.className = 'mt-auto pt-2';
|
buttonGroup.className = 'mt-auto pt-2';
|
||||||
buttonGroup.innerHTML = `
|
buttonGroup.innerHTML = `
|
||||||
|
<button class="btn btn-light btn-sm view-recipe"><i class="bi bi-eye"></i> View</button>
|
||||||
<button class="btn btn-light btn-sm edit-recipe"><i class="bi bi-pencil"></i> Edit</button>
|
<button class="btn btn-light btn-sm edit-recipe"><i class="bi bi-pencil"></i> Edit</button>
|
||||||
<button class="btn btn-danger btn-sm delete-recipe" title="Delete"><i class="bi bi-trash"></i></button>
|
<button class="btn btn-danger btn-sm delete-recipe" title="Delete"><i class="bi bi-trash"></i></button>
|
||||||
`;
|
`;
|
||||||
@ -273,6 +282,7 @@ const app = {
|
|||||||
app.dom.recipeIdInput.value = '';
|
app.dom.recipeIdInput.value = '';
|
||||||
app.dom.recipeNameInput.value = '';
|
app.dom.recipeNameInput.value = '';
|
||||||
app.dom.recipeCategoryInput.value = '';
|
app.dom.recipeCategoryInput.value = '';
|
||||||
|
app.dom.recipeImage.value = '';
|
||||||
app.dom.guestCountInput.value = '1';
|
app.dom.guestCountInput.value = '1';
|
||||||
app.dom.ingredientsContainer.innerHTML = '';
|
app.dom.ingredientsContainer.innerHTML = '';
|
||||||
app.ui.addIngredientRow();
|
app.ui.addIngredientRow();
|
||||||
@ -303,6 +313,42 @@ const app = {
|
|||||||
|
|
||||||
app.dom.recipeNameInput.focus();
|
app.dom.recipeNameInput.focus();
|
||||||
},
|
},
|
||||||
|
populateViewModal(recipeId) {
|
||||||
|
const recipe = app.state.recipes.find(r => r.id == recipeId);
|
||||||
|
if (!recipe) return;
|
||||||
|
|
||||||
|
const modal = document.getElementById('view-recipe-modal');
|
||||||
|
const img = modal.querySelector('img');
|
||||||
|
if (img) {
|
||||||
|
img.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipe.image_url) {
|
||||||
|
const newImg = document.createElement('img');
|
||||||
|
newImg.src = recipe.image_url;
|
||||||
|
newImg.className = 'card-img-top mb-3';
|
||||||
|
newImg.alt = recipe.name;
|
||||||
|
modal.querySelector('.modal-body').prepend(newImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('view-recipe-name').textContent = recipe.name;
|
||||||
|
document.getElementById('view-recipe-category').textContent = recipe.category || 'No category';
|
||||||
|
document.getElementById('view-recipe-guests').textContent = recipe.guests;
|
||||||
|
|
||||||
|
const ingredientsList = document.getElementById('view-recipe-ingredients');
|
||||||
|
ingredientsList.innerHTML = '';
|
||||||
|
if (recipe.ingredients) {
|
||||||
|
recipe.ingredients.forEach(ing => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'list-group-item';
|
||||||
|
li.textContent = `${ing.name} - ${ing.quantity} ${ing.unit}`;
|
||||||
|
ingredientsList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewRecipeModal = new bootstrap.Modal(modal);
|
||||||
|
viewRecipeModal.show();
|
||||||
|
},
|
||||||
getRecipeDataFromForm() {
|
getRecipeDataFromForm() {
|
||||||
const recipeName = app.dom.recipeNameInput.value.trim();
|
const recipeName = app.dom.recipeNameInput.value.trim();
|
||||||
const guests = parseInt(app.dom.guestCountInput.value, 10) || 0;
|
const guests = parseInt(app.dom.guestCountInput.value, 10) || 0;
|
||||||
@ -374,12 +420,23 @@ const app = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', recipeData.name);
|
||||||
|
formData.append('guests', recipeData.guests);
|
||||||
|
formData.append('category', recipeData.category);
|
||||||
|
formData.append('ingredients', JSON.stringify(recipeData.ingredients));
|
||||||
|
|
||||||
const recipeId = app.dom.recipeIdInput.value;
|
const recipeId = app.dom.recipeIdInput.value;
|
||||||
if (recipeId) {
|
if (recipeId) {
|
||||||
recipeData.id = recipeId;
|
formData.append('id', recipeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await app.api.saveRecipe(recipeData);
|
const imageInput = document.getElementById('recipeImage');
|
||||||
|
if (imageInput.files[0]) {
|
||||||
|
formData.append('image', imageInput.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await app.api.saveRecipe(formData);
|
||||||
|
|
||||||
if (data.success && data.recipe) {
|
if (data.success && data.recipe) {
|
||||||
app.api.getRecipes().then(() => {
|
app.api.getRecipes().then(() => {
|
||||||
@ -415,6 +472,10 @@ const app = {
|
|||||||
if (target.classList.contains('edit-recipe')) {
|
if (target.classList.contains('edit-recipe')) {
|
||||||
app.ui.populateFormForEdit(recipeId);
|
app.ui.populateFormForEdit(recipeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target.classList.contains('view-recipe')) {
|
||||||
|
app.ui.populateViewModal(recipeId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.dom.shoppingListContainer.addEventListener('click', function(e) {
|
app.dom.shoppingListContainer.addEventListener('click', function(e) {
|
||||||
@ -652,6 +713,7 @@ const app = {
|
|||||||
recipeCardsContainer: document.getElementById('recipe-cards-container'),
|
recipeCardsContainer: document.getElementById('recipe-cards-container'),
|
||||||
recipeIdInput: document.getElementById('recipeId'),
|
recipeIdInput: document.getElementById('recipeId'),
|
||||||
recipeCategoryInput: document.getElementById('recipeCategory'),
|
recipeCategoryInput: document.getElementById('recipeCategory'),
|
||||||
|
recipeImage: document.getElementById('recipeImage'),
|
||||||
recipeSearchInput: document.getElementById('recipe-search'),
|
recipeSearchInput: document.getElementById('recipe-search'),
|
||||||
categoryFilters: document.getElementById('category-filters'),
|
categoryFilters: document.getElementById('category-filters'),
|
||||||
addProductBtn: document.getElementById('add-product-btn'),
|
addProductBtn: document.getElementById('add-product-btn'),
|
||||||
|
|||||||
BIN
assets/pasted-20251130-191602-0ffc27f4.png
Normal file
BIN
assets/pasted-20251130-191602-0ffc27f4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@ -3,24 +3,40 @@ require_once __DIR__ . '/config.php';
|
|||||||
|
|
||||||
function run_migrations() {
|
function run_migrations() {
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
|
||||||
|
// Ensure migrations table exists
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS migrations (migration VARCHAR(255) NOT NULL, PRIMARY KEY (migration))");
|
||||||
|
|
||||||
|
// Get executed migrations
|
||||||
|
$executedMigrations = $pdo->query("SELECT migration FROM migrations")->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
$migrationsDir = __DIR__ . '/migrations';
|
$migrationsDir = __DIR__ . '/migrations';
|
||||||
$files = glob($migrationsDir . '/*.sql');
|
$files = glob($migrationsDir . '/*.sql');
|
||||||
sort($files);
|
sort($files);
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
echo "Running migration: " . basename($file) . "\n";
|
$migrationName = basename($file);
|
||||||
|
if (in_array($migrationName, $executedMigrations)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Running migration: " . $migrationName . "\n";
|
||||||
$sql = file_get_contents($file);
|
$sql = file_get_contents($file);
|
||||||
try {
|
try {
|
||||||
$pdo->exec($sql);
|
$pdo->exec($sql);
|
||||||
|
|
||||||
|
// Record migration
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
|
||||||
|
$stmt->execute([$migrationName]);
|
||||||
|
|
||||||
echo "Success.\n";
|
echo "Success.\n";
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
echo "Error: " . $e->getMessage() . "\n";
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
// A simple way to track which migrations have been run is needed
|
|
||||||
// For now, we'll just stop on error.
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo "All migrations have been run.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
run_migrations();
|
run_migrations();
|
||||||
|
|
||||||
1
db/migrations/003_add_image_to_recipes.sql
Normal file
1
db/migrations/003_add_image_to_recipes.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE recipes ADD COLUMN image_url VARCHAR(255) DEFAULT NULL;
|
||||||
40
index.php
40
index.php
@ -32,19 +32,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<div id="christmas-decorations-right">
|
||||||
|
<img src="assets/pasted-20251130-191602-0ffc27f4.png" alt="Christmas Decorations" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="snow-container"></div>
|
<div id="snow-container"></div>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark shadow-sm">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand" href="#">Recipe Calculator</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="container my-5">
|
<main class="container my-5">
|
||||||
<div class="text-center mb-5">
|
<div class="text-center mb-5">
|
||||||
<h1 class="display-4">Hey, it's Christmas time!</h1>
|
<h1 class="display-4 mt-4">Hey, it's Christmas time!</h1>
|
||||||
<p class="lead">Let's get your holiday recipes sorted.</p>
|
<p class="lead">Let's get your holiday recipes sorted.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -121,6 +121,10 @@
|
|||||||
<option value="Appetizers">Appetizers</option>
|
<option value="Appetizers">Appetizers</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="recipeImage" class="form-label">Recipe Image</label>
|
||||||
|
<input type="file" class="form-control" id="recipeImage">
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr class="my-4 border-secondary">
|
<hr class="my-4 border-secondary">
|
||||||
|
|
||||||
@ -211,6 +215,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal View Recipe -->
|
||||||
|
<div class="modal fade" id="view-recipe-modal" tabindex="-1" aria-labelledby="view-recipe-modal-label" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="view-recipe-modal-label">View Recipe</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h2 id="view-recipe-name"></h2>
|
||||||
|
<p><strong>Category:</strong> <span id="view-recipe-category"></span></p>
|
||||||
|
<p><strong>Serves:</strong> <span id="view-recipe-guests"></span></p>
|
||||||
|
<hr>
|
||||||
|
<h3>Ingredients</h3>
|
||||||
|
<ul id="view-recipe-ingredients" class="list-group">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer class="text-center py-4 mt-5">
|
<footer class="text-center py-4 mt-5">
|
||||||
<p class="mb-0">© <?php echo date("Y"); ?> Christmas Recipe Calculator. Happy Holidays!</p>
|
<p class="mb-0">© <?php echo date("Y"); ?> Christmas Recipe Calculator. Happy Holidays!</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user