lokesh341 commited on
Commit
ce4323d
·
verified ·
1 Parent(s): 9715188

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. 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
- {% if item.Section__c != 'Soft Drinks' %}
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
- {% if item.Section__c == 'Soft Drinks' %}
983
- <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)">ADD</button>
984
- {% else %}
985
- <button class="btn btn-primary"
986
- data-bs-toggle="modal"
987
- data-bs-target="#itemModal"
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
- {% if item.Section__c != 'Soft Drinks' %}
1000
- <div class="item-details" id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">
1001
- <h6>Description</h6>
1002
- <p>{{ item.Description__c | default('No description available') }}</p>
1003
- <h6>Ingredients Info</h6>
1004
- <p>{{ item.Ingredientsinfo__c | default('Not specified') }}</p>
1005
- <h6>Nutritional Info</h6>
1006
- <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
1007
- <h6>Allergens</h6>
1008
- <p>{{ item.Allergens__c | default('None listed') }}</p>
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; // 15MB in bytes
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. Using default image.'));
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. Using default image as fallback.');
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. Please try again.');
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, observer) => {
1516
  entries.forEach(entry => {
1517
  if (entry.isIntersecting) {
1518
  entry.target.classList.add('visible');
1519
- observer.unobserve(entry.target);
1520
  }
1521
  });
1522
- }, {
1523
- root: null,
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 && !video.querySelector('source[src="' + 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 isCurrentlyShown = detailsDiv.classList.contains('show');
1555
-
1556
- document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1557
- if (otherDetails !== detailsDiv) {
1558
- otherDetails.classList.remove('show');
1559
- const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1560
- if (otherLink) {
1561
- otherLink.innerText = 'Show Details';
1562
- }
1563
  }
1564
  });
1565
-
1566
- if (!isCurrentlyShown) {
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
- event.preventDefault();
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 custom dish: ' + (data.error || 'Unknown error'));
1709
  }
1710
  })
1711
  .catch(error => {
1712
- console.error('Error submitting custom dish:', error);
1713
- alert('Error submitting custom dish. Please try again.');
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
- console.error('Failed to fetch cart:', data.error);
1730
- const cart = getCartLocalStorage();
1731
- updateCartUI(cart);
1732
  }
1733
  })
1734
  .catch(err => {
1735
  console.error('Error fetching cart:', err);
1736
- const cart = getCartLocalStorage();
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 currentQuantity = parseInt(quantityInput.value);
1752
- if (currentQuantity > 1) {
1753
- currentQuantity--;
1754
- quantityInput.value = currentQuantity;
1755
- }
1756
  });
1757
  increaseBtn.addEventListener('click', function () {
1758
- let currentQuantity = parseInt(quantityInput.value);
1759
- currentQuantity++;
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
- finalTranscript += transcript;
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
- if (micPopup.classList.contains('active')) {
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
- micPopup.classList.remove('active');
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
- const modalImg = document.getElementById('modal-img');
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
- addonsList.appendChild(sectionDiv);
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>