Good registration page

This commit is contained in:
Flatlogic Bot 2026-01-26 21:49:35 +00:00
parent 7643a96d2f
commit afdfe09b22
5 changed files with 156 additions and 121 deletions

View File

@ -6,19 +6,19 @@ $data = json_decode(file_get_contents('php://input'), true);
$email = $data['email'] ?? ''; $email = $data['email'] ?? '';
$password = $data['password'] ?? ''; $password = $data['password'] ?? '';
if (empty($email) || empty($password)) { if ($email === '' || $password === '') {
echo json_encode(['success' => false, 'error' => 'Email and password are required.']); echo json_encode(['success' => false, 'error' => 'Email and password are required.']);
exit; exit;
} }
try { try {
$pdo = db(); $stmt = db()->prepare("SELECT id, email, password FROM users WHERE email = ?");
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE email = ?");
$stmt->execute([$email]); $stmt->execute([$email]);
$user = $stmt->fetch(); $user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password_hash'])) { if ($user && password_verify($password, $user['password'])) {
login_user($user['id']); $_SESSION['user_id'] = $user['id'];
$_SESSION['email'] = $user['email'];
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
} else { } else {
echo json_encode(['success' => false, 'error' => 'Invalid email or password.']); echo json_encode(['success' => false, 'error' => 'Invalid email or password.']);

View File

@ -6,7 +6,7 @@ $data = json_decode(file_get_contents('php://input'), true);
$email = $data['email'] ?? ''; $email = $data['email'] ?? '';
$password = $data['password'] ?? ''; $password = $data['password'] ?? '';
if (empty($email) || empty($password)) { if ($email === '' || $password === '') {
echo json_encode(['success' => false, 'error' => 'Email and password are required.']); echo json_encode(['success' => false, 'error' => 'Email and password are required.']);
exit; exit;
} }
@ -16,25 +16,19 @@ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit; exit;
} }
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
try { try {
$pdo = db(); $stmt = db()->prepare("INSERT INTO users (email, password) VALUES (?, ?)");
if ($stmt->execute([$email, $hashedPassword])) {
// Check if user already exists $_SESSION['user_id'] = db()->lastInsertId();
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?"); $_SESSION['email'] = $email;
$stmt->execute([$email]);
if ($stmt->fetch()) {
echo json_encode(['success' => false, 'error' => 'Email already registered.']);
exit;
}
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (email, password_hash) VALUES (?, ?)");
$stmt->execute([$email, $password_hash]);
$user_id = $pdo->lastInsertId();
login_user($user_id);
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
}
} catch (PDOException $e) { } catch (PDOException $e) {
if ($e->getCode() == 23000) {
echo json_encode(['success' => false, 'error' => 'Email is already registered.']);
} else {
echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]); echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]);
}
} }

View File

@ -20,20 +20,20 @@ $imageType = $_FILES['image']['type'];
file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Processing image: type=$imageType, size=$imageSize bytes" . PHP_EOL, FILE_APPEND); file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Processing image: type=$imageType, size=$imageSize bytes" . PHP_EOL, FILE_APPEND);
file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Data URL prefix: " . substr("data:$imageType;base64,$imageData", 0, 50) . "..." . PHP_EOL, FILE_APPEND); file_put_contents(__DIR__ . '/scan_debug.log', date('Y-m-d H:i:s') . " Data URL prefix: " . substr("data:$imageType;base64,$imageData", 0, 50) . "..." . PHP_EOL, FILE_APPEND);
$prompt = "Analyze this image. If it's a photo of a dish, identify what it is and generate a possible recipe for it. If it's a photo of a written recipe, extract the information. $prompt = "Analyze this image. If it's a photo of a dish, identify what it is and create a possible recipe for it. If it's a photo of a written recipe, extract the information from it.
Extract or generate the following information in JSON format: Provide the information in JSON format IN ENGLISH:
- name: The name of the dish/recipe - name: Dish/recipe name
- category: One of 'Drinks', 'Breakfast', 'Dinner', 'Appetizers' - category: One of the following values: 'Drinks', 'Breakfast', 'Dinner', 'Appetizers'
- ingredients: An array of objects, each with: - ingredients: Array of objects, each containing:
- name: Name of the ingredient - name: Ingredient name
- quantity: Numeric quantity (float) - quantity: Numeric quantity (float)
- unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'piece', 'pack') - unit: Unit of measurement (e.g., 'g', 'kg', 'ml', 'l', 'pcs', 'pack')
- guests: The default number of guests/servings the recipe is for (integer) - guests: Default number of guests/portions (integer)
Important: Important:
1. The quantities should be for 1 person if possible, or for the number of guests specified. If you're generating a recipe for a dish, default to 1 or 2 guests. 1. Quantities should be specified per 1 person if possible. If you are creating a recipe for a dish, default to 1 or 2 guests.
2. Return ONLY the JSON object. 2. Return ONLY the JSON object.
3. If you can't determine something, provide a best guess or null."; 3. If something cannot be determined, make the most accurate assumption or specify null.";
try { try {
$response = LocalAIApi::createResponse([ $response = LocalAIApi::createResponse([

View File

@ -117,7 +117,7 @@ const app = {
} }
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
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">Failed to connect to the server.</p></div>';
} }
}, },
async saveRecipe(formData) { async saveRecipe(formData) {
@ -191,7 +191,10 @@ const app = {
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';
categoryLabel.textContent = recipe.category; categoryLabel.textContent = recipe.category === 'Drinks' ? 'Drinks' :
recipe.category === 'Breakfast' ? 'Breakfast' :
recipe.category === 'Dinner' ? 'Lunch/Dinner' :
recipe.category === 'Appetizers' ? 'Appetizers' : recipe.category;
titleWrapper.appendChild(categoryLabel); titleWrapper.appendChild(categoryLabel);
} }
@ -280,7 +283,7 @@ const app = {
} }
const item = combinedIngredients.get(key); const item = combinedIngredients.get(key);
item.additionalQty += prod.quantity; item.additionalQty += prod.quantity;
const source = prod.source || 'Additional product'; const source = prod.source || 'Add. product';
if (!item.sources.includes(source)) { if (!item.sources.includes(source)) {
item.sources.push(source); item.sources.push(source);
} }
@ -292,10 +295,10 @@ const app = {
// 3. Group for display // 3. Group for display
const groups = { const groups = {
'Food': { ingredients: [] }, 'Food': { label: 'Food', ingredients: [] },
'Drinks': { ingredients: [] }, 'Drinks': { label: 'Drinks', ingredients: [] },
'Cooking and serving': { ingredients: [] }, 'Cooking and serving': { label: 'Kitchen & serving', ingredients: [] },
'Tableware and consumables': { ingredients: [] } 'Tableware and consumables': { label: 'Tableware & consumables', ingredients: [] }
}; };
combinedIngredients.forEach((item, key) => { combinedIngredients.forEach((item, key) => {
@ -321,7 +324,7 @@ const app = {
const group = groups[groupName]; const group = groups[groupName];
if (group.ingredients.length > 0) { if (group.ingredients.length > 0) {
totalIngredients += group.ingredients.length; totalIngredients += group.ingredients.length;
html += `<h4 class="mt-3">${groupName}</h4>`; html += `<h4 class="mt-3">${group.label}</h4>`;
html += '<ul class="list-group list-group-flush">'; html += '<ul class="list-group list-group-flush">';
group.ingredients.forEach((item, index) => { group.ingredients.forEach((item, index) => {
const totalQty = item.recipeQty + item.additionalQty; const totalQty = item.recipeQty + item.additionalQty;
@ -369,14 +372,14 @@ const app = {
const row = document.createElement('div'); const row = document.createElement('div');
row.className = 'ingredient-row mb-3'; row.className = 'ingredient-row mb-3';
const units = ['g', 'kg', 'ml', 'l', 'piece', 'pack']; const units = ['g', 'kg', 'ml', 'l', 'pcs', 'pack'];
const unitButtons = units.map(u => const unitButtons = units.map(u =>
`<button type="button" class="btn ${ingredient.unit === u ? 'btn-secondary' : 'btn-outline-secondary'} unit-btn">${u}</button>` `<button type="button" class="btn ${ingredient.unit === u ? 'btn-secondary' : 'btn-outline-secondary'} unit-btn">${u}</button>`
).join(''); ).join('');
row.innerHTML = ` row.innerHTML = `
<div class="mb-2"> <div class="mb-2">
<input type="text" class="form-control" placeholder="Ingredient Name" aria-label="Ingredient Name" value="${ingredient.name}"> <input type="text" class="form-control" placeholder="Ingredient name" aria-label="Ingredient Name" value="${ingredient.name}">
</div> </div>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input type="number" class="form-control me-2" placeholder="Qty" aria-label="Quantity" min="0" step="any" value="${ingredient.quantity}" style="width: 100px;"> <input type="number" class="form-control me-2" placeholder="Qty" aria-label="Quantity" min="0" step="any" value="${ingredient.quantity}" style="width: 100px;">
@ -396,9 +399,9 @@ const app = {
app.dom.guestCountInput.value = '1'; app.dom.guestCountInput.value = '1';
app.dom.ingredientsContainer.innerHTML = ''; app.dom.ingredientsContainer.innerHTML = '';
app.ui.addIngredientRow(); app.ui.addIngredientRow();
app.dom.newRecipeBtn.textContent = 'Save Recipe'; app.dom.newRecipeBtn.textContent = 'Save recipe';
app.dom.cancelEditBtn.style.display = 'none'; app.dom.cancelEditBtn.style.display = 'none';
document.getElementById('recipe-form-modal-label').textContent = 'Add a Recipe'; document.getElementById('recipe-form-modal-label').textContent = 'Add recipe';
}, },
populateFormForEdit(recipeId) { populateFormForEdit(recipeId) {
const recipe = app.state.recipes.find(r => r.id == recipeId); const recipe = app.state.recipes.find(r => r.id == recipeId);
@ -416,9 +419,9 @@ const app = {
app.ui.addIngredientRow(); app.ui.addIngredientRow();
} }
app.dom.newRecipeBtn.textContent = 'Update Recipe'; app.dom.newRecipeBtn.textContent = 'Update recipe';
app.dom.cancelEditBtn.style.display = 'block'; app.dom.cancelEditBtn.style.display = 'block';
document.getElementById('recipe-form-modal-label').textContent = 'Edit Recipe'; document.getElementById('recipe-form-modal-label').textContent = 'Edit recipe';
app.dom.recipeFormModal.show(); app.dom.recipeFormModal.show();
app.dom.recipeNameInput.focus(); app.dom.recipeNameInput.focus();
@ -442,7 +445,10 @@ const app = {
} }
document.getElementById('view-recipe-name').textContent = recipe.name; document.getElementById('view-recipe-name').textContent = recipe.name;
document.getElementById('view-recipe-category').textContent = recipe.category || 'No category'; document.getElementById('view-recipe-category').textContent = recipe.category === 'Drinks' ? 'Drinks' :
recipe.category === 'Breakfast' ? 'Breakfast' :
recipe.category === 'Dinner' ? 'Lunch/Dinner' :
recipe.category === 'Appetizers' ? 'Appetizers' : 'No category';
document.getElementById('view-recipe-guests').textContent = recipe.guests; document.getElementById('view-recipe-guests').textContent = recipe.guests;
const ingredientsList = document.getElementById('view-recipe-ingredients'); const ingredientsList = document.getElementById('view-recipe-ingredients');
@ -467,8 +473,10 @@ const app = {
const ingredients = []; const ingredients = [];
const rows = app.dom.ingredientsContainer.querySelectorAll('.ingredient-row'); const rows = app.dom.ingredientsContainer.querySelectorAll('.ingredient-row');
rows.forEach(row => { rows.forEach(row => {
const name = row.querySelector('input[placeholder="Ingredient Name"]').value.trim(); const nameInput = row.querySelector('input[placeholder="Ingredient name"]');
const qty = parseFloat(row.querySelector('input[placeholder="Qty"]').value); const name = nameInput ? nameInput.value.trim() : '';
const qtyInput = row.querySelector('input[placeholder="Qty"]');
const qty = qtyInput ? parseFloat(qtyInput.value) : NaN;
const activeButton = row.querySelector('.unit-selector .btn-secondary'); const activeButton = row.querySelector('.unit-selector .btn-secondary');
const unit = activeButton ? activeButton.textContent.trim() : 'g'; const unit = activeButton ? activeButton.textContent.trim() : 'g';
@ -504,7 +512,7 @@ const app = {
<span class="text-muted">Welcome, ${app.state.user.email}</span> <span class="text-muted">Welcome, ${app.state.user.email}</span>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button class="btn btn-outline-primary btn-sm" id="logout-btn">Logout</button> <button class="btn btn-outline-primary btn-sm" id="logout-btn">Log Out</button>
</li> </li>
`; `;
if (guestView) guestView.classList.add('d-none'); if (guestView) guestView.classList.add('d-none');
@ -613,11 +621,11 @@ const app = {
}); });
} }
} else { } else {
alert('AI Scan failed: ' + result.error); alert('AI scan error: ' + result.error);
} }
} catch (error) { } catch (error) {
console.error('Error during AI scan:', error); console.error('Error during AI scan:', error);
alert('An error occurred during AI scanning.'); alert('An error occurred during AI scan.');
} finally { } finally {
app.dom.aiScanBtn.disabled = false; app.dom.aiScanBtn.disabled = false;
app.dom.aiScanLoading.classList.add('d-none'); app.dom.aiScanLoading.classList.add('d-none');
@ -643,7 +651,7 @@ const app = {
app.dom.newRecipeBtn.addEventListener('click', async function() { app.dom.newRecipeBtn.addEventListener('click', async function() {
const recipeData = app.ui.getRecipeDataFromForm(); const recipeData = app.ui.getRecipeDataFromForm();
if (!recipeData) { if (!recipeData) {
alert('Please fill out the recipe name, category, guests, and at least one ingredient before saving.'); alert('Please fill in recipe name, category, guest count, and at least one ingredient before saving.');
return; return;
} }
@ -745,7 +753,7 @@ const app = {
name: properName, name: properName,
quantity: 0, quantity: 0,
unit: unit, unit: unit,
source: 'Additional product' source: 'Add. product'
}; };
app.state.additionalProducts.push(productToModify); app.state.additionalProducts.push(productToModify);
} }
@ -769,7 +777,7 @@ const app = {
name: properName, name: properName,
quantity: 0, quantity: 0,
unit: unit, unit: unit,
source: 'Additional product' source: 'Add. product'
}; };
app.state.additionalProducts.push(productToModify); app.state.additionalProducts.push(productToModify);
} }
@ -885,7 +893,7 @@ const app = {
const category = app.dom.productCategory.value; const category = app.dom.productCategory.value;
if (!name || isNaN(quantity) || quantity <= 0 || !unit) { if (!name || isNaN(quantity) || quantity <= 0 || !unit) {
alert('Please fill out all fields with valid values.'); alert('Please fill in all fields with valid values.');
return; return;
} }
@ -899,7 +907,7 @@ const app = {
if (existingProduct) { if (existingProduct) {
existingProduct.quantity += quantity; existingProduct.quantity += quantity;
} else { } else {
const newProduct = { name, quantity, unit, source: 'Additional product', category: category }; const newProduct = { name, quantity, unit, source: 'Add. product', category: category };
app.state.additionalProducts.push(newProduct); app.state.additionalProducts.push(newProduct);
} }

153
index.php
View File

@ -5,20 +5,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO & Meta Tags --> <!-- SEO & Meta Tags -->
<title>Smart Recipe & Shopping List</title> <title>FoodieFlow An AI-powered flow from recipe to grocery</title>
<meta name="description" content="Manage your recipes and generate smart shopping lists. Identify dishes with AI and calculate ingredients effortlessly."> <meta name="description" content="FoodieFlow — An AI-powered flow from recipe to grocery — every day, every occasion. Manage recipes, create smart shopping lists, and optimize your grocery budget with AI.">
<meta name="keywords" content="recipe manager, shopping list generator, AI recipe scanner, cooking organizer, meal planner"> <meta name="keywords" content="FoodieFlow, recipe manager, shopping list, AI recipe scanner, cooking organizer, meal planner">
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:title" content="Smart Recipe & Shopping List"> <meta property="og:title" content="FoodieFlow — An AI-powered flow from recipe to grocery">
<meta property="og:description" content="Manage your recipes and generate smart shopping lists."> <meta property="og:description" content="Manage recipes and create smart shopping lists. An AI-powered flow from recipe to grocery — every day, every occasion.">
<meta property="og:image" content=""> <meta property="og:image" content="">
<!-- Twitter --> <!-- Twitter -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Smart Recipe & Shopping List"> <meta name="twitter:title" content="FoodieFlow — An AI-powered flow from recipe to grocery">
<meta name="twitter:description" content="Manage your recipes and generate smart shopping lists."> <meta name="twitter:description" content="An AI-powered flow from recipe to grocery — every day, every occasion.">
<meta name="twitter:image" content=""> <meta name="twitter:image" content="">
<!-- Styles --> <!-- Styles -->
@ -27,7 +27,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>_v3"> <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>_v5">
</head> </head>
<body> <body>
@ -35,15 +35,15 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light shadow-sm sticky-top mb-4"> <nav class="navbar navbar-expand-lg navbar-light bg-light shadow-sm sticky-top mb-4">
<div class="container"> <div class="container">
<a class="navbar-brand d-flex align-items-center" href="/"> <a class="navbar-brand d-flex align-items-center" href="/">
<i class="bi bi-book-half me-2" style="color: var(--accent-color);"></i> <i class="bi bi-flow me-2" style="color: var(--accent-color);"></i>
<span class="fw-bold">SmartRecipe</span> <span class="fw-bold">FoodieFlow</span>
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center" id="auth-nav"> <ul class="navbar-nav ms-auto align-items-center" id="auth-nav">
<!-- Auth elements will be injected here --> <!-- Auth items will be inserted here -->
<li class="nav-item"> <li class="nav-item">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div> <div class="spinner-border spinner-border-sm text-primary" role="status"></div>
</li> </li>
@ -55,13 +55,46 @@
<div id="guest-view" class="d-none auth-screen"> <div id="guest-view" class="d-none auth-screen">
<div class="container-fluid h-100 p-0"> <div class="container-fluid h-100 p-0">
<div class="row h-100 g-0"> <div class="row h-100 g-0">
<!-- Left Column: Image --> <!-- Left Column: Image & Description -->
<div class="col-lg-6 d-none d-lg-block auth-image-container"> <div class="col-lg-6 d-none d-lg-block auth-image-container">
<div class="auth-image-overlay d-flex align-items-center justify-content-center p-5 text-white"> <div class="auth-image-overlay d-flex align-items-center justify-content-center p-5 text-white">
<div class="auth-branding-content text-center"> <div class="auth-branding-content">
<i class="bi bi-book-half display-1 mb-4" style="color: #FF7F50; filter: drop-shadow(0 0 10px rgba(255, 127, 80, 0.3));"></i> <div class="text-center mb-4">
<h1 class="display-3 fw-bold mb-4">SmartRecipe</h1> <i class="bi bi-flow display-1 mb-2" style="color: #FF7F50; filter: drop-shadow(0 0 10px rgba(255, 127, 80, 0.3));"></i>
<p class="lead fs-4">Manage your recipes and generate smart shopping lists. Identify dishes with AI and calculate ingredients effortlessly.</p> <h1 class="display-3 fw-bold">FoodieFlow</h1>
<p class="lead mt-2">An AI-powered flow from recipe to grocery every day, every occasion</p>
</div>
<div class="feature-list mt-4 text-start">
<div class="d-flex mb-3">
<i class="bi bi-stars fs-3 me-3 text-warning"></i>
<div>
<h5 class="mb-1">AI Recipes from Photos</h5>
<p class="small mb-0">Upload a photo of any dish, and our AI will instantly create its recipe.</p>
</div>
</div>
<div class="d-flex mb-3">
<i class="bi bi-cart-check fs-3 me-3 text-info"></i>
<div>
<h5 class="mb-1">Smart Shopping</h5>
<p class="small mb-0">Automatic grocery lists. We'll find where it's cheaper and build your basket.</p>
</div>
</div>
<div class="d-flex mb-3">
<i class="bi bi-people fs-3 me-3 text-success"></i>
<div>
<h5 class="mb-1">For Holidays & Weekdays</h5>
<p class="small mb-0">Plan parties together: share your grocery list with friends and family.</p>
</div>
</div>
<div class="d-flex mb-3">
<i class="bi bi-receipt fs-3 me-3 text-danger"></i>
<div>
<h5 class="mb-1">Expense Control</h5>
<p class="small mb-0">Snap a photo of your receipt, and the app will help split the cost among all participants.</p>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -72,12 +105,12 @@
<!-- Login Form --> <!-- Login Form -->
<div id="login-container"> <div id="login-container">
<div class="mb-5"> <div class="mb-5">
<h2 class="display-5 mb-2">Welcome back</h2> <h2 class="display-5 mb-2">Welcome back!</h2>
<p class="text-muted">Enter your credentials to access your recipes.</p> <p class="text-muted">Log in to access your recipes.</p>
</div> </div>
<form id="login-form-landing"> <form id="login-form-landing">
<div class="mb-4"> <div class="mb-4">
<label class="form-label">Email Address</label> <label class="form-label">Email address</label>
<input type="email" class="form-control form-control-lg" name="email" placeholder="name@example.com" required> <input type="email" class="form-control form-control-lg" name="email" placeholder="name@example.com" required>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -85,7 +118,7 @@
<input type="password" class="form-control form-control-lg" name="password" placeholder="••••••••" required> <input type="password" class="form-control form-control-lg" name="password" placeholder="••••••••" required>
</div> </div>
<div class="d-grid gap-2 mb-4"> <div class="d-grid gap-2 mb-4">
<button type="submit" class="btn btn-primary btn-lg">Sign In</button> <button type="submit" class="btn btn-primary btn-lg">Log In</button>
</div> </div>
</form> </form>
<p class="text-center text-muted"> <p class="text-center text-muted">
@ -96,12 +129,12 @@
<!-- Register Form --> <!-- Register Form -->
<div id="register-container" class="d-none"> <div id="register-container" class="d-none">
<div class="mb-5"> <div class="mb-5">
<h2 class="display-5 mb-2">Create Account</h2> <h2 class="display-5 mb-2">Create account</h2>
<p class="text-muted">Start your smart cooking journey today.</p> <p class="text-muted">Start your smart culinary journey today.</p>
</div> </div>
<form id="register-form-landing"> <form id="register-form-landing">
<div class="mb-4"> <div class="mb-4">
<label class="form-label">Email Address</label> <label class="form-label">Email address</label>
<input type="email" class="form-control form-control-lg" name="email" placeholder="name@example.com" required> <input type="email" class="form-control form-control-lg" name="email" placeholder="name@example.com" required>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -109,11 +142,11 @@
<input type="password" class="form-control form-control-lg" name="password" placeholder="••••••••" required> <input type="password" class="form-control form-control-lg" name="password" placeholder="••••••••" required>
</div> </div>
<div class="d-grid gap-2 mb-4"> <div class="d-grid gap-2 mb-4">
<button type="submit" class="btn btn-primary btn-lg">Register</button> <button type="submit" class="btn btn-primary btn-lg">Sign Up</button>
</div> </div>
</form> </form>
<p class="text-center text-muted"> <p class="text-center text-muted">
Already have an account? <a href="#" id="show-login" class="text-primary fw-bold text-decoration-none">Login here</a> Already have an account? <a href="#" id="show-login" class="text-primary fw-bold text-decoration-none">Log in here</a>
</p> </p>
</div> </div>
@ -126,8 +159,8 @@
<div id="app-view" class="d-none"> <div id="app-view" class="d-none">
<main class="container my-5"> <main class="container my-5">
<div class="text-center mb-5" style="padding-top: 20px;"> <div class="text-center mb-5" style="padding-top: 20px;">
<h1 class="display-4 mt-4">My Recipe Book</h1> <h1 class="display-4 mt-4">FoodieFlow</h1>
<p class="lead">Plan your meals and get your shopping lists sorted.</p> <p class="lead">An AI-powered flow from recipe to grocery every day, every occasion</p>
</div> </div>
<div class="row g-4"> <div class="row g-4">
@ -136,7 +169,7 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="text-center mb-0">All Recipes</h2> <h2 class="text-center mb-0">All Recipes</h2>
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#recipe-form-modal"> <button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#recipe-form-modal">
Add Recipe Add recipe
</button> </button>
</div> </div>
<div class="mb-3 search-container"> <div class="mb-3 search-container">
@ -147,7 +180,7 @@
<button class="btn btn-secondary active" data-category="all">All</button> <button class="btn btn-secondary active" data-category="all">All</button>
<button class="btn btn-outline-secondary" data-category="Drinks">Drinks</button> <button class="btn btn-outline-secondary" data-category="Drinks">Drinks</button>
<button class="btn btn-outline-secondary" data-category="Breakfast">Breakfast</button> <button class="btn btn-outline-secondary" data-category="Breakfast">Breakfast</button>
<button class="btn btn-outline-secondary" data-category="Dinner">Dinner</button> <button class="btn btn-outline-secondary" data-category="Dinner">Lunch/Dinner</button>
<button class="btn btn-outline-secondary" data-category="Appetizers">Appetizers</button> <button class="btn btn-outline-secondary" data-category="Appetizers">Appetizers</button>
<button class="btn btn-outline-secondary" data-category="No category">No category</button> <button class="btn btn-outline-secondary" data-category="No category">No category</button>
</div> </div>
@ -161,16 +194,16 @@
<!-- Right Column: Shopping List / Products --> <!-- Right Column: Shopping List / Products -->
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="text-center mb-0">Shopping List</h2> <h2 class="text-center mb-0">Shopping list</h2>
<div> <div>
<button id="add-product-btn" class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#add-product-modal">Add Product</button> <button id="add-product-btn" class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#add-product-modal">Add product</button>
<button id="print-shopping-list-btn" class="btn btn-outline-secondary"><i class="bi bi-printer"></i> Print</button> <button id="print-shopping-list-btn" class="btn btn-outline-secondary"><i class="bi bi-printer"></i> Print</button>
</div> </div>
</div> </div>
<div class="card shadow"> <div class="card shadow">
<div class="card-body" id="shopping-list-container"> <div class="card-body" id="shopping-list-container">
<div class="text-center text-muted p-5"> <div class="text-center text-muted p-5">
<p>Your calculated list will appear here.</p> <p>Your grocery list will appear here.</p>
</div> </div>
</div> </div>
</div> </div>
@ -184,7 +217,7 @@
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="recipe-form-modal-label">Add a Recipe</h5> <h5 class="modal-title" id="recipe-form-modal-label">Add recipe</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -192,8 +225,8 @@
<form id="recipe-form"> <form id="recipe-form">
<input type="hidden" id="recipeId"> <input type="hidden" id="recipeId">
<div class="mb-3"> <div class="mb-3">
<label for="recipeName" class="form-label">Recipe Name</label> <label for="recipeName" class="form-label">Recipe name</label>
<input type="text" class="form-control" id="recipeName" placeholder="e.g., Avocado Toast"> <input type="text" class="form-control" id="recipeName" placeholder="e.g. Avocado toast">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="recipeCategory" class="form-label">Category</label> <label for="recipeCategory" class="form-label">Category</label>
@ -201,31 +234,31 @@
<option value="" selected disabled>Choose...</option> <option value="" selected disabled>Choose...</option>
<option value="Drinks">Drinks</option> <option value="Drinks">Drinks</option>
<option value="Breakfast">Breakfast</option> <option value="Breakfast">Breakfast</option>
<option value="Dinner">Dinner</option> <option value="Dinner">Lunch/Dinner</option>
<option value="Appetizers">Appetizers</option> <option value="Appetizers">Appetizers</option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="recipeImage" class="form-label d-flex justify-content-between"> <label for="recipeImage" class="form-label d-flex justify-content-between">
Recipe Image Image
<button type="button" id="ai-scan-btn" class="btn btn-outline-primary btn-sm"> <button type="button" id="ai-scan-btn" class="btn btn-outline-primary btn-sm">
<i class="bi bi-magic"></i> AI Scan <i class="bi bi-magic"></i> AI Scanning
</button> </button>
</label> </label>
<input type="file" class="form-control" id="recipeImage"> <input type="file" class="form-control" id="recipeImage">
<div id="ai-scan-loading" class="text-primary mt-2 d-none"> <div id="ai-scan-loading" class="text-primary mt-2 d-none">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
AI is identifying the dish and recipe... AI is recognizing the dish and creating a recipe...
</div> </div>
</div> </div>
<hr class="my-4 border-secondary"> <hr class="my-4 border-secondary">
<h3 class="h5 mb-3">Ingredients (for 1 person)</h3> <h3 class="h5 mb-3">Ingredients (per 1 person)</h3>
<div id="ingredients-container"> <div id="ingredients-container">
<!-- Ingredient rows will be injected here by JS --> <!-- Ingredient rows will be injected here by JS -->
</div> </div>
<button type="button" id="add-ingredient" class="btn btn-secondary btn-sm mt-2">+ Add Ingredient</button> <button type="button" id="add-ingredient" class="btn btn-secondary btn-sm mt-2">+ Add ingredient</button>
<hr class="my-4 border-secondary"> <hr class="my-4 border-secondary">
@ -233,19 +266,19 @@
<div class="col"> <div class="col">
<div class="mb-3"> <div class="mb-3">
<label for="guestCount" class="form-label">How many guests?</label> <label for="guestCount" class="form-label">How many guests?</label>
<input type="number" class="form-control" id="guestCount" placeholder="e.g., 8" min="1" value="1"> <input type="number" class="form-control" id="guestCount" placeholder="e.g. 8" min="1" value="1">
</div> </div>
</div> </div>
<div class="col"> <div class="col">
<div class="mb-3"> <div class="mb-3">
<label for="portionsPerGuest" class="form-label">Portions/guest</label> <label for="portionsPerGuest" class="form-label">Portions per guest</label>
<input type="number" class="form-control" id="portionsPerGuest" placeholder="e.g., 2" min="1" value="1"> <input type="number" class="form-control" id="portionsPerGuest" placeholder="e.g. 2" min="1" value="1">
</div> </div>
</div> </div>
</div> </div>
<div class="d-grid gap-2 mt-4"> <div class="d-grid gap-2 mt-4">
<button type="button" id="new-recipe-btn" class="btn btn-primary">Save Recipe</button> <button type="button" id="new-recipe-btn" class="btn btn-primary">Save recipe</button>
<button type="button" id="cancel-edit-btn" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" id="cancel-edit-btn" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
</div> </div>
</form> </form>
@ -260,20 +293,20 @@
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="add-product-modal-label">Add a Product</h5> <h5 class="modal-title" id="add-product-modal-label">Add product</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="add-product-form"> <form id="add-product-form">
<div class="mb-3"> <div class="mb-3">
<label for="productName" class="form-label">Product Name</label> <label for="productName" class="form-label">Product name</label>
<input type="text" class="form-control" id="productName" placeholder="e.g., Milk"> <input type="text" class="form-control" id="productName" placeholder="e.g. Milk">
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="mb-3"> <div class="mb-3">
<label for="productQuantity" class="form-label">Quantity</label> <label for="productQuantity" class="form-label">Quantity</label>
<input type="number" class="form-control" id="productQuantity" placeholder="e.g., 1" min="1" value="1"> <input type="number" class="form-control" id="productQuantity" placeholder="1" min="1" value="1">
</div> </div>
</div> </div>
<div class="col"> <div class="col">
@ -284,7 +317,7 @@
<button type="button" class="btn btn-outline-secondary unit-btn">kg</button> <button type="button" class="btn btn-outline-secondary unit-btn">kg</button>
<button type="button" class="btn btn-outline-secondary unit-btn">ml</button> <button type="button" class="btn btn-outline-secondary unit-btn">ml</button>
<button type="button" class="btn btn-outline-secondary unit-btn">l</button> <button type="button" class="btn btn-outline-secondary unit-btn">l</button>
<button type="button" class="btn btn-outline-secondary unit-btn">piece</button> <button type="button" class="btn btn-outline-secondary unit-btn">pcs</button>
<button type="button" class="btn btn-outline-secondary unit-btn">pack</button> <button type="button" class="btn btn-outline-secondary unit-btn">pack</button>
</div> </div>
</div> </div>
@ -295,12 +328,12 @@
<select class="form-select" id="productCategory"> <select class="form-select" id="productCategory">
<option value="Food" selected>Food</option> <option value="Food" selected>Food</option>
<option value="Drinks">Drinks</option> <option value="Drinks">Drinks</option>
<option value="Cooking and serving">Cooking and serving</option> <option value="Cooking and serving">Kitchen & serving</option>
<option value="Tableware and consumables">Tableware and consumables</option> <option value="Tableware and consumables">Tableware & consumables</option>
</select> </select>
</div> </div>
<div class="d-grid gap-2 mt-4"> <div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary">Add Product</button> <button type="submit" class="btn btn-primary">Add product</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
</div> </div>
</form> </form>
@ -314,13 +347,13 @@
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="view-recipe-modal-label">View Recipe</h5> <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> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h2 id="view-recipe-name"></h2> <h2 id="view-recipe-name"></h2>
<p><strong>Category:</strong> <span id="view-recipe-category"></span></p> <p><strong>Category:</strong> <span id="view-recipe-category"></span></p>
<p><strong>Serves:</strong> <span id="view-recipe-guests"></span></p> <p><strong>Guest count:</strong> <span id="view-recipe-guests"></span></p>
<hr> <hr>
<h3>Ingredients</h3> <h3>Ingredients</h3>
<ul id="view-recipe-ingredients" class="list-group"> <ul id="view-recipe-ingredients" class="list-group">
@ -334,29 +367,29 @@
</div> </div>
<footer class="text-center py-4 mt-5"> <footer class="text-center py-4 mt-5">
<p class="mb-0">&copy; <?php echo date("Y"); ?> Smart Recipe & Shopping List.</p> <p class="mb-0">&copy; <?php echo date("Y"); ?> FoodieFlow — An AI-powered flow from recipe to grocery — every day, every occasion.</p>
</footer> </footer>
<!-- Scripts --> <!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script> <script src="assets/js/main.js?v=<?php echo time(); ?>_v5"></script>
<!-- Confirmation Modal --> <!-- Confirmation Modal -->
<div class="modal fade" id="confirmRemoveModal" tabindex="-1" aria-labelledby="confirmRemoveModalLabel" aria-hidden="true"> <div class="modal fade" id="confirmRemoveModal" tabindex="-1" aria-labelledby="confirmRemoveModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="confirmRemoveModalLabel">Confirm Removal</h5> <h5 class="modal-title" id="confirmRemoveModalLabel">Confirm Deletion</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>You are about to remove an ingredient from a recipe. This will affect the recipe itself. Are you sure you want to continue?</p> <p>You are about to remove an ingredient from the recipe. This will modify the recipe. Are you sure?</p>
<p><strong>Recipe:</strong> <span id="modal-recipe-name"></span></p> <p><strong>Recipe:</strong> <span id="modal-recipe-name"></span></p>
<p><strong>Ingredient:</strong> <span id="modal-ingredient-name"></span></p> <p><strong>Ingredient:</strong> <span id="modal-ingredient-name"></span></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirm-remove-btn">Remove</button> <button type="button" class="btn btn-danger" id="confirm-remove-btn">Delete</button>
</div> </div>
</div> </div>
</div> </div>