Update templates/menu.html
Browse files- templates/menu.html +164 -301
templates/menu.html
CHANGED
@@ -15,6 +15,7 @@
|
|
15 |
{% endfor %}
|
16 |
{% endfor %}
|
17 |
<style>
|
|
|
18 |
body {
|
19 |
font-family: Arial, sans-serif;
|
20 |
background-color: #fdf4e3;
|
@@ -675,6 +676,38 @@
|
|
675 |
50% { transform: scale(1.1); }
|
676 |
100% { transform: scale(1); }
|
677 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
678 |
@media (max-width: 576px) {
|
679 |
.fixed-top-bar {
|
680 |
height: 60px;
|
@@ -946,7 +979,7 @@
|
|
946 |
<h3>{{ section }}</h3>
|
947 |
<div class="row">
|
948 |
{% for item in items %}
|
949 |
-
<div class="col-md-6 mb-4">
|
950 |
<div class="card menu-card">
|
951 |
<video
|
952 |
class="card-img-top menu-video"
|
@@ -968,9 +1001,7 @@
|
|
968 |
<div>
|
969 |
<h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
|
970 |
<p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
|
971 |
-
|
972 |
-
<div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}">Show Details</div>
|
973 |
-
{% endif %}
|
974 |
</div>
|
975 |
<div class="d-flex flex-column align-item-center justify-content-center">
|
976 |
<div class="button-container"
|
@@ -979,16 +1010,12 @@
|
|
979 |
data-item-image="{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}"
|
980 |
data-item-section="{{ item.Section__c | default(section) }}"
|
981 |
data-item-category="{{ selected_category }}">
|
982 |
-
|
983 |
-
|
984 |
-
|
985 |
-
|
986 |
-
|
987 |
-
|
988 |
-
onclick="showItemDetails('{{ item.Name | default('Unnamed Item') | e }}', '{{ item.Price__c | default('0.00') }}', '{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}', '{{ item.Description__c | default('No description') | e }}', '{{ item.IngredientsInfo__c | default('Not specified') | e }}', '{{ item.NutritionalInfo__c | default('Not available') | e }}', '{{ item.Allergens__c | default('None listed') | e }}', '{{ item.Section__c | default(section) | e }}', '{{ selected_category | e }}')">
|
989 |
-
ADD
|
990 |
-
</button>
|
991 |
-
{% endif %}
|
992 |
{% if item.Section__c != 'Apetizer' and item.Section__c != 'Customized dish' and item.Section__c != 'Soft Drinks' %}
|
993 |
<span class="customisable-text">Customisable</span>
|
994 |
{% endif %}
|
@@ -996,18 +1023,16 @@
|
|
996 |
</div>
|
997 |
</div>
|
998 |
</div>
|
999 |
-
{
|
1000 |
-
<
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
1004 |
-
|
1005 |
-
|
1006 |
-
|
1007 |
-
|
1008 |
-
|
1009 |
-
</div>
|
1010 |
-
{% endif %}
|
1011 |
</div>
|
1012 |
</div>
|
1013 |
{% endfor %}
|
@@ -1085,35 +1110,6 @@
|
|
1085 |
</div>
|
1086 |
</div>
|
1087 |
|
1088 |
-
<!-- Modal for Soft Drinks Quantity Selection -->
|
1089 |
-
<div class="modal fade" id="softDrinkModal" tabindex="-1" aria-labelledby="softDrinkModalLabel" aria-hidden="true">
|
1090 |
-
<div class="modal-dialog modal-dialog-centered">
|
1091 |
-
<div class="modal-content" style="border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
|
1092 |
-
<div class="modal-header" style="background: linear-gradient(45deg, #0FAA39, #0D9232); color: white; border-radius: 15px 15px 0 0;">
|
1093 |
-
<h5 class="modal-title" id="softDrinkModalLabel">Select Your Drink</h5>
|
1094 |
-
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
1095 |
-
</div>
|
1096 |
-
<div class="modal-body" style="padding: 20px;">
|
1097 |
-
<div class="text-center mb-4">
|
1098 |
-
<img id="soft-drink-image" class="img-fluid rounded mb-3" alt="Soft Drink Image" style="max-height: 150px; width: auto; object-fit: contain;">
|
1099 |
-
<h5 id="soft-drink-name" class="fw-bold" style="color: #333;"></h5>
|
1100 |
-
<p id="soft-drink-price" class="text-muted" style="font-size: 1.1rem;"></p>
|
1101 |
-
</div>
|
1102 |
-
<div class="d-flex justify-content-center align-items-center mb-4">
|
1103 |
-
<div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
|
1104 |
-
<button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;">-</button>
|
1105 |
-
<input type="text" class="form-control text-center mx-2" id="soft-drink-quantity" value="1" readonly style="width: 60px; font-weight: bold; font-size: 1.1rem;">
|
1106 |
-
<button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;">+</button>
|
1107 |
-
</div>
|
1108 |
-
</div>
|
1109 |
-
</div>
|
1110 |
-
<div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
|
1111 |
-
<button type="button" class="btn btn-primary" onclick="addSoftDrinkToCart()" style="width: 200px; padding: 12px; font-size: 1.1rem; background-color: #0FAA39; border-color: #0FAA39;">Add to Cart</button>
|
1112 |
-
</div>
|
1113 |
-
</div>
|
1114 |
-
</div>
|
1115 |
-
</div>
|
1116 |
-
|
1117 |
<!-- Mic Popup -->
|
1118 |
<div class="mic-popup" id="micPopup">
|
1119 |
<div class="mic-popup-icon">
|
@@ -1123,10 +1119,14 @@
|
|
1123 |
<button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
|
1124 |
</div>
|
1125 |
|
|
|
|
|
|
|
|
|
|
|
1126 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
1127 |
<script>
|
1128 |
let isProcessingRequest = false;
|
1129 |
-
let currentSoftDrinkButton = null;
|
1130 |
|
1131 |
const menuItems = [
|
1132 |
{% for section, items in ordered_menu.items() %}
|
@@ -1152,7 +1152,6 @@
|
|
1152 |
"Whole Wheat Flour", "Yogurt (Curd)"
|
1153 |
];
|
1154 |
|
1155 |
-
// Utility function to sanitize input to prevent XSS
|
1156 |
function sanitizeInput(input) {
|
1157 |
const div = document.createElement('div');
|
1158 |
div.textContent = input;
|
@@ -1205,79 +1204,6 @@
|
|
1205 |
};
|
1206 |
}
|
1207 |
|
1208 |
-
function showSoftDrinkModal(button) {
|
1209 |
-
currentSoftDrinkButton = button;
|
1210 |
-
const buttonContainer = button.closest('.button-container');
|
1211 |
-
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
|
1212 |
-
const itemPrice = buttonContainer.getAttribute('data-item-price');
|
1213 |
-
const itemImage = buttonContainer.getAttribute('data-item-image');
|
1214 |
-
|
1215 |
-
document.getElementById('soft-drink-name').textContent = itemName;
|
1216 |
-
document.getElementById('soft-drink-price').textContent = `$${itemPrice}`;
|
1217 |
-
document.getElementById('soft-drink-quantity').value = '1';
|
1218 |
-
const softDrinkImage = document.getElementById('soft-drink-image');
|
1219 |
-
softDrinkImage.src = itemImage || '/static/placeholder.jpg';
|
1220 |
-
|
1221 |
-
const modal = new bootstrap.Modal(document.getElementById('softDrinkModal'));
|
1222 |
-
modal.show();
|
1223 |
-
}
|
1224 |
-
|
1225 |
-
function addSoftDrinkToCart() {
|
1226 |
-
if (!currentSoftDrinkButton) return;
|
1227 |
-
|
1228 |
-
const buttonContainer = currentSoftDrinkButton.closest('.button-container');
|
1229 |
-
const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
|
1230 |
-
|
1231 |
-
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
|
1232 |
-
const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
|
1233 |
-
const itemImage = buttonContainer.getAttribute('data-item-image');
|
1234 |
-
const section = sanitizeInput(buttonContainer.getAttribute('data-item-section'));
|
1235 |
-
const selectedCategory = sanitizeInput(buttonContainer.getAttribute('data-item-category'));
|
1236 |
-
|
1237 |
-
const cartPayload = {
|
1238 |
-
itemName: itemName,
|
1239 |
-
itemPrice: itemPrice,
|
1240 |
-
itemImage: itemImage,
|
1241 |
-
section: section,
|
1242 |
-
category: selectedCategory,
|
1243 |
-
addons: [],
|
1244 |
-
instructions: '',
|
1245 |
-
quantity: quantity
|
1246 |
-
};
|
1247 |
-
|
1248 |
-
fetch('/cart/add', {
|
1249 |
-
method: 'POST',
|
1250 |
-
headers: {
|
1251 |
-
'Content-Type': 'application/json',
|
1252 |
-
},
|
1253 |
-
body: JSON.stringify(cartPayload)
|
1254 |
-
})
|
1255 |
-
.then(response => response.json())
|
1256 |
-
.then(data => {
|
1257 |
-
if (data.success) {
|
1258 |
-
alert('Item added to cart successfully!');
|
1259 |
-
updateCartUI(data.cart);
|
1260 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
|
1261 |
-
modal.hide();
|
1262 |
-
} else {
|
1263 |
-
console.error('Failed to add item to cart:', data.error);
|
1264 |
-
alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
|
1265 |
-
const cart = addToCartLocalStorage(cartPayload);
|
1266 |
-
updateCartUI(cart);
|
1267 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
|
1268 |
-
modal.hide();
|
1269 |
-
}
|
1270 |
-
})
|
1271 |
-
.catch(err => {
|
1272 |
-
console.error('Error adding item to cart:', err);
|
1273 |
-
alert('Error adding item to cart. Using local storage as fallback.');
|
1274 |
-
const cart = addToCartLocalStorage(cartPayload);
|
1275 |
-
updateCartUI(cart);
|
1276 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
|
1277 |
-
modal.hide();
|
1278 |
-
});
|
1279 |
-
}
|
1280 |
-
|
1281 |
function updateCartUI(cart) {
|
1282 |
if (!Array.isArray(cart)) {
|
1283 |
console.error('Invalid cart data:', cart);
|
@@ -1296,7 +1222,63 @@
|
|
1296 |
}
|
1297 |
}
|
1298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1299 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
|
1300 |
const avatarContainer = document.querySelector('.avatar-dropdown-container');
|
1301 |
const dropdownMenu = document.querySelector('.dropdown-menu');
|
1302 |
const avatarIcon = document.getElementById('avatarIcon');
|
@@ -1304,7 +1286,6 @@
|
|
1304 |
const deleteAvatar = document.getElementById('deleteAvatar');
|
1305 |
const viewAvatar = document.getElementById('viewAvatar');
|
1306 |
|
1307 |
-
// Load avatar from localStorage if available
|
1308 |
function loadAvatar() {
|
1309 |
const savedAvatar = localStorage.getItem('userAvatar');
|
1310 |
const avatarImage = avatarIcon.querySelector('.avatar-image');
|
@@ -1323,7 +1304,6 @@
|
|
1323 |
}
|
1324 |
}
|
1325 |
|
1326 |
-
// Ensure avatar options (Delete and View) are present only once
|
1327 |
function ensureAvatarOptions() {
|
1328 |
if (!document.querySelector('#deleteAvatar')) {
|
1329 |
const deleteItem = document.createElement('div');
|
@@ -1344,7 +1324,6 @@
|
|
1344 |
}
|
1345 |
}
|
1346 |
|
1347 |
-
// Remove avatar options
|
1348 |
function removeAvatarOptions() {
|
1349 |
const deleteItem = document.getElementById('deleteAvatar');
|
1350 |
const viewItem = document.getElementById('viewAvatar');
|
@@ -1352,29 +1331,25 @@
|
|
1352 |
if (viewItem) viewItem.remove();
|
1353 |
}
|
1354 |
|
1355 |
-
// Save avatar to localStorage
|
1356 |
function saveAvatar(imageUrl) {
|
1357 |
localStorage.setItem('userAvatar', imageUrl);
|
1358 |
}
|
1359 |
|
1360 |
-
// Clear avatar from localStorage
|
1361 |
function clearAvatar() {
|
1362 |
localStorage.removeItem('userAvatar');
|
1363 |
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
|
1364 |
removeAvatarOptions();
|
1365 |
}
|
1366 |
|
1367 |
-
// Avatar click handler
|
1368 |
avatarIcon.addEventListener('click', function(event) {
|
1369 |
event.stopPropagation();
|
1370 |
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
|
1371 |
});
|
1372 |
|
1373 |
-
// Handle image upload with 15MB limit and error handling
|
1374 |
avatarUpload.addEventListener('change', function(event) {
|
1375 |
const file = event.target.files[0];
|
1376 |
if (file) {
|
1377 |
-
const maxSize = 15 * 1024 * 1024;
|
1378 |
if (file.size > maxSize) {
|
1379 |
alert('Image size must not exceed 15MB.');
|
1380 |
this.value = '';
|
@@ -1407,52 +1382,18 @@
|
|
1407 |
ensureAvatarOptions();
|
1408 |
dropdownMenu.style.display = 'none';
|
1409 |
} else {
|
1410 |
-
alert('Failed to upload image: ' + (data.error || 'Unknown error
|
1411 |
-
const img = document.createElement('img');
|
1412 |
-
img.src = '/static/default-avatar.jpg';
|
1413 |
-
img.alt = 'User Avatar';
|
1414 |
-
img.className = 'avatar-image';
|
1415 |
-
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
|
1416 |
-
avatarIcon.innerHTML = '';
|
1417 |
-
avatarIcon.appendChild(img);
|
1418 |
-
saveAvatar('/static/default-avatar.jpg');
|
1419 |
-
ensureAvatarOptions();
|
1420 |
-
dropdownMenu.style.display = 'none';
|
1421 |
}
|
1422 |
})
|
1423 |
.catch(error => {
|
1424 |
console.error('Upload error:', error);
|
1425 |
-
alert('Error uploading image.
|
1426 |
-
const img = document.createElement('img');
|
1427 |
-
img.src = '/static/default-avatar.jpg';
|
1428 |
-
img.alt = 'User Avatar';
|
1429 |
-
img.className = 'avatar-image';
|
1430 |
-
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
|
1431 |
-
avatarIcon.innerHTML = '';
|
1432 |
-
avatarIcon.appendChild(img);
|
1433 |
-
saveAvatar('/static/default-avatar.jpg');
|
1434 |
-
ensureAvatarOptions();
|
1435 |
-
dropdownMenu.style.display = 'none';
|
1436 |
});
|
1437 |
};
|
1438 |
-
reader.onerror = function() {
|
1439 |
-
alert('Error reading the image file. Using default image as fallback.');
|
1440 |
-
const img = document.createElement('img');
|
1441 |
-
img.src = '/static/default-avatar.jpg';
|
1442 |
-
img.alt = 'User Avatar';
|
1443 |
-
img.className = 'avatar-image';
|
1444 |
-
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
|
1445 |
-
avatarIcon.innerHTML = '';
|
1446 |
-
avatarIcon.appendChild(img);
|
1447 |
-
saveAvatar('/static/default-avatar.jpg');
|
1448 |
-
ensureAvatarOptions();
|
1449 |
-
dropdownMenu.style.display = 'none';
|
1450 |
-
};
|
1451 |
reader.readAsDataURL(file);
|
1452 |
}
|
1453 |
});
|
1454 |
|
1455 |
-
// Handle image deletion
|
1456 |
function addDeleteListener(deleteElement) {
|
1457 |
deleteElement.addEventListener('click', function() {
|
1458 |
fetch('/delete_avatar', {
|
@@ -1461,10 +1402,7 @@
|
|
1461 |
'Content-Type': 'application/json'
|
1462 |
}
|
1463 |
})
|
1464 |
-
.then(response =>
|
1465 |
-
if (!response.ok) throw new Error('Delete failed');
|
1466 |
-
return response.json();
|
1467 |
-
})
|
1468 |
.then(data => {
|
1469 |
if (data.success) {
|
1470 |
clearAvatar();
|
@@ -1475,12 +1413,11 @@
|
|
1475 |
})
|
1476 |
.catch(error => {
|
1477 |
console.error('Delete error:', error);
|
1478 |
-
alert('Error deleting image.
|
1479 |
});
|
1480 |
});
|
1481 |
}
|
1482 |
|
1483 |
-
// Handle avatar view
|
1484 |
function addViewListener(viewElement) {
|
1485 |
viewElement.addEventListener('click', function() {
|
1486 |
const avatarImage = avatarIcon.querySelector('.avatar-image');
|
@@ -1489,21 +1426,16 @@
|
|
1489 |
modalImg.src = avatarImage.src;
|
1490 |
const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
|
1491 |
modal.show();
|
1492 |
-
} else {
|
1493 |
-
alert('No avatar image to view.');
|
1494 |
}
|
1495 |
dropdownMenu.style.display = 'none';
|
1496 |
});
|
1497 |
}
|
1498 |
|
1499 |
-
// Add listeners if elements exist
|
1500 |
if (deleteAvatar) addDeleteListener(deleteAvatar);
|
1501 |
if (viewAvatar) addViewListener(viewAvatar);
|
1502 |
|
1503 |
-
// Load avatar on page load
|
1504 |
loadAvatar();
|
1505 |
|
1506 |
-
// Close dropdown when clicking outside
|
1507 |
document.addEventListener('click', function(event) {
|
1508 |
if (!avatarContainer.contains(event.target)) {
|
1509 |
dropdownMenu.style.display = 'none';
|
@@ -1512,37 +1444,27 @@
|
|
1512 |
|
1513 |
const menuCards = document.querySelectorAll('.menu-card');
|
1514 |
const menuVideos = document.querySelectorAll('.menu-video');
|
1515 |
-
const cardObserver = new IntersectionObserver((entries
|
1516 |
entries.forEach(entry => {
|
1517 |
if (entry.isIntersecting) {
|
1518 |
entry.target.classList.add('visible');
|
1519 |
-
observer.unobserve(entry.target);
|
1520 |
}
|
1521 |
});
|
1522 |
-
}, {
|
1523 |
-
|
1524 |
-
rootMargin: '0px',
|
1525 |
-
threshold: 0.1
|
1526 |
-
});
|
1527 |
-
const videoObserver = new IntersectionObserver((entries, observer) => {
|
1528 |
entries.forEach(entry => {
|
1529 |
if (entry.isIntersecting) {
|
1530 |
const video = entry.target;
|
1531 |
const src = video.getAttribute('data-src');
|
1532 |
-
if (src
|
1533 |
const source = video.querySelector('source');
|
1534 |
source.src = src;
|
1535 |
video.load();
|
1536 |
}
|
1537 |
video.classList.add('loaded');
|
1538 |
-
observer.unobserve(video);
|
1539 |
}
|
1540 |
});
|
1541 |
-
}, {
|
1542 |
-
root: null,
|
1543 |
-
rootMargin: '200px',
|
1544 |
-
threshold: 0.01
|
1545 |
-
});
|
1546 |
menuCards.forEach(card => cardObserver.observe(card));
|
1547 |
menuVideos.forEach(video => videoObserver.observe(video));
|
1548 |
|
@@ -1551,25 +1473,15 @@
|
|
1551 |
link.addEventListener('click', function () {
|
1552 |
const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
|
1553 |
const detailsDiv = document.getElementById(`details-${itemName}`);
|
1554 |
-
const
|
1555 |
-
|
1556 |
-
|
1557 |
-
|
1558 |
-
|
1559 |
-
const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
|
1560 |
-
if (otherLink) {
|
1561 |
-
otherLink.innerText = 'Show Details';
|
1562 |
-
}
|
1563 |
}
|
1564 |
});
|
1565 |
-
|
1566 |
-
|
1567 |
-
detailsDiv.classList.add('show');
|
1568 |
-
this.innerText = 'Hide Details';
|
1569 |
-
} else {
|
1570 |
-
detailsDiv.classList.remove('show');
|
1571 |
-
this.innerText = 'Show Details';
|
1572 |
-
}
|
1573 |
});
|
1574 |
});
|
1575 |
|
@@ -1592,14 +1504,19 @@
|
|
1592 |
const searchBar = document.getElementById('searchBar');
|
1593 |
const suggestionsContainer = document.getElementById('autocompleteSuggestions');
|
1594 |
const debouncedFilterMenu = debounce(filterMenu, 300);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1595 |
searchBar.addEventListener('input', function () {
|
1596 |
const input = sanitizeInput(this.value.trim().toLowerCase());
|
1597 |
suggestionsContainer.innerHTML = '';
|
1598 |
suggestionsContainer.style.display = 'none';
|
1599 |
if (input) {
|
1600 |
-
const filteredItems = menuItems.filter(item =>
|
1601 |
-
item.toLowerCase().includes(input)
|
1602 |
-
);
|
1603 |
if (filteredItems.length > 0) {
|
1604 |
filteredItems.forEach(item => {
|
1605 |
const suggestionDiv = document.createElement('div');
|
@@ -1617,6 +1534,7 @@
|
|
1617 |
}
|
1618 |
debouncedFilterMenu();
|
1619 |
});
|
|
|
1620 |
document.addEventListener('click', function (event) {
|
1621 |
if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
|
1622 |
suggestionsContainer.style.display = 'none';
|
@@ -1677,18 +1595,16 @@
|
|
1677 |
});
|
1678 |
}
|
1679 |
|
1680 |
-
// Custom Dish Form Validation
|
1681 |
const customDishForm = document.getElementById('customDishForm');
|
1682 |
if (customDishForm) {
|
1683 |
customDishForm.addEventListener('submit', function(event) {
|
|
|
1684 |
const dishName = document.getElementById('custom-dish-name').value.trim();
|
1685 |
const description = document.getElementById('custom-dish-description').value.trim();
|
1686 |
if (!dishName || !description) {
|
1687 |
-
|
1688 |
-
alert('Please fill in both the dish name and description.');
|
1689 |
return;
|
1690 |
}
|
1691 |
-
event.preventDefault();
|
1692 |
fetch('/customdish/generate_custom_dish', {
|
1693 |
method: 'POST',
|
1694 |
headers: {
|
@@ -1705,82 +1621,42 @@
|
|
1705 |
alert('Custom dish submitted successfully!');
|
1706 |
window.location.reload();
|
1707 |
} else {
|
1708 |
-
alert('Failed to submit
|
1709 |
}
|
1710 |
})
|
1711 |
.catch(error => {
|
1712 |
-
console.error('Error
|
1713 |
-
alert('Error submitting custom dish.
|
1714 |
});
|
1715 |
});
|
1716 |
}
|
1717 |
|
1718 |
fetch('/cart/get')
|
1719 |
-
.then(response =>
|
1720 |
-
if (!response.ok) {
|
1721 |
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
1722 |
-
}
|
1723 |
-
return response.json();
|
1724 |
-
})
|
1725 |
.then(data => {
|
1726 |
if (data.success) {
|
1727 |
updateCartUI(data.cart);
|
1728 |
} else {
|
1729 |
-
|
1730 |
-
const cart = getCartLocalStorage();
|
1731 |
-
updateCartUI(cart);
|
1732 |
}
|
1733 |
})
|
1734 |
.catch(err => {
|
1735 |
console.error('Error fetching cart:', err);
|
1736 |
-
|
1737 |
-
updateCartUI(cart);
|
1738 |
});
|
1739 |
|
1740 |
-
const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
|
1741 |
-
preloadedVideos.forEach(link => {
|
1742 |
-
const video = document.createElement('video');
|
1743 |
-
video.src = link.href;
|
1744 |
-
video.preload = 'auto';
|
1745 |
-
});
|
1746 |
-
|
1747 |
const decreaseBtn = document.getElementById('decreaseQuantity');
|
1748 |
const increaseBtn = document.getElementById('increaseQuantity');
|
1749 |
const quantityInput = document.getElementById('quantityInput');
|
1750 |
decreaseBtn.addEventListener('click', function () {
|
1751 |
-
let
|
1752 |
-
if (
|
1753 |
-
currentQuantity--;
|
1754 |
-
quantityInput.value = currentQuantity;
|
1755 |
-
}
|
1756 |
});
|
1757 |
increaseBtn.addEventListener('click', function () {
|
1758 |
-
let
|
1759 |
-
|
1760 |
-
quantityInput.value = currentQuantity;
|
1761 |
-
});
|
1762 |
-
|
1763 |
-
const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
|
1764 |
-
const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
|
1765 |
-
const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
|
1766 |
-
|
1767 |
-
softDrinkDecreaseBtn.addEventListener('click', function() {
|
1768 |
-
let currentQuantity = parseInt(softDrinkQuantityInput.value);
|
1769 |
-
if (currentQuantity > 1) {
|
1770 |
-
currentQuantity--;
|
1771 |
-
softDrinkQuantityInput.value = currentQuantity;
|
1772 |
-
}
|
1773 |
-
});
|
1774 |
-
|
1775 |
-
softDrinkIncreaseBtn.addEventListener('click', function() {
|
1776 |
-
let currentQuantity = parseInt(softDrinkQuantityInput.value);
|
1777 |
-
if (currentQuantity < 1000) {
|
1778 |
-
currentQuantity++;
|
1779 |
-
softDrinkQuantityInput.value = currentQuantity;
|
1780 |
-
}
|
1781 |
});
|
1782 |
|
1783 |
-
// Mic Popup Functionality
|
1784 |
const micIcon = document.getElementById('micIcon');
|
1785 |
const micUnsupported = document.getElementById('micUnsupported');
|
1786 |
const micPopup = document.getElementById('micPopup');
|
@@ -1803,20 +1679,12 @@
|
|
1803 |
recognition.onresult = (event) => {
|
1804 |
let interimTranscript = '';
|
1805 |
let finalTranscript = '';
|
1806 |
-
|
1807 |
for (let i = event.resultIndex; i < event.results.length; i++) {
|
1808 |
const transcript = event.results[i][0].transcript;
|
1809 |
-
if (event.results[i].isFinal)
|
1810 |
-
|
1811 |
-
} else {
|
1812 |
-
interimTranscript += transcript;
|
1813 |
-
}
|
1814 |
}
|
1815 |
-
|
1816 |
-
if (interimTranscript) {
|
1817 |
-
micPopupText.textContent = interimTranscript;
|
1818 |
-
}
|
1819 |
-
|
1820 |
if (finalTranscript) {
|
1821 |
searchBar.value = sanitizeInput(finalTranscript.trim());
|
1822 |
debouncedFilterMenu();
|
@@ -1826,19 +1694,13 @@
|
|
1826 |
|
1827 |
recognition.onend = () => {
|
1828 |
micIcon.classList.remove('active');
|
1829 |
-
|
1830 |
-
setTimeout(() => {
|
1831 |
-
micPopup.classList.remove('active');
|
1832 |
-
}, 1000);
|
1833 |
-
}
|
1834 |
};
|
1835 |
|
1836 |
recognition.onerror = (event) => {
|
1837 |
micIcon.classList.remove('active');
|
1838 |
micPopupText.textContent = 'Error: ' + event.error;
|
1839 |
-
setTimeout(() =>
|
1840 |
-
micPopup.classList.remove('active');
|
1841 |
-
}, 2000);
|
1842 |
console.error('Speech error:', event.error);
|
1843 |
};
|
1844 |
|
@@ -1847,10 +1709,8 @@
|
|
1847 |
recognition.start();
|
1848 |
} catch (e) {
|
1849 |
micPopupText.textContent = 'Error starting microphone';
|
1850 |
-
setTimeout(() =>
|
1851 |
-
|
1852 |
-
}, 2000);
|
1853 |
-
console.error('Recognition start error:', e);
|
1854 |
}
|
1855 |
});
|
1856 |
|
@@ -1865,6 +1725,10 @@
|
|
1865 |
}
|
1866 |
});
|
1867 |
|
|
|
|
|
|
|
|
|
1868 |
function filterMenu() {
|
1869 |
const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
|
1870 |
const sections = document.querySelectorAll('h3');
|
@@ -1906,8 +1770,7 @@
|
|
1906 |
function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
|
1907 |
document.getElementById('modal-name').innerText = name;
|
1908 |
document.getElementById('modal-price').innerText = `$${price}`;
|
1909 |
-
|
1910 |
-
modalImg.src = image || '/static/placeholder.jpg';
|
1911 |
document.getElementById('modal-description').innerText = description || 'No description available.';
|
1912 |
document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
|
1913 |
document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
|
@@ -1949,7 +1812,7 @@
|
|
1949 |
optionsContainer.appendChild(listItem);
|
1950 |
});
|
1951 |
sectionDiv.appendChild(optionsContainer);
|
1952 |
-
|
1953 |
});
|
1954 |
})
|
1955 |
.catch(err => {
|
@@ -2035,4 +1898,4 @@
|
|
2035 |
}
|
2036 |
</script>
|
2037 |
</body>
|
2038 |
-
</html>
|
|
|
15 |
{% endfor %}
|
16 |
{% endfor %}
|
17 |
<style>
|
18 |
+
/* Existing styles remain unchanged */
|
19 |
body {
|
20 |
font-family: Arial, sans-serif;
|
21 |
background-color: #fdf4e3;
|
|
|
676 |
50% { transform: scale(1.1); }
|
677 |
100% { transform: scale(1); }
|
678 |
}
|
679 |
+
.highlight-item {
|
680 |
+
animation: highlight 2s ease-out;
|
681 |
+
}
|
682 |
+
@keyframes highlight {
|
683 |
+
0% { background-color: #FFFFE0; }
|
684 |
+
100% { background-color: transparent; }
|
685 |
+
}
|
686 |
+
.loading-overlay {
|
687 |
+
position: fixed;
|
688 |
+
top: 0;
|
689 |
+
left: 0;
|
690 |
+
width: 100%;
|
691 |
+
height: 100%;
|
692 |
+
background-color: rgba(255,255,255,0.8);
|
693 |
+
display: flex;
|
694 |
+
justify-content: center;
|
695 |
+
align-items: center;
|
696 |
+
z-index: 2000;
|
697 |
+
display: none;
|
698 |
+
}
|
699 |
+
.spinner {
|
700 |
+
width: 40px;
|
701 |
+
height: 40px;
|
702 |
+
border: 4px solid #f3f3f3;
|
703 |
+
border-top: 4px solid #0FAA39;
|
704 |
+
border-radius: 50%;
|
705 |
+
animation: spin 1s linear infinite;
|
706 |
+
}
|
707 |
+
@keyframes spin {
|
708 |
+
0% { transform: rotate(0deg); }
|
709 |
+
100% { transform: rotate(360deg); }
|
710 |
+
}
|
711 |
@media (max-width: 576px) {
|
712 |
.fixed-top-bar {
|
713 |
height: 60px;
|
|
|
979 |
<h3>{{ section }}</h3>
|
980 |
<div class="row">
|
981 |
{% for item in items %}
|
982 |
+
<div class="col-md-6 mb-4" data-item-name="{{ item.Name | default('Unnamed Item') | e }}">
|
983 |
<div class="card menu-card">
|
984 |
<video
|
985 |
class="card-img-top menu-video"
|
|
|
1001 |
<div>
|
1002 |
<h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
|
1003 |
<p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
|
1004 |
+
<div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}">Show Details</div>
|
|
|
|
|
1005 |
</div>
|
1006 |
<div class="d-flex flex-column align-item-center justify-content-center">
|
1007 |
<div class="button-container"
|
|
|
1010 |
data-item-image="{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}"
|
1011 |
data-item-section="{{ item.Section__c | default(section) }}"
|
1012 |
data-item-category="{{ selected_category }}">
|
1013 |
+
<button class="btn btn-primary"
|
1014 |
+
data-bs-toggle="modal"
|
1015 |
+
data-bs-target="#itemModal"
|
1016 |
+
onclick="showItemDetails('{{ item.Name | default('Unnamed Item') | e }}', '{{ item.Price__c | default('0.00') }}', '{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}', '{{ item.Description__c | default('No description') | e }}', '{{ item.IngredientsInfo__c | default('Not specified') | e }}', '{{ item.NutritionalInfo__c | default('Not available') | e }}', '{{ item.Allergens__c | default('None listed') | e }}', '{{ item.Section__c | default(section) | e }}', '{{ selected_category | e }}')">
|
1017 |
+
ADD
|
1018 |
+
</button>
|
|
|
|
|
|
|
|
|
1019 |
{% if item.Section__c != 'Apetizer' and item.Section__c != 'Customized dish' and item.Section__c != 'Soft Drinks' %}
|
1020 |
<span class="customisable-text">Customisable</span>
|
1021 |
{% endif %}
|
|
|
1023 |
</div>
|
1024 |
</div>
|
1025 |
</div>
|
1026 |
+
<div class="item-details" id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">
|
1027 |
+
<h6>Description</h6>
|
1028 |
+
<p>{{ item.Description__c | default('No description available') }}</p>
|
1029 |
+
<h6>Ingredients Info</h6>
|
1030 |
+
<p>{{ item.IngredientsInfo__c | default('Not specified') }}</p>
|
1031 |
+
<h6>Nutritional Info</h6>
|
1032 |
+
<p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
|
1033 |
+
<h6>Allergens</h6>
|
1034 |
+
<p>{{ item.Allergens__c | default('None listed') }}</p>
|
1035 |
+
</div>
|
|
|
|
|
1036 |
</div>
|
1037 |
</div>
|
1038 |
{% endfor %}
|
|
|
1110 |
</div>
|
1111 |
</div>
|
1112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1113 |
<!-- Mic Popup -->
|
1114 |
<div class="mic-popup" id="micPopup">
|
1115 |
<div class="mic-popup-icon">
|
|
|
1119 |
<button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
|
1120 |
</div>
|
1121 |
|
1122 |
+
<!-- Loading Overlay -->
|
1123 |
+
<div class="loading-overlay" id="loadingOverlay">
|
1124 |
+
<div class="spinner"></div>
|
1125 |
+
</div>
|
1126 |
+
|
1127 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
1128 |
<script>
|
1129 |
let isProcessingRequest = false;
|
|
|
1130 |
|
1131 |
const menuItems = [
|
1132 |
{% for section, items in ordered_menu.items() %}
|
|
|
1152 |
"Whole Wheat Flour", "Yogurt (Curd)"
|
1153 |
];
|
1154 |
|
|
|
1155 |
function sanitizeInput(input) {
|
1156 |
const div = document.createElement('div');
|
1157 |
div.textContent = input;
|
|
|
1204 |
};
|
1205 |
}
|
1206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1207 |
function updateCartUI(cart) {
|
1208 |
if (!Array.isArray(cart)) {
|
1209 |
console.error('Invalid cart data:', cart);
|
|
|
1222 |
}
|
1223 |
}
|
1224 |
|
1225 |
+
function handleHighlightedItem() {
|
1226 |
+
const urlParams = new URLSearchParams(window.location.search);
|
1227 |
+
const highlightItemName = urlParams.get('highlight');
|
1228 |
+
|
1229 |
+
if (highlightItemName) {
|
1230 |
+
const itemData = JSON.parse(sessionStorage.getItem('highlightItem'));
|
1231 |
+
|
1232 |
+
if (itemData) {
|
1233 |
+
setTimeout(() => {
|
1234 |
+
const itemElement = document.querySelector(`[data-item-name="${itemData.name}"]`);
|
1235 |
+
|
1236 |
+
if (itemElement) {
|
1237 |
+
itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
1238 |
+
itemElement.classList.add('highlight-item');
|
1239 |
+
setTimeout(() => {
|
1240 |
+
itemElement.classList.remove('highlight-item');
|
1241 |
+
}, 2000);
|
1242 |
+
|
1243 |
+
const addButton = itemElement.querySelector('.btn-primary');
|
1244 |
+
const buttonContainer = addButton.closest('.button-container');
|
1245 |
+
const name = buttonContainer.getAttribute('data-item-name');
|
1246 |
+
const price = buttonContainer.getAttribute('data-item-price');
|
1247 |
+
const image = buttonContainer.getAttribute('data-item-image');
|
1248 |
+
const category = buttonContainer.getAttribute('data-item-category');
|
1249 |
+
const section = buttonContainer.getAttribute('data-item-section');
|
1250 |
+
|
1251 |
+
const description = itemElement.querySelector('.item-details h6:contains("Description") + p')?.textContent || 'No description available';
|
1252 |
+
const ingredients = itemElement.querySelector('.item-details h6:contains("Ingredients Info") + p')?.textContent || 'Not specified';
|
1253 |
+
const nutrition = itemElement.querySelector('.item-details h6:contains("Nutritional Info") + p')?.textContent || 'Not available';
|
1254 |
+
const allergens = itemElement.querySelector('.item-details h6:contains("Allergens") + p')?.textContent || 'None listed';
|
1255 |
+
|
1256 |
+
showItemDetails(
|
1257 |
+
name,
|
1258 |
+
price,
|
1259 |
+
image,
|
1260 |
+
description,
|
1261 |
+
ingredients,
|
1262 |
+
nutrition,
|
1263 |
+
allergens,
|
1264 |
+
section,
|
1265 |
+
category
|
1266 |
+
);
|
1267 |
+
|
1268 |
+
const modal = new bootstrap.Modal(document.getElementById('itemModal'));
|
1269 |
+
modal.show();
|
1270 |
+
}
|
1271 |
+
|
1272 |
+
sessionStorage.removeItem('highlightItem');
|
1273 |
+
window.history.replaceState({}, document.title, window.location.pathname);
|
1274 |
+
}, 500);
|
1275 |
+
}
|
1276 |
+
}
|
1277 |
+
}
|
1278 |
+
|
1279 |
document.addEventListener('DOMContentLoaded', function () {
|
1280 |
+
handleHighlightedItem();
|
1281 |
+
|
1282 |
const avatarContainer = document.querySelector('.avatar-dropdown-container');
|
1283 |
const dropdownMenu = document.querySelector('.dropdown-menu');
|
1284 |
const avatarIcon = document.getElementById('avatarIcon');
|
|
|
1286 |
const deleteAvatar = document.getElementById('deleteAvatar');
|
1287 |
const viewAvatar = document.getElementById('viewAvatar');
|
1288 |
|
|
|
1289 |
function loadAvatar() {
|
1290 |
const savedAvatar = localStorage.getItem('userAvatar');
|
1291 |
const avatarImage = avatarIcon.querySelector('.avatar-image');
|
|
|
1304 |
}
|
1305 |
}
|
1306 |
|
|
|
1307 |
function ensureAvatarOptions() {
|
1308 |
if (!document.querySelector('#deleteAvatar')) {
|
1309 |
const deleteItem = document.createElement('div');
|
|
|
1324 |
}
|
1325 |
}
|
1326 |
|
|
|
1327 |
function removeAvatarOptions() {
|
1328 |
const deleteItem = document.getElementById('deleteAvatar');
|
1329 |
const viewItem = document.getElementById('viewAvatar');
|
|
|
1331 |
if (viewItem) viewItem.remove();
|
1332 |
}
|
1333 |
|
|
|
1334 |
function saveAvatar(imageUrl) {
|
1335 |
localStorage.setItem('userAvatar', imageUrl);
|
1336 |
}
|
1337 |
|
|
|
1338 |
function clearAvatar() {
|
1339 |
localStorage.removeItem('userAvatar');
|
1340 |
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
|
1341 |
removeAvatarOptions();
|
1342 |
}
|
1343 |
|
|
|
1344 |
avatarIcon.addEventListener('click', function(event) {
|
1345 |
event.stopPropagation();
|
1346 |
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
|
1347 |
});
|
1348 |
|
|
|
1349 |
avatarUpload.addEventListener('change', function(event) {
|
1350 |
const file = event.target.files[0];
|
1351 |
if (file) {
|
1352 |
+
const maxSize = 15 * 1024 * 1024;
|
1353 |
if (file.size > maxSize) {
|
1354 |
alert('Image size must not exceed 15MB.');
|
1355 |
this.value = '';
|
|
|
1382 |
ensureAvatarOptions();
|
1383 |
dropdownMenu.style.display = 'none';
|
1384 |
} else {
|
1385 |
+
alert('Failed to upload image: ' + (data.error || 'Unknown error'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1386 |
}
|
1387 |
})
|
1388 |
.catch(error => {
|
1389 |
console.error('Upload error:', error);
|
1390 |
+
alert('Error uploading image.');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1391 |
});
|
1392 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1393 |
reader.readAsDataURL(file);
|
1394 |
}
|
1395 |
});
|
1396 |
|
|
|
1397 |
function addDeleteListener(deleteElement) {
|
1398 |
deleteElement.addEventListener('click', function() {
|
1399 |
fetch('/delete_avatar', {
|
|
|
1402 |
'Content-Type': 'application/json'
|
1403 |
}
|
1404 |
})
|
1405 |
+
.then(response => response.json())
|
|
|
|
|
|
|
1406 |
.then(data => {
|
1407 |
if (data.success) {
|
1408 |
clearAvatar();
|
|
|
1413 |
})
|
1414 |
.catch(error => {
|
1415 |
console.error('Delete error:', error);
|
1416 |
+
alert('Error deleting image.');
|
1417 |
});
|
1418 |
});
|
1419 |
}
|
1420 |
|
|
|
1421 |
function addViewListener(viewElement) {
|
1422 |
viewElement.addEventListener('click', function() {
|
1423 |
const avatarImage = avatarIcon.querySelector('.avatar-image');
|
|
|
1426 |
modalImg.src = avatarImage.src;
|
1427 |
const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
|
1428 |
modal.show();
|
|
|
|
|
1429 |
}
|
1430 |
dropdownMenu.style.display = 'none';
|
1431 |
});
|
1432 |
}
|
1433 |
|
|
|
1434 |
if (deleteAvatar) addDeleteListener(deleteAvatar);
|
1435 |
if (viewAvatar) addViewListener(viewAvatar);
|
1436 |
|
|
|
1437 |
loadAvatar();
|
1438 |
|
|
|
1439 |
document.addEventListener('click', function(event) {
|
1440 |
if (!avatarContainer.contains(event.target)) {
|
1441 |
dropdownMenu.style.display = 'none';
|
|
|
1444 |
|
1445 |
const menuCards = document.querySelectorAll('.menu-card');
|
1446 |
const menuVideos = document.querySelectorAll('.menu-video');
|
1447 |
+
const cardObserver = new IntersectionObserver((entries) => {
|
1448 |
entries.forEach(entry => {
|
1449 |
if (entry.isIntersecting) {
|
1450 |
entry.target.classList.add('visible');
|
|
|
1451 |
}
|
1452 |
});
|
1453 |
+
}, { threshold: 0.1 });
|
1454 |
+
const videoObserver = new IntersectionObserver((entries) => {
|
|
|
|
|
|
|
|
|
1455 |
entries.forEach(entry => {
|
1456 |
if (entry.isIntersecting) {
|
1457 |
const video = entry.target;
|
1458 |
const src = video.getAttribute('data-src');
|
1459 |
+
if (src) {
|
1460 |
const source = video.querySelector('source');
|
1461 |
source.src = src;
|
1462 |
video.load();
|
1463 |
}
|
1464 |
video.classList.add('loaded');
|
|
|
1465 |
}
|
1466 |
});
|
1467 |
+
}, { rootMargin: '200px' });
|
|
|
|
|
|
|
|
|
1468 |
menuCards.forEach(card => cardObserver.observe(card));
|
1469 |
menuVideos.forEach(video => videoObserver.observe(video));
|
1470 |
|
|
|
1473 |
link.addEventListener('click', function () {
|
1474 |
const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
|
1475 |
const detailsDiv = document.getElementById(`details-${itemName}`);
|
1476 |
+
const isShown = detailsDiv.classList.contains('show');
|
1477 |
+
document.querySelectorAll('.item-details.show').forEach(other => {
|
1478 |
+
if (other !== detailsDiv) {
|
1479 |
+
other.classList.remove('show');
|
1480 |
+
other.previousElementSibling.querySelector('.toggle-details').innerText = 'Show Details';
|
|
|
|
|
|
|
|
|
1481 |
}
|
1482 |
});
|
1483 |
+
detailsDiv.classList.toggle('show');
|
1484 |
+
this.innerText = isShown ? 'Show Details' : 'Hide Details';
|
|
|
|
|
|
|
|
|
|
|
|
|
1485 |
});
|
1486 |
});
|
1487 |
|
|
|
1504 |
const searchBar = document.getElementById('searchBar');
|
1505 |
const suggestionsContainer = document.getElementById('autocompleteSuggestions');
|
1506 |
const debouncedFilterMenu = debounce(filterMenu, 300);
|
1507 |
+
|
1508 |
+
searchBar.addEventListener('click', function (event) {
|
1509 |
+
event.stopPropagation();
|
1510 |
+
document.getElementById('loadingOverlay').style.display = 'flex';
|
1511 |
+
window.location.href = '/search';
|
1512 |
+
});
|
1513 |
+
|
1514 |
searchBar.addEventListener('input', function () {
|
1515 |
const input = sanitizeInput(this.value.trim().toLowerCase());
|
1516 |
suggestionsContainer.innerHTML = '';
|
1517 |
suggestionsContainer.style.display = 'none';
|
1518 |
if (input) {
|
1519 |
+
const filteredItems = menuItems.filter(item => item.toLowerCase().includes(input));
|
|
|
|
|
1520 |
if (filteredItems.length > 0) {
|
1521 |
filteredItems.forEach(item => {
|
1522 |
const suggestionDiv = document.createElement('div');
|
|
|
1534 |
}
|
1535 |
debouncedFilterMenu();
|
1536 |
});
|
1537 |
+
|
1538 |
document.addEventListener('click', function (event) {
|
1539 |
if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
|
1540 |
suggestionsContainer.style.display = 'none';
|
|
|
1595 |
});
|
1596 |
}
|
1597 |
|
|
|
1598 |
const customDishForm = document.getElementById('customDishForm');
|
1599 |
if (customDishForm) {
|
1600 |
customDishForm.addEventListener('submit', function(event) {
|
1601 |
+
event.preventDefault();
|
1602 |
const dishName = document.getElementById('custom-dish-name').value.trim();
|
1603 |
const description = document.getElementById('custom-dish-description').value.trim();
|
1604 |
if (!dishName || !description) {
|
1605 |
+
alert('Please fill in both fields.');
|
|
|
1606 |
return;
|
1607 |
}
|
|
|
1608 |
fetch('/customdish/generate_custom_dish', {
|
1609 |
method: 'POST',
|
1610 |
headers: {
|
|
|
1621 |
alert('Custom dish submitted successfully!');
|
1622 |
window.location.reload();
|
1623 |
} else {
|
1624 |
+
alert('Failed to submit: ' + (data.error || 'Unknown error'));
|
1625 |
}
|
1626 |
})
|
1627 |
.catch(error => {
|
1628 |
+
console.error('Error:', error);
|
1629 |
+
alert('Error submitting custom dish.');
|
1630 |
});
|
1631 |
});
|
1632 |
}
|
1633 |
|
1634 |
fetch('/cart/get')
|
1635 |
+
.then(response => response.json())
|
|
|
|
|
|
|
|
|
|
|
1636 |
.then(data => {
|
1637 |
if (data.success) {
|
1638 |
updateCartUI(data.cart);
|
1639 |
} else {
|
1640 |
+
updateCartUI(getCartLocalStorage());
|
|
|
|
|
1641 |
}
|
1642 |
})
|
1643 |
.catch(err => {
|
1644 |
console.error('Error fetching cart:', err);
|
1645 |
+
updateCartUI(getCartLocalStorage());
|
|
|
1646 |
});
|
1647 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1648 |
const decreaseBtn = document.getElementById('decreaseQuantity');
|
1649 |
const increaseBtn = document.getElementById('increaseQuantity');
|
1650 |
const quantityInput = document.getElementById('quantityInput');
|
1651 |
decreaseBtn.addEventListener('click', function () {
|
1652 |
+
let qty = parseInt(quantityInput.value);
|
1653 |
+
if (qty > 1) quantityInput.value = qty - 1;
|
|
|
|
|
|
|
1654 |
});
|
1655 |
increaseBtn.addEventListener('click', function () {
|
1656 |
+
let qty = parseInt(quantityInput.value);
|
1657 |
+
quantityInput.value = qty + 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1658 |
});
|
1659 |
|
|
|
1660 |
const micIcon = document.getElementById('micIcon');
|
1661 |
const micUnsupported = document.getElementById('micUnsupported');
|
1662 |
const micPopup = document.getElementById('micPopup');
|
|
|
1679 |
recognition.onresult = (event) => {
|
1680 |
let interimTranscript = '';
|
1681 |
let finalTranscript = '';
|
|
|
1682 |
for (let i = event.resultIndex; i < event.results.length; i++) {
|
1683 |
const transcript = event.results[i][0].transcript;
|
1684 |
+
if (event.results[i].isFinal) finalTranscript += transcript;
|
1685 |
+
else interimTranscript += transcript;
|
|
|
|
|
|
|
1686 |
}
|
1687 |
+
if (interimTranscript) micPopupText.textContent = interimTranscript;
|
|
|
|
|
|
|
|
|
1688 |
if (finalTranscript) {
|
1689 |
searchBar.value = sanitizeInput(finalTranscript.trim());
|
1690 |
debouncedFilterMenu();
|
|
|
1694 |
|
1695 |
recognition.onend = () => {
|
1696 |
micIcon.classList.remove('active');
|
1697 |
+
setTimeout(() => micPopup.classList.remove('active'), 1000);
|
|
|
|
|
|
|
|
|
1698 |
};
|
1699 |
|
1700 |
recognition.onerror = (event) => {
|
1701 |
micIcon.classList.remove('active');
|
1702 |
micPopupText.textContent = 'Error: ' + event.error;
|
1703 |
+
setTimeout(() => micPopup.classList.remove('active'), 2000);
|
|
|
|
|
1704 |
console.error('Speech error:', event.error);
|
1705 |
};
|
1706 |
|
|
|
1709 |
recognition.start();
|
1710 |
} catch (e) {
|
1711 |
micPopupText.textContent = 'Error starting microphone';
|
1712 |
+
setTimeout(() => micPopup.classList.remove('active'), 2000);
|
1713 |
+
console.error('Error:', e);
|
|
|
|
|
1714 |
}
|
1715 |
});
|
1716 |
|
|
|
1725 |
}
|
1726 |
});
|
1727 |
|
1728 |
+
window.addEventListener('load', function() {
|
1729 |
+
document.getElementById('loadingOverlay').style.display = 'none';
|
1730 |
+
});
|
1731 |
+
|
1732 |
function filterMenu() {
|
1733 |
const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
|
1734 |
const sections = document.querySelectorAll('h3');
|
|
|
1770 |
function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
|
1771 |
document.getElementById('modal-name').innerText = name;
|
1772 |
document.getElementById('modal-price').innerText = `$${price}`;
|
1773 |
+
document.getElementById('modal-img').src = image || '/static/placeholder.jpg';
|
|
|
1774 |
document.getElementById('modal-description').innerText = description || 'No description available.';
|
1775 |
document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
|
1776 |
document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
|
|
|
1812 |
optionsContainer.appendChild(listItem);
|
1813 |
});
|
1814 |
sectionDiv.appendChild(optionsContainer);
|
1815 |
+
addonsList.appendChild(sectionDiv);
|
1816 |
});
|
1817 |
})
|
1818 |
.catch(err => {
|
|
|
1898 |
}
|
1899 |
</script>
|
1900 |
</body>
|
1901 |
+
</html>
|