lokesh341 commited on
Commit
1e496b7
·
verified ·
1 Parent(s): c32eb64

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. templates/menu.html +624 -536
templates/menu.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
@@ -26,6 +27,7 @@
26
  }
27
  .container {
28
  max-width: 900px;
 
29
  }
30
  .menu-card {
31
  max-width: 350px;
@@ -36,11 +38,12 @@
36
  display: flex;
37
  flex-direction: column;
38
  opacity: 0;
39
- transition: opacity 0.3s ease-in-out;
40
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
41
  }
42
  .menu-card.visible {
43
  opacity: 1;
 
44
  }
45
  .menu-video {
46
  height: 200px;
@@ -78,11 +81,12 @@
78
  font-weight: bold;
79
  border-radius: 5px;
80
  border: none;
81
- transition: background-color 0.3s ease;
82
  margin-left: 13px;
83
  }
84
  .addbutton .btn:hover {
85
  background-color: #218838;
 
86
  }
87
  .button-container {
88
  display: flex;
@@ -174,6 +178,11 @@
174
  border: 1px solid #ffd8b1;
175
  padding: 5px 0;
176
  z-index: 1000;
 
 
 
 
 
177
  }
178
  .dropdown-menu .dropdown-item {
179
  padding: 10px 16px;
@@ -204,7 +213,7 @@
204
  color: #2c2c2c;
205
  }
206
  .fixed-top-bar {
207
- position: relative;
208
  top: 0;
209
  left: 0;
210
  width: 100%;
@@ -216,6 +225,7 @@
216
  justify-content: space-between;
217
  align-items: center;
218
  z-index: 1000;
 
219
  }
220
  .search-bar-container {
221
  position: absolute;
@@ -237,6 +247,10 @@
237
  background-color: #fff;
238
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
239
  outline: none;
 
 
 
 
240
  }
241
  .search-bar-container input::placeholder {
242
  color: #888;
@@ -288,6 +302,7 @@
288
  cursor: pointer;
289
  font-size: 14px;
290
  color: #333;
 
291
  }
292
  .autocomplete-suggestions .suggestion-item:hover {
293
  background-color: #f1f1f1;
@@ -565,13 +580,56 @@
565
  #custom-dish-form {
566
  position: relative;
567
  padding-bottom: 80px;
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  }
569
  #custom-dish-form .btn-primary {
570
- position: absolute;
571
- right: 15px;
572
- bottom: 15px;
573
- width: auto;
574
  padding: 10px 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  }
576
  .bottom-action-bar {
577
  position: fixed;
@@ -602,6 +660,10 @@
602
  text-align: center;
603
  min-width: 0;
604
  white-space: nowrap;
 
 
 
 
605
  }
606
  .bottom-action-bar .btn-order-history {
607
  background-color: #FFA07A;
@@ -630,6 +692,7 @@
630
  justify-content: center;
631
  font-size: 12px;
632
  margin-left: 8px;
 
633
  }
634
  .mic-popup {
635
  position: fixed;
@@ -872,75 +935,114 @@
872
  padding: 6px 16px;
873
  font-size: 14px;
874
  }
 
 
 
 
875
  }
876
  </style>
877
  </head>
878
  <body>
879
- <div class="fixed-top-bar">
880
  <div class="avatar-dropdown-container">
881
- <div class="avatar-icon" id="avatarIcon">
882
  {% if user_image %}
883
  <img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
884
  {% else %}
885
  <span>{{ first_letter }}</span>
886
  {% endif %}
887
  </div>
888
- <div class="dropdown-menu" id="avatarDropdown">
889
  {% if user_image %}
890
- <div class="dropdown-item delete-item" id="deleteAvatar">Delete Image</div>
891
- <div class="dropdown-item view-item" id="viewAvatar">View Avatar</div>
892
  {% endif %}
893
- <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item">Order History</a>
894
- <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item">View Profile</a>
895
- <div class="dropdown-item upload-item">
896
  <label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
897
  Upload Image
898
  </label>
899
- <input type="file" id="avatarUpload" accept="image/*" style="display: none;">
900
  </div>
901
- <a href="{{ url_for('logout') }}" class="dropdown-item">Logout</a>
902
  </div>
903
  </div>
904
  <div class="search-bar-container">
905
- <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off">
906
  <i class="bi bi-search search-icon"></i>
907
- <i class="bi bi-mic mic-icon" id="micIcon"></i>
908
  <span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
909
  <div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
910
  </div>
911
  </div>
912
 
913
- <form method="get" action="/menu" class="text-center mb-4" id="categoryForm">
914
- <label class="form-label fw-bold">Select a Category:</label>
915
  <div class="category-buttons">
916
  {% for category in categories %}
917
- <button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}">{{ category }}</button>
918
  {% endfor %}
919
- <button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish">Customized Dish</button>
920
  </div>
921
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
922
  </form>
923
 
924
- <div class="container mt-4">
925
  {% if selected_category == "Customized Dish" %}
926
  <div id="custom-dish-form" class="mt-4">
927
  <h3>Create Your Custom Dish</h3>
928
- <form method="POST" action="/customdish/generate_custom_dish" id="customDishForm">
929
- <div class="mb-3">
 
 
 
 
 
 
 
 
930
  <label for="custom-dish-name" class="form-label">Dish Name</label>
931
- <input type="text" class="form-control" id="custom-dish-name" name="name" required>
 
932
  </div>
933
- <div class="mb-3 position-relative">
 
 
 
 
934
  <label for="custom-dish-description" class="form-label">Dish Description</label>
935
- <textarea class="form-control" id="custom-dish-description" name="description" required></textarea>
936
- <div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
 
 
 
937
  </div>
938
- <button type="submit" class="btn btn-primary">Submit Custom Dish</button>
 
939
  </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
  </div>
941
  {% else %}
942
  {% if ordered_menu.items()|length == 0 %}
943
- <p>No menu items available for this category.</p>
944
  {% else %}
945
  {% for section, items in ordered_menu.items() %}
946
  <h3>{{ section }}</h3>
@@ -959,7 +1061,8 @@
959
  height="200"
960
  onmouseover="this.play()"
961
  onmouseout="this.pause(); this.currentTime = 0;"
962
- onerror="this.poster='/static/placeholder.jpg';">
 
963
  <source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
964
  Your browser does not support the video tag.
965
  </video>
@@ -969,7 +1072,7 @@
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">
@@ -980,12 +1083,13 @@
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 %}
@@ -1017,11 +1121,11 @@
1017
  {% endif %}
1018
  </div>
1019
 
1020
- <div class="bottom-action-bar">
1021
- <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history">
1022
  <i class="bi bi-clock-history"></i> Order History
1023
  </a>
1024
- <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart">
1025
  <i class="bi bi-cart"></i> View Cart
1026
  <span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
1027
  </a>
@@ -1051,17 +1155,17 @@
1051
  </div>
1052
  <div class="mt-4">
1053
  <h6>Custom Request</h6>
1054
- <textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..."></textarea>
1055
  </div>
1056
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
1057
  </div>
1058
  <div class="modal-footer d-flex align-items-center justify-content-between">
1059
  <div class="d-flex align-items-center gap-2">
1060
- <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity">-</button>
1061
- <input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;"/>
1062
- <button type="button" class="btn btn-outline-secondary" id="increaseQuantity">+</button>
1063
  </div>
1064
- <button type="button" class="btn btn-primary" onclick="addToCartFromModal()">Add to Cart</button>
1065
  </div>
1066
  </div>
1067
  </div>
@@ -1079,7 +1183,7 @@
1079
  <img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
1080
  </div>
1081
  <div class="modal-footer">
1082
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
1083
  </div>
1084
  </div>
1085
  </div>
@@ -1101,26 +1205,45 @@
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">
1120
  <i class="bi bi-mic-fill"></i>
1121
  </div>
1122
  <div class="mic-popup-text" id="micPopupText">Listening...</div>
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>
@@ -1136,23 +1259,29 @@
1136
  {% endfor %}
1137
  ];
1138
 
1139
- const ingredientsList = [
1140
- "Basmati Rice", "Bell Pepper", "Biryani Masala", "Butter", "Capsicum", "Cauliflower",
1141
- "Chickpea Flour (Besan)", "Chickpea Flour (for batter)", "Chickpeas (Channa)", "Chili Powder",
1142
- "Chili Sauce", "Coconut Milk", "Coriander Powder", "Cornflour", "Cream", "Cumin Powder",
1143
- "Cumin Seeds", "Curd (Yogurt)", "Curry Leaves", "Fish (e.g., King Fish or Salmon)",
1144
- "Fresh Coriander Leaves", "Garam Masala", "Garlic", "Ghee (Clarified Butter)", "Ginger",
1145
- "Ginger-Garlic Paste", "Goat Meat (Mutton)", "Green Chilies", "Honey",
1146
- "Kasuri Methi (dried fenugreek leaves)", "Lemon Juice", "Mango Puree", "Mint Leaves",
1147
- "Mixed Vegetables (Carrot, Peas, Potato, Cauliflower)", "Mixed Vegetables (Carrot, Peas, Potato)",
1148
- "Mustard Seeds", "Mutton (Goat Meat)", "Oil", "Oil (for frying)", "Onion",
1149
- "Paneer (Indian Cottage Cheese)", "Peas", "Potatoes", "Prawns", "Red Chili Powder",
1150
- "Rice Flour", "Saffron", "Salt", "Soy Sauce", "Spring Onion", "Tamarind (for sourness)",
1151
- "Tomato Ketchup", "Tomatoes", "Turmeric Powder", "Vinegar", "Water", "Wheat Flour (for dough)",
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;
@@ -1167,7 +1296,7 @@
1167
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
1168
  );
1169
  if (existingItem) {
1170
- existingItem.quantity = payload.quantity;
1171
  } else {
1172
  cart.push(payload);
1173
  }
@@ -1205,6 +1334,17 @@
1205
  };
1206
  }
1207
 
 
 
 
 
 
 
 
 
 
 
 
1208
  function showSoftDrinkModal(button) {
1209
  currentSoftDrinkButton = button;
1210
  const buttonContainer = button.closest('.button-container');
@@ -1227,6 +1367,10 @@
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'));
@@ -1242,7 +1386,8 @@
1242
  category: selectedCategory,
1243
  addons: [],
1244
  instructions: '',
1245
- quantity: quantity
 
1246
  };
1247
 
1248
  fetch('/cart/add', {
@@ -1257,15 +1402,9 @@
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 => {
@@ -1273,8 +1412,7 @@
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
 
@@ -1304,7 +1442,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 +1460,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 +1480,6 @@
1344
  }
1345
  }
1346
 
1347
- // Remove avatar options
1348
  function removeAvatarOptions() {
1349
  const deleteItem = document.getElementById('deleteAvatar');
1350
  const viewItem = document.getElementById('viewAvatar');
@@ -1352,25 +1487,21 @@
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) {
@@ -1407,17 +1538,7 @@
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 => {
@@ -1436,51 +1557,43 @@
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', {
1459
- method: 'POST',
1460
- headers: {
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();
1471
  dropdownMenu.style.display = 'none';
1472
- } else {
1473
- alert('Failed to delete image: ' + (data.error || 'Unknown error'));
1474
- }
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');
@@ -1496,14 +1609,11 @@
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';
@@ -1559,6 +1669,7 @@
1559
  const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1560
  if (otherLink) {
1561
  otherLink.innerText = 'Show Details';
 
1562
  }
1563
  }
1564
  });
@@ -1566,9 +1677,11 @@
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
  });
@@ -1578,7 +1691,8 @@
1578
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
1579
  if (!selectedCategoryInput.value) {
1580
  selectedCategoryInput.value = "All";
1581
- document.querySelector('.category-button[data-category="All"]').classList.add('selected');
 
1582
  }
1583
  categoryButtons.forEach(button => {
1584
  button.addEventListener('click', function () {
@@ -1591,7 +1705,6 @@
1591
 
1592
  const searchBar = document.getElementById('searchBar');
1593
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1594
- const debouncedFilterMenu = debounce(filterMenu, 300);
1595
 
1596
  // Redirect to search page on click
1597
  searchBar.addEventListener('click', function (event) {
@@ -1599,269 +1712,283 @@
1599
  window.location.href = '/search';
1600
  });
1601
 
1602
- searchBar.addEventListener('input', function () {
1603
- const input = sanitizeInput(this.value.trim().toLowerCase());
1604
- suggestionsContainer.innerHTML = '';
1605
- suggestionsContainer.style.display = 'none';
1606
- if (input) {
1607
- const filteredItems = menuItems.filter(item =>
1608
- item.toLowerCase().includes(input)
1609
- );
1610
- if (filteredItems.length > 0) {
1611
- filteredItems.forEach(item => {
1612
- const suggestionDiv = document.createElement('div');
1613
- suggestionDiv.classList.add('suggestion-item');
1614
- suggestionDiv.innerText = item;
1615
- suggestionDiv.addEventListener('click', function () {
1616
- searchBar.value = item;
1617
- suggestionsContainer.style.display = 'none';
1618
- debouncedFilterMenu();
1619
- });
1620
- suggestionsContainer.appendChild(suggestionDiv);
1621
- });
1622
- suggestionsContainer.style.display = 'block';
1623
- }
1624
- }
1625
- debouncedFilterMenu();
1626
- });
1627
 
1628
- document.addEventListener('click', function (event) {
1629
- if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
1630
- suggestionsContainer.style.display = 'none';
1631
- }
1632
- });
1633
 
1634
- const descriptionTextarea = document.getElementById('custom-dish-description');
1635
- const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1636
- if (descriptionTextarea && descriptionSuggestions) {
1637
- let usedIngredients = new Set();
1638
- function updateUsedIngredients() {
1639
- const inputText = descriptionTextarea.value.trim();
1640
- usedIngredients.clear();
1641
- if (inputText) {
1642
- const words = inputText.split(/,\s*/).map(word => word.trim());
1643
- words.forEach(word => {
1644
- if (word && ingredientsList.includes(word)) {
1645
- usedIngredients.add(word);
1646
- }
1647
- });
1648
  }
1649
  }
1650
- descriptionTextarea.addEventListener('input', function () {
1651
- const inputText = this.value.trim();
1652
- const words = inputText.split(/,\s*/);
1653
- const lastWord = words[words.length - 1].trim().toLowerCase();
1654
- descriptionSuggestions.innerHTML = '';
1655
- descriptionSuggestions.style.display = 'none';
1656
- updateUsedIngredients();
1657
- if (lastWord) {
1658
- const filteredIngredients = ingredientsList.filter(ingredient =>
1659
- ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
1660
- );
1661
- if (filteredIngredients.length > 0) {
1662
- filteredIngredients.forEach(ingredient => {
1663
- const suggestionDiv = document.createElement('div');
1664
- suggestionDiv.classList.add('suggestion-item');
1665
- suggestionDiv.innerText = ingredient;
1666
- suggestionDiv.addEventListener('click', function () {
1667
- const currentValue = descriptionTextarea.value;
1668
- const lastCommaIndex = currentValue.lastIndexOf(',');
1669
- const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
1670
- descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
1671
- descriptionSuggestions.style.display = 'none';
1672
- descriptionTextarea.focus();
1673
- updateUsedIngredients();
 
 
 
 
 
 
 
 
1674
  });
1675
- descriptionSuggestions.appendChild(suggestionDiv);
1676
  });
1677
- descriptionSuggestions.style.display = 'block';
1678
  }
1679
  }
 
 
 
 
 
1680
  });
1681
- document.addEventListener('click', function (event) {
1682
- if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1683
- descriptionSuggestions.style.display = 'none';
 
 
1684
  }
1685
  });
1686
- }
1687
 
1688
- const customDishForm = document.getElementById('customDishForm');
1689
- if (customDishForm) {
1690
- customDishForm.addEventListener('submit', function(event) {
1691
- const dishName = document.getElementById('custom-dish-name').value.trim();
1692
- const description = document.getElementById('custom-dish-description').value.trim();
1693
- if (!dishName || !description) {
1694
- event.preventDefault();
1695
- alert('Please fill in both the dish name and description.');
1696
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1697
  }
 
 
 
 
 
1698
  event.preventDefault();
1699
- fetch('/customdish/generate_custom_dish', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1700
  method: 'POST',
1701
  headers: {
1702
- 'Content-Type': 'application/x-www-form-urlencoded',
1703
  },
1704
- body: new URLSearchParams({
1705
- 'name': dishName,
1706
- 'description': description
1707
- })
 
 
 
1708
  })
1709
- .then(response => response.json())
1710
  .then(data => {
1711
  if (data.success) {
1712
- alert('Custom dish submitted successfully!');
1713
- window.location.reload();
 
 
 
 
1714
  } else {
1715
- alert('Failed to submit custom dish: ' + (data.error || 'Unknown error'));
1716
  }
1717
  })
1718
  .catch(error => {
1719
  console.error('Error submitting custom dish:', error);
1720
- alert('Error submitting custom dish. Please try again.');
 
 
 
 
1721
  });
1722
  });
1723
- }
1724
 
1725
- fetch('/cart/get')
1726
- .then(response => {
1727
- if (!response.ok) {
1728
- throw new Error(`HTTP error! Status: ${response.status}`);
1729
- }
1730
- return response.json();
1731
- })
1732
- .then(data => {
1733
- if (data.success) {
1734
- updateCartUI(data.cart);
1735
- } else {
1736
- console.error('Failed to fetch cart:', data.error);
1737
- const cart = getCartLocalStorage();
1738
- updateCartUI(cart);
1739
- }
1740
- })
1741
- .catch(err => {
1742
- console.error('Error fetching cart:', err);
1743
- const cart = getCartLocalStorage();
1744
- updateCartUI(cart);
1745
- });
1746
-
1747
- const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
1748
- preloadedVideos.forEach(link => {
1749
- const video = document.createElement('video');
1750
- video.src = link.href;
1751
- video.preload = 'auto';
1752
- });
1753
-
1754
- const decreaseBtn = document.getElementById('decreaseQuantity');
1755
- const increaseBtn = document.getElementById('increaseQuantity');
1756
- const quantityInput = document.getElementById('quantityInput');
1757
- decreaseBtn.addEventListener('click', function () {
1758
- let currentQuantity = parseInt(quantityInput.value);
1759
- if (currentQuantity > 1) {
1760
- currentQuantity--;
1761
- quantityInput.value = currentQuantity;
1762
- }
1763
- });
1764
- increaseBtn.addEventListener('click', function () {
1765
- let currentQuantity = parseInt(quantityInput.value);
1766
- currentQuantity++;
1767
- quantityInput.value = currentQuantity;
1768
- });
1769
-
1770
- const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
1771
- const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
1772
- const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
1773
-
1774
- softDrinkDecreaseBtn.addEventListener('click', function() {
1775
- let currentQuantity = parseInt(softDrinkQuantityInput.value);
1776
- if (currentQuantity > 1) {
1777
- currentQuantity--;
1778
- softDrinkQuantityInput.value = currentQuantity;
1779
- }
1780
- });
1781
-
1782
- softDrinkIncreaseBtn.addEventListener('click', function() {
1783
- let currentQuantity = parseInt(softDrinkQuantityInput.value);
1784
- if (currentQuantity < 1000) {
1785
- currentQuantity++;
1786
- softDrinkQuantityInput.value = currentQuantity;
1787
  }
1788
- });
1789
 
1790
- // Mic Popup Functionality
1791
  const micIcon = document.getElementById('micIcon');
1792
- const micUnsupported = document.getElementById('micUnsupported');
1793
  const micPopup = document.getElementById('micPopup');
1794
  const micPopupText = document.getElementById('micPopupText');
1795
  const micPopupCancel = document.getElementById('micPopupCancel');
1796
-
1797
- if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
 
1798
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1799
  const recognition = new SpeechRecognition();
1800
  recognition.continuous = false;
1801
- recognition.interimResults = true;
1802
  recognition.lang = 'en-US';
1803
-
1804
- recognition.onstart = () => {
1805
- micIcon.classList.add('active');
1806
  micPopup.classList.add('active');
 
 
 
 
1807
  micPopupText.textContent = 'Listening...';
 
1808
  };
1809
-
1810
- recognition.onresult = (event) => {
1811
- let interimTranscript = '';
1812
- let finalTranscript = '';
1813
-
1814
- for (let i = event.resultIndex; i < event.results.length; i++) {
1815
- const transcript = event.results[i][0].transcript;
1816
- if (event.results[i].isFinal) {
1817
- finalTranscript += transcript;
1818
- } else {
1819
- interimTranscript += transcript;
1820
- }
1821
- }
1822
-
1823
- if (interimTranscript) {
1824
- micPopupText.textContent = interimTranscript;
1825
- }
1826
-
1827
- if (finalTranscript) {
1828
- searchBar.value = sanitizeInput(finalTranscript.trim());
1829
- debouncedFilterMenu();
1830
  micPopup.classList.remove('active');
1831
- }
1832
- };
1833
-
1834
- recognition.onend = () => {
1835
- micIcon.classList.remove('active');
1836
- if (micPopup.classList.contains('active')) {
1837
- setTimeout(() => {
1838
- micPopup.classList.remove('active');
1839
- }, 1000);
1840
- }
1841
  };
1842
-
1843
- recognition.onerror = (event) => {
1844
- micIcon.classList.remove('active');
1845
- micPopupText.textContent = 'Error: ' + event.error;
1846
  setTimeout(() => {
1847
  micPopup.classList.remove('active');
 
1848
  }, 2000);
1849
- console.error('Speech error:', event.error);
1850
  };
1851
-
1852
- micIcon.addEventListener('click', () => {
1853
- try {
1854
- recognition.start();
1855
- } catch (e) {
1856
- micPopupText.textContent = 'Error starting microphone';
1857
  setTimeout(() => {
1858
  micPopup.classList.remove('active');
1859
- }, 2000);
1860
- console.error('Recognition start error:', e);
1861
  }
1862
- });
1863
-
1864
- micPopupCancel.addEventListener('click', () => {
1865
  recognition.stop();
1866
  micPopup.classList.remove('active');
1867
  micIcon.classList.remove('active');
@@ -1871,217 +1998,178 @@
1871
  micUnsupported.style.display = 'block';
1872
  }
1873
 
1874
- // Highlight and open modal for item from search redirect
1875
- const highlightItem = "{{ highlight_item | e }}";
1876
- if (highlightItem) {
1877
- const itemElement = document.querySelector(`[data-item-name="${highlightItem}"]`);
1878
- if (itemElement) {
1879
- itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
1880
- itemElement.style.backgroundColor = '#FFFFE0'; // Light yellow highlight
1881
- setTimeout(() => {
1882
- itemElement.style.backgroundColor = ''; // Remove highlight after 2 seconds
1883
- }, 2000);
 
 
 
 
 
 
 
 
 
 
1884
 
1885
- // Find the ADD button and trigger the appropriate modal
1886
- const addButton = itemElement.querySelector('.btn-primary');
1887
- const buttonContainer = addButton.closest('.button-container');
1888
- const section = buttonContainer.getAttribute('data-item-section');
 
 
 
 
 
 
 
 
1889
 
1890
- if (section === 'Soft Drinks') {
1891
- showSoftDrinkModal(addButton);
1892
- } else {
1893
- const name = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
1894
- const price = buttonContainer.getAttribute('data-item-price');
1895
- const image = buttonContainer.getAttribute('data-item-image');
1896
- const category = buttonContainer.getAttribute('data-item-category');
1897
- // Use data from the button's onclick attribute or fetch from server
1898
- const onclickAttr = addButton.getAttribute('onclick');
1899
- if (onclickAttr) {
1900
- const argsMatch = onclickAttr.match(/showItemDetails\((.*?)\)/);
1901
- if (argsMatch) {
1902
- const args = argsMatch[1].split(',').map(arg => arg.trim().replace(/['"]/g, ''));
1903
- const [name, price, image, description, ingredients, nutrition, allergens, section, category] = args;
1904
- showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, category);
1905
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1906
  } else {
1907
- // Fallback if onclick isn't sufficient
1908
- showItemDetails(name, price, image, 'No description available', 'Not specified', 'Not available', 'None listed', section, category);
1909
  }
1910
- }
1911
- }
 
 
 
1912
  }
1913
- });
1914
 
1915
- function filterMenu() {
1916
- const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
1917
- const sections = document.querySelectorAll('h3');
1918
- const items = document.querySelectorAll('.menu-card');
1919
- let matchedSections = new Set();
1920
- items.forEach(item => {
1921
- const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1922
- const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1923
- if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
1924
- item.style.display = 'block';
1925
- item.classList.add('visible');
1926
- matchedSections.add(item.closest('.row'));
1927
- } else {
1928
- item.style.display = 'none';
1929
- }
1930
- });
1931
- sections.forEach(section => {
1932
- const sectionRow = section.nextElementSibling;
1933
- if (matchedSections.has(sectionRow)) {
1934
- section.style.display = 'block';
1935
- sectionRow.style.display = 'flex';
1936
- } else {
1937
- section.style.display = 'none';
1938
- sectionRow.style.display = 'none';
1939
  }
1940
- });
1941
- if (!input) {
1942
- sections.forEach(section => {
1943
- section.style.display = 'block';
1944
- section.nextElementSibling.style.display = 'flex';
1945
- });
1946
- items.forEach(item => {
1947
- item.style.display = 'block';
1948
- item.classList.add('visible');
1949
  });
1950
- }
1951
- }
1952
 
1953
- function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
1954
- document.getElementById('modal-name').innerText = name;
1955
- document.getElementById('modal-price').innerText = `$${price}`;
1956
- const modalImg = document.getElementById('modal-img');
1957
- modalImg.src = image || '/static/placeholder.jpg';
1958
- document.getElementById('modal-description').innerText = description || 'No description available.';
1959
- document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
1960
- document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
1961
- document.getElementById('modal-allergens').innerText = allergens || 'None listed';
1962
- document.getElementById('addons-list').innerHTML = 'Loading customization options...';
1963
- document.getElementById('modal-instructions').value = '';
1964
- const modalSectionEl = document.getElementById('modal-section');
1965
- modalSectionEl.setAttribute('data-section', section);
1966
- modalSectionEl.setAttribute('data-category', selectedCategory);
1967
- document.getElementById('quantityInput').value = 1;
1968
-
1969
- fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
 
 
1970
  .then(response => response.json())
1971
  .then(data => {
1972
- const addonsList = document.getElementById('addons-list');
1973
- addonsList.innerHTML = '';
1974
- if (!data.success || !data.addons || data.addons.length === 0) {
1975
- addonsList.innerHTML = '<p>No customization options available.</p>';
1976
- return;
 
1977
  }
1978
- data.addons.forEach(addon => {
1979
- const sectionDiv = document.createElement('div');
1980
- sectionDiv.classList.add('addon-section');
1981
- const title = document.createElement('h6');
1982
-
1983
- title.innerText = addon.name;
1984
- sectionDiv.appendChild(title);
1985
- const optionsContainer = document.createElement('div');
1986
- addon.options.forEach((option, index) => {
1987
- const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
1988
- const listItem = document.createElement('div');
1989
- listItem.classList.add('form-check');
1990
- listItem.innerHTML = `
1991
- <input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
1992
- data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
1993
- <label class="form-check-label" for="${optionId}">
1994
- ${option} ${addon.extra_charge ? `($${addon.extra_charge_amount})` : ''}
1995
- </label>
1996
- `;
1997
- optionsContainer.appendChild(listItem);
1998
- });
1999
- sectionDiv.appendChild(optionsContainer);
2000
- addonsList.appendChild(sectionDiv);
2001
- });
2002
  })
2003
  .catch(err => {
2004
- console.error('Error fetching add-ons:', err);
2005
- document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
 
 
 
 
 
 
2006
  });
2007
- }
2008
-
2009
- document.addEventListener('click', function(event) {
2010
- if (event.target.classList.contains('addon-option')) {
2011
- handleAddonClick(event.target);
2012
  }
2013
- });
2014
 
2015
- function handleAddonClick(checkbox) {
2016
- const groupName = checkbox.getAttribute('data-group');
2017
- const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it"].includes(groupName);
2018
- const checkboxes = document.querySelectorAll(`.addon-option[data-group="${groupName}"]`);
2019
- if (!isMultiSelectGroup) {
2020
- checkboxes.forEach(cb => {
2021
- if (cb !== checkbox) cb.checked = false;
2022
- });
2023
- }
2024
- }
2025
 
2026
- function addToCartFromModal() {
2027
- const itemName = document.getElementById('modal-name').innerText;
2028
- const itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
2029
- const itemImage = document.getElementById('modal-img').src;
2030
- const instructions = document.getElementById('modal-instructions').value.trim();
2031
- const quantity = parseInt(document.getElementById('quantityInput').value);
2032
- const section = document.getElementById('modal-section').getAttribute('data-section');
2033
- const selectedCategory = document.getElementById('modal-section').getAttribute('data-category');
2034
- const addons = Array.from(document.querySelectorAll('.addon-option:checked')).map(cb => ({
2035
- name: cb.getAttribute('data-name'),
2036
- price: parseFloat(cb.getAttribute('data-price') || 0)
2037
- }));
2038
 
2039
- const cartPayload = {
2040
- itemName: itemName,
2041
- itemPrice: itemPrice,
2042
- itemImage: itemImage,
2043
- section: section,
2044
- category: selectedCategory,
2045
- addons: addons,
2046
- instructions: instructions,
2047
- quantity: quantity
2048
- };
2049
 
2050
- fetch('/cart/add', {
2051
- method: 'POST',
2052
- headers: {
2053
- 'Content-Type': 'application/json',
2054
- },
2055
- body: JSON.stringify(cartPayload)
2056
- })
2057
- .then(response => response.json())
2058
- .then(data => {
2059
- if (data.success) {
2060
- alert('Item added to cart successfully!');
2061
- updateCartUI(data.cart);
2062
- const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
2063
- modal.hide();
2064
- } else {
2065
- console.error('Failed to add item to cart:', data.error);
2066
- alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
2067
- const cart = addToCartLocalStorage(cartPayload);
2068
- updateCartUI(cart);
2069
- const modal = document.getElementById('itemModal');
2070
- const modalInstance = bootstrap.Modal.getInstance(modal);
2071
- modalInstance.hide();
2072
  }
2073
- })
2074
- .catch(err => {
2075
- console.error('Error adding item to cart:', err);
2076
- alert('Error adding item to cart. Using local storage as fallback.');
2077
- const cart = addToCartLocalStorage(cartPayload);
2078
- updateCartUI(cart);
2079
- const modal = document.getElementById('itemModal');
2080
- const modalInstance = bootstrap.Modal.getInstance(modal);
2081
- modalInstance.hide();
2082
  });
2083
- }
 
 
 
2084
  </script>
2085
  </body>
2086
- </html>
2087
-
 
1
+
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
 
27
  }
28
  .container {
29
  max-width: 900px;
30
+ padding: 0 15px;
31
  }
32
  .menu-card {
33
  max-width: 350px;
 
38
  display: flex;
39
  flex-direction: column;
40
  opacity: 0;
41
+ transition: opacity 0.3s ease-in-out, transform 0.3s ease;
42
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
43
  }
44
  .menu-card.visible {
45
  opacity: 1;
46
+ transform: translateY(0);
47
  }
48
  .menu-video {
49
  height: 200px;
 
81
  font-weight: bold;
82
  border-radius: 5px;
83
  border: none;
84
+ transition: background-color 0.3s ease, transform 0.2s ease;
85
  margin-left: 13px;
86
  }
87
  .addbutton .btn:hover {
88
  background-color: #218838;
89
+ transform: scale(1.05);
90
  }
91
  .button-container {
92
  display: flex;
 
178
  border: 1px solid #ffd8b1;
179
  padding: 5px 0;
180
  z-index: 1000;
181
+ animation: slideDown 0.2s ease-out;
182
+ }
183
+ @keyframes slideDown {
184
+ from { opacity: 0; transform: translateY(-10px); }
185
+ to { opacity: 1; transform: translateY(0); }
186
  }
187
  .dropdown-menu .dropdown-item {
188
  padding: 10px 16px;
 
213
  color: #2c2c2c;
214
  }
215
  .fixed-top-bar {
216
+ position: fixed;
217
  top: 0;
218
  left: 0;
219
  width: 100%;
 
225
  justify-content: space-between;
226
  align-items: center;
227
  z-index: 1000;
228
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
229
  }
230
  .search-bar-container {
231
  position: absolute;
 
247
  background-color: #fff;
248
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
249
  outline: none;
250
+ transition: box-shadow 0.3s ease;
251
+ }
252
+ .search-bar-container input:focus {
253
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
254
  }
255
  .search-bar-container input::placeholder {
256
  color: #888;
 
302
  cursor: pointer;
303
  font-size: 14px;
304
  color: #333;
305
+ transition: background-color 0.2s ease;
306
  }
307
  .autocomplete-suggestions .suggestion-item:hover {
308
  background-color: #f1f1f1;
 
580
  #custom-dish-form {
581
  position: relative;
582
  padding-bottom: 80px;
583
+ background: white;
584
+ padding: 20px;
585
+ border-radius: 10px;
586
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
587
+ margin-bottom: 20px;
588
+ max-height: 70vh;
589
+ overflow-y: auto;
590
+ }
591
+ #custom-dish-form h3 {
592
+ font-size: 1.3rem;
593
+ color: #0FAA39;
594
+ margin-bottom: 20px;
595
+ text-align: center;
596
  }
597
  #custom-dish-form .btn-primary {
598
+ width: 100%;
 
 
 
599
  padding: 10px 20px;
600
+ margin-top: 15px;
601
+ }
602
+ #custom-dish-form::-webkit-scrollbar {
603
+ width: 8px;
604
+ }
605
+ #custom-dish-form::-webkit-scrollbar-track {
606
+ background: #f1f1f1;
607
+ border-radius: 10px;
608
+ }
609
+ #custom-dish-form::-webkit-scrollbar-thumb {
610
+ background: #0FAA39;
611
+ border-radius: 10px;
612
+ }
613
+ #custom-dish-form::-webkit-scrollbar-thumb:hover {
614
+ background: #0D9232;
615
+ }
616
+ #dishNameSuggestions {
617
+ position: absolute;
618
+ width: calc(100% - 30px);
619
+ max-height: 200px;
620
+ overflow-y: auto;
621
+ background: white;
622
+ border: 1px solid #ddd;
623
+ border-radius: 0 0 4px 4px;
624
+ z-index: 1000;
625
+ display: none;
626
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
627
+ }
628
+ .selection-buttons .btn {
629
+ width: 100px;
630
+ }
631
+ .chat-bar {
632
+ margin-top: 20px;
633
  }
634
  .bottom-action-bar {
635
  position: fixed;
 
660
  text-align: center;
661
  min-width: 0;
662
  white-space: nowrap;
663
+ transition: transform 0.2s ease;
664
+ }
665
+ .bottom-action-bar .btn:hover {
666
+ transform: scale(1.05);
667
  }
668
  .bottom-action-bar .btn-order-history {
669
  background-color: #FFA07A;
 
692
  justify-content: center;
693
  font-size: 12px;
694
  margin-left: 8px;
695
+ transition: all 0.3s ease;
696
  }
697
  .mic-popup {
698
  position: fixed;
 
935
  padding: 6px 16px;
936
  font-size: 14px;
937
  }
938
+ #custom-dish-form {
939
+ padding: 15px;
940
+ max-height: 60vh;
941
+ }
942
  }
943
  </style>
944
  </head>
945
  <body>
946
+ <div class="fixed-top-bar" role="banner">
947
  <div class="avatar-dropdown-container">
948
+ <div class="avatar-icon" id="avatarIcon" role="button" aria-label="User Menu">
949
  {% if user_image %}
950
  <img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
951
  {% else %}
952
  <span>{{ first_letter }}</span>
953
  {% endif %}
954
  </div>
955
+ <div class="dropdown-menu" id="avatarDropdown" role="menu">
956
  {% if user_image %}
957
+ <div class="dropdown-item delete-item" id="deleteAvatar" role="menuitem">Delete Image</div>
958
+ <div class="dropdown-item view-item" id="viewAvatar" role="menuitem">View Avatar</div>
959
  {% endif %}
960
+ <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item" role="menuitem">Order History</a>
961
+ <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item" role="menuitem">View Profile</a>
962
+ <div class="dropdown-item upload-item" role="menuitem">
963
  <label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
964
  Upload Image
965
  </label>
966
+ <input type="file" id="avatarUpload" accept="image/*" style="display: none;" aria-label="Upload Avatar">
967
  </div>
968
+ <a href="{{ url_for('logout') }}" class="dropdown-item" role="menuitem">Logout</a>
969
  </div>
970
  </div>
971
  <div class="search-bar-container">
972
+ <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off" aria-label="Search menu items">
973
  <i class="bi bi-search search-icon"></i>
974
+ <i class="bi bi-mic mic-icon" id="micIcon" role="button" aria-label="Voice Search"></i>
975
  <span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
976
  <div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
977
  </div>
978
  </div>
979
 
980
+ <form method="get" action="/menu" class="text-center mb-4" id="categoryForm" role="form">
981
+ <label class="form-label fw-bold" for="selectedCategoryInput">Select a Category:</label>
982
  <div class="category-buttons">
983
  {% for category in categories %}
984
+ <button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}" aria-pressed="{% if selected_category == category %}true{% else %}false{% endif %}">{{ category }}</button>
985
  {% endfor %}
986
+ <button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish" aria-pressed="{% if selected_category == 'Customized Dish' %}true{% else %}false{% endif %}">Customized Dish</button>
987
  </div>
988
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
989
  </form>
990
 
991
+ <div class="container mt-4" role="main">
992
  {% if selected_category == "Customized Dish" %}
993
  <div id="custom-dish-form" class="mt-4">
994
  <h3>Create Your Custom Dish</h3>
995
+
996
+ <!-- Veg/Non-Veg Selection -->
997
+ <div class="selection-buttons mb-3">
998
+ <button id="veg-btn" class="btn btn-outline-success me-2" aria-label="Select Vegetarian">Veg</button>
999
+ <button id="non-veg-btn" class="btn btn-outline-danger" aria-label="Select Non-Vegetarian">Non-Veg</button>
1000
+ </div>
1001
+
1002
+ <!-- Custom Dish Form -->
1003
+ <form id="customDishForm" role="form">
1004
+ <div class="mb-3 position-relative">
1005
  <label for="custom-dish-name" class="form-label">Dish Name</label>
1006
+ <input type="text" class="form-control" id="custom-dish-name" name="name" required autocomplete="off" placeholder="e.g., Spicy Chicken Curry" aria-label="Custom Dish Name">
1007
+ <div id="dishNameSuggestions" class="autocomplete-suggestions"></div>
1008
  </div>
1009
+ <div class="mb-3">
1010
+ <label for="custom-dish-ingredients" class="form-label">Ingredients List</label>
1011
+ <textarea class="form-control" id="custom-dish-ingredients" name="ingredients" required rows="3" placeholder="e.g., Chicken, Tomatoes, Spices" aria-label="Ingredients List"></textarea>
1012
+ </div>
1013
+ <div class="mb-3">
1014
  <label for="custom-dish-description" class="form-label">Dish Description</label>
1015
+ <textarea class="form-control" id="custom-dish-description" name="description" required rows="3" placeholder="e.g., A spicy and flavorful chicken dish" aria-label="Dish Description"></textarea>
1016
+ </div>
1017
+ <div class="mb-3">
1018
+ <label for="custom-dish-price" class="form-label">Price ($)</label>
1019
+ <input type="number" class="form-control" id="custom-dish-price" name="price" step="0.01" min="0.01" required placeholder="e.g., 12.99" aria-label="Price">
1020
  </div>
1021
+ <button type="submit" class="btn btn-primary" id="submitCustomDish" aria-label="Submit to Salesforce">Submit to Salesforce</button>
1022
+ <div id="form-feedback" class="mt-3" style="display: none; text-align: center;"></div>
1023
  </form>
1024
+
1025
+ <!-- Selected Item Details -->
1026
+ <div id="item-details" class="mt-3" style="display: none; background: #f8f9fa; padding: 15px; border-radius: 8px;">
1027
+ <h4>Selected Item Details</h4>
1028
+ <p><strong>Name:</strong> <span id="detail-name"></span></p>
1029
+ <p><strong>Ingredients:</strong> <span id="detail-ingredients"></span></p>
1030
+ <p><strong>Description:</strong> <span id="detail-description"></span></p>
1031
+ <p><strong>Price:</strong> $<span id="detail-price"></span></p>
1032
+ </div>
1033
+
1034
+ <!-- Text Bar and Send Button -->
1035
+ <div class="chat-bar mt-3">
1036
+ <div class="input-group">
1037
+ <input type="text" class="form-control" id="chat-input" placeholder="Type 'hi' or 'veg'/'non-veg'..." aria-label="Chat Input">
1038
+ <button class="btn btn-primary" id="send-btn" aria-label="Send Message">Send</button>
1039
+ </div>
1040
+ <div id="chat-feedback" class="mt-2" style="text-align: center;"></div>
1041
+ </div>
1042
  </div>
1043
  {% else %}
1044
  {% if ordered_menu.items()|length == 0 %}
1045
+ <p class="text-center">No menu items available for this category.</p>
1046
  {% else %}
1047
  {% for section, items in ordered_menu.items() %}
1048
  <h3>{{ section }}</h3>
 
1061
  height="200"
1062
  onmouseover="this.play()"
1063
  onmouseout="this.pause(); this.currentTime = 0;"
1064
+ onerror="this.poster='/static/placeholder.jpg';"
1065
+ aria-label="Video preview of {{ item.Name | default('Unnamed Item') }}">
1066
  <source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
1067
  Your browser does not support the video tag.
1068
  </video>
 
1072
  <h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
1073
  <p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
1074
  {% if item.Section__c != 'Soft Drinks' %}
1075
+ <div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}" role="button" aria-expanded="false" aria-controls="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">Show Details</div>
1076
  {% endif %}
1077
  </div>
1078
  <div class="d-flex flex-column align-item-center justify-content-center">
 
1083
  data-item-section="{{ item.Section__c | default(section) }}"
1084
  data-item-category="{{ selected_category }}">
1085
  {% if item.Section__c == 'Soft Drinks' %}
1086
+ <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)" aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">ADD</button>
1087
  {% else %}
1088
  <button class="btn btn-primary"
1089
  data-bs-toggle="modal"
1090
  data-bs-target="#itemModal"
1091
+ 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 }}')"
1092
+ aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">
1093
  ADD
1094
  </button>
1095
  {% endif %}
 
1121
  {% endif %}
1122
  </div>
1123
 
1124
+ <div class="bottom-action-bar" role="navigation">
1125
+ <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history" aria-label="View Order History">
1126
  <i class="bi bi-clock-history"></i> Order History
1127
  </a>
1128
+ <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart" aria-label="View Cart">
1129
  <i class="bi bi-cart"></i> View Cart
1130
  <span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
1131
  </a>
 
1155
  </div>
1156
  <div class="mt-4">
1157
  <h6>Custom Request</h6>
1158
+ <textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..." aria-label="Special Instructions"></textarea>
1159
  </div>
1160
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
1161
  </div>
1162
  <div class="modal-footer d-flex align-items-center justify-content-between">
1163
  <div class="d-flex align-items-center gap-2">
1164
+ <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity" aria-label="Decrease Quantity">-</button>
1165
+ <input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;" aria-label="Quantity"/>
1166
+ <button type="button" class="btn btn-outline-secondary" id="increaseQuantity" aria-label="Increase Quantity">+</button>
1167
  </div>
1168
+ <button type="button" class="btn btn-primary" onclick="addToCartFromModal()" aria-label="Add to Cart">Add to Cart</button>
1169
  </div>
1170
  </div>
1171
  </div>
 
1183
  <img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
1184
  </div>
1185
  <div class="modal-footer">
1186
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Close Avatar Modal">Close</button>
1187
  </div>
1188
  </div>
1189
  </div>
 
1205
  </div>
1206
  <div class="d-flex justify-content-center align-items-center mb-4">
1207
  <div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
1208
+ <button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;" aria-label="Decrease Drink Quantity">-</button>
1209
+ <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;" aria-label="Drink Quantity">
1210
+ <button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;" aria-label="Increase Drink Quantity">+</button>
1211
  </div>
1212
  </div>
1213
  </div>
1214
  <div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
1215
+ <button type="button" class="btn btn-primary" onclick="addSoftDrinkToCart()" style="width: 200px; padding: 12px; font-size: 1.1rem; background-color: #0FAA39; border-color: #0FAA39;" aria-label="Add Drink to Cart">Add to Cart</button>
1216
  </div>
1217
  </div>
1218
  </div>
1219
  </div>
1220
 
1221
  <!-- Mic Popup -->
1222
+ <div class="mic-popup" id="micPopup" role="alert" aria-live="polite">
1223
  <div class="mic-popup-icon">
1224
  <i class="bi bi-mic-fill"></i>
1225
  </div>
1226
  <div class="mic-popup-text" id="micPopupText">Listening...</div>
1227
+ <button class="mic-popup-cancel" id="micPopupCancel" aria-label="Cancel Voice Search">Cancel</button>
1228
+ </div>
1229
+
1230
+ <!-- Confirmation Modal -->
1231
+ <div class="modal fade" id="confirmationModal" tabindex="-1" aria-labelledby="confirmationModalLabel" aria-hidden="true">
1232
+ <div class="modal-dialog modal-dialog-centered">
1233
+ <div class="modal-content">
1234
+ <div class="modal-header">
1235
+ <h5 class="modal-title" id="confirmationModalLabel">Confirm Action</h5>
1236
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
1237
+ </div>
1238
+ <div class="modal-body">
1239
+ <p id="confirmationMessage"></p>
1240
+ </div>
1241
+ <div class="modal-footer">
1242
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Cancel">Cancel</button>
1243
+ <button type="button" class="btn btn-primary" id="confirmAction" aria-label="Confirm">Confirm</button>
1244
+ </div>
1245
+ </div>
1246
+ </div>
1247
  </div>
1248
 
1249
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
 
1259
  {% endfor %}
1260
  ];
1261
 
1262
+ const sections = [
1263
+ {% for section in ordered_menu.keys() %}
1264
+ "{{ section | e }}",
1265
+ {% endfor %}
 
 
 
 
 
 
 
 
 
 
1266
  ];
1267
 
1268
+ const dishDatabase = {
1269
+ veg: {
1270
+ "Aloo Paratha": { ingredients: "Wheat flour, potatoes, spices (cumin, coriander), ghee", description: "Stuffed potato flatbread cooked with ghee, a North Indian breakfast favorite.", price: 8.99 },
1271
+ "Dosa": { ingredients: "Rice, urad dal, salt (fermented batter)", description: "Crispy South Indian crepe, typically served with sambar and chutney.", price: 8.99 },
1272
+ "Dal Tadka": { ingredients: "Toor dal, garlic, cumin, ghee, tomatoes", description: "Tempered lentil dish with aromatic spices, a staple across India.", price: 7.99 },
1273
+ "Pav Bhaji": { ingredients: "Mixed veggies (potatoes, peas, capsicum), butter, pav bread", description: "Mumbai's famous buttery vegetable mash with soft bread rolls.", price: 10.49 },
1274
+ "Masala Dosa": { ingredients: "Dosa batter, potato filling, mustard seeds, curry leaves", description: "Crispy crepe stuffed with spiced potatoes, served with chutney.", price: 9.99 }
1275
+ },
1276
+ nonVeg: {
1277
+ "Butter Chicken": { ingredients: "Chicken, tomato puree, butter, cream, garam masala", description: "Creamy Punjabi curry with tender chicken pieces in rich tomato sauce.", price: 15.99 },
1278
+ "Biryani": { ingredients: "Basmati rice, meat (chicken/mutton), spices (saffron, bay leaf), yogurt, fried onions", description: "Fragrant layered rice dish with spiced meat, a celebration favorite.", price: 14.99 },
1279
+ "Chicken Chettinad": { ingredients: "Chicken, coconut, black pepper, fennel, curry leaves", description: "Fiery Tamil Nadu chicken curry with aromatic spices and coconut.", price: 16.99 },
1280
+ "Fish Curry": { ingredients: "Fish (pomfret/rohu), coconut milk, tamarind, curry leaves", description: "Goan/Kerala style fish in tangy coconut gravy with spices.", price: 17.99 },
1281
+ "Tandoori Chicken": { ingredients: "Chicken, yogurt, red chili, tandoori masala, lemon", description: "Clay oven-roasted chicken marinated in yogurt and spices.", price: 15.99 }
1282
+ }
1283
+ };
1284
+
1285
  function sanitizeInput(input) {
1286
  const div = document.createElement('div');
1287
  div.textContent = input;
 
1296
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
1297
  );
1298
  if (existingItem) {
1299
+ existingItem.quantity += payload.quantity;
1300
  } else {
1301
  cart.push(payload);
1302
  }
 
1334
  };
1335
  }
1336
 
1337
+ function showConfirmation(message, callback) {
1338
+ document.getElementById('confirmationMessage').innerText = message;
1339
+ const confirmBtn = document.getElementById('confirmAction');
1340
+ confirmBtn.onclick = function() {
1341
+ callback();
1342
+ bootstrap.Modal.getInstance(document.getElementById('confirmationModal')).hide();
1343
+ };
1344
+ const modal = new bootstrap.Modal(document.getElementById('confirmationModal'));
1345
+ modal.show();
1346
+ }
1347
+
1348
  function showSoftDrinkModal(button) {
1349
  currentSoftDrinkButton = button;
1350
  const buttonContainer = button.closest('.button-container');
 
1367
 
1368
  const buttonContainer = currentSoftDrinkButton.closest('.button-container');
1369
  const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
1370
+ if (quantity < 1 || quantity > 99) {
1371
+ alert('Quantity must be between 1 and 99.');
1372
+ return;
1373
+ }
1374
 
1375
  const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
1376
  const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
 
1386
  category: selectedCategory,
1387
  addons: [],
1388
  instructions: '',
1389
+ quantity: quantity,
1390
+ timestamp: new Date().toISOString()
1391
  };
1392
 
1393
  fetch('/cart/add', {
 
1402
  if (data.success) {
1403
  alert('Item added to cart successfully!');
1404
  updateCartUI(data.cart);
1405
+ bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
 
1406
  } else {
1407
+ throw new Error(data.error || 'Failed to add item to cart');
 
 
 
 
 
1408
  }
1409
  })
1410
  .catch(err => {
 
1412
  alert('Error adding item to cart. Using local storage as fallback.');
1413
  const cart = addToCartLocalStorage(cartPayload);
1414
  updateCartUI(cart);
1415
+ bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
 
1416
  });
1417
  }
1418
 
 
1442
  const deleteAvatar = document.getElementById('deleteAvatar');
1443
  const viewAvatar = document.getElementById('viewAvatar');
1444
 
 
1445
  function loadAvatar() {
1446
  const savedAvatar = localStorage.getItem('userAvatar');
1447
  const avatarImage = avatarIcon.querySelector('.avatar-image');
 
1460
  }
1461
  }
1462
 
 
1463
  function ensureAvatarOptions() {
1464
  if (!document.querySelector('#deleteAvatar')) {
1465
  const deleteItem = document.createElement('div');
 
1480
  }
1481
  }
1482
 
 
1483
  function removeAvatarOptions() {
1484
  const deleteItem = document.getElementById('deleteAvatar');
1485
  const viewItem = document.getElementById('viewAvatar');
 
1487
  if (viewItem) viewItem.remove();
1488
  }
1489
 
 
1490
  function saveAvatar(imageUrl) {
1491
  localStorage.setItem('userAvatar', imageUrl);
1492
  }
1493
 
 
1494
  function clearAvatar() {
1495
  localStorage.removeItem('userAvatar');
1496
  avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1497
  removeAvatarOptions();
1498
  }
1499
 
 
1500
  avatarIcon.addEventListener('click', function(event) {
1501
  event.stopPropagation();
1502
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1503
  });
1504
 
 
1505
  avatarUpload.addEventListener('change', function(event) {
1506
  const file = event.target.files[0];
1507
  if (file) {
 
1538
  ensureAvatarOptions();
1539
  dropdownMenu.style.display = 'none';
1540
  } else {
1541
+ throw new Error(data.error || 'Unknown error');
 
 
 
 
 
 
 
 
 
 
1542
  }
1543
  })
1544
  .catch(error => {
 
1557
  });
1558
  };
1559
  reader.onerror = function() {
1560
+ alert('Error reading the image file.');
 
 
 
 
 
 
 
 
 
 
1561
  };
1562
  reader.readAsDataURL(file);
1563
  }
1564
  });
1565
 
 
1566
  function addDeleteListener(deleteElement) {
1567
  deleteElement.addEventListener('click', function() {
1568
+ showConfirmation('Are you sure you want to delete your avatar?', function() {
1569
+ fetch('/delete_avatar', {
1570
+ method: 'POST',
1571
+ headers: {
1572
+ 'Content-Type': 'application/json'
1573
+ }
1574
+ })
1575
+ .then(response => {
1576
+ if (!response.ok) throw new Error('Delete failed');
1577
+ return response.json();
1578
+ })
1579
+ .then(data => {
1580
+ if (data.success) {
1581
+ clearAvatar();
1582
+ dropdownMenu.style.display = 'none';
1583
+ } else {
1584
+ throw new Error(data.error || 'Unknown error');
1585
+ }
1586
+ })
1587
+ .catch(error => {
1588
+ console.error('Delete error:', error);
1589
+ alert('Error deleting image. Cleared locally.');
1590
  clearAvatar();
1591
  dropdownMenu.style.display = 'none';
1592
+ });
 
 
 
 
 
 
1593
  });
1594
  });
1595
  }
1596
 
 
1597
  function addViewListener(viewElement) {
1598
  viewElement.addEventListener('click', function() {
1599
  const avatarImage = avatarIcon.querySelector('.avatar-image');
 
1609
  });
1610
  }
1611
 
 
1612
  if (deleteAvatar) addDeleteListener(deleteAvatar);
1613
  if (viewAvatar) addViewListener(viewAvatar);
1614
 
 
1615
  loadAvatar();
1616
 
 
1617
  document.addEventListener('click', function(event) {
1618
  if (!avatarContainer.contains(event.target)) {
1619
  dropdownMenu.style.display = 'none';
 
1669
  const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1670
  if (otherLink) {
1671
  otherLink.innerText = 'Show Details';
1672
+ otherLink.setAttribute('aria-expanded', 'false');
1673
  }
1674
  }
1675
  });
 
1677
  if (!isCurrentlyShown) {
1678
  detailsDiv.classList.add('show');
1679
  this.innerText = 'Hide Details';
1680
+ this.setAttribute('aria-expanded', 'true');
1681
  } else {
1682
  detailsDiv.classList.remove('show');
1683
  this.innerText = 'Show Details';
1684
+ this.setAttribute('aria-expanded', 'false');
1685
  }
1686
  });
1687
  });
 
1691
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
1692
  if (!selectedCategoryInput.value) {
1693
  selectedCategoryInput.value = "All";
1694
+ const allBtn = document.querySelector('.category-button[data-category="All"]');
1695
+ if (allBtn) allBtn.classList.add('selected');
1696
  }
1697
  categoryButtons.forEach(button => {
1698
  button.addEventListener('click', function () {
 
1705
 
1706
  const searchBar = document.getElementById('searchBar');
1707
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
 
1708
 
1709
  // Redirect to search page on click
1710
  searchBar.addEventListener('click', function (event) {
 
1712
  window.location.href = '/search';
1713
  });
1714
 
1715
+ // Customized Dish Logic
1716
+ const customDishForm = document.getElementById('customDishForm');
1717
+ const dishNameInput = document.getElementById('custom-dish-name');
1718
+ const dishIngredients = document.getElementById('custom-dish-ingredients');
1719
+ const dishDescription = document.getElementById('custom-dish-description');
1720
+ const dishPrice = document.getElementById('custom-dish-price');
1721
+ const dishSuggestions = document.getElementById('dishNameSuggestions');
1722
+ const formFeedback = document.getElementById('form-feedback');
1723
+ const chatInput = document.getElementById('chat-input');
1724
+ const sendBtn = document.getElementById('send-btn');
1725
+ const chatFeedback = document.getElementById('chat-feedback');
1726
+ const vegBtn = document.getElementById('veg-btn');
1727
+ const nonVegBtn = document.getElementById('non-veg-btn');
1728
+ const itemDetails = document.getElementById('item-details');
 
 
 
 
 
 
 
 
 
 
 
1729
 
1730
+ if (customDishForm) {
1731
+ let filterType = 'all'; // Default to show all items
 
 
 
1732
 
1733
+ // Show item details
1734
+ function showItemDetails(dish) {
1735
+ if (dish) {
1736
+ itemDetails.style.display = 'block';
1737
+ document.getElementById('detail-name').textContent = dish;
1738
+ document.getElementById('detail-ingredients').textContent = (dishDatabase.veg[dish] || dishDatabase.nonVeg[dish]).ingredients;
1739
+ document.getElementById('detail-description').textContent = (dishDatabase.veg[dish] || dishDatabase.nonVeg[dish]).description;
1740
+ document.getElementById('detail-price').textContent = (dishDatabase.veg[dish] || dishDatabase.nonVeg[dish]).price.toFixed(2);
1741
+ } else {
1742
+ itemDetails.style.display = 'none';
 
 
 
 
1743
  }
1744
  }
1745
+
1746
+ // Autocomplete function
1747
+ function updateSuggestions(input) {
1748
+ const sanitizedInput = sanitizeInput(input.trim().toLowerCase());
1749
+ dishSuggestions.innerHTML = '';
1750
+ dishSuggestions.style.display = 'none';
1751
+
1752
+ if (sanitizedInput.length > 0) {
1753
+ let matches = [];
1754
+ if (filterType === 'veg') {
1755
+ matches = Object.keys(dishDatabase.veg).filter(dish => dish.toLowerCase().includes(sanitizedInput));
1756
+ } else if (filterType === 'non-veg') {
1757
+ matches = Object.keys(dishDatabase.nonVeg).filter(dish => dish.toLowerCase().includes(sanitizedInput));
1758
+ } else {
1759
+ matches = [...Object.keys(dishDatabase.veg), ...Object.keys(dishDatabase.nonVeg)].filter(dish => dish.toLowerCase().includes(sanitizedInput));
1760
+ }
1761
+
1762
+ if (matches.length > 0) {
1763
+ matches.slice(0, 5).forEach(dish => {
1764
+ const suggestion = document.createElement('div');
1765
+ suggestion.classList.add('suggestion-item');
1766
+ suggestion.textContent = dish;
1767
+ suggestion.addEventListener('click', function () {
1768
+ dishNameInput.value = dish;
1769
+ const dishData = dishDatabase.veg[dish] || dishDatabase.nonVeg[dish];
1770
+ if (dishData) {
1771
+ dishIngredients.value = dishData.ingredients;
1772
+ dishDescription.value = dishData.description;
1773
+ dishPrice.value = dishData.price.toFixed(2);
1774
+ showItemDetails(dish);
1775
+ }
1776
+ dishSuggestions.style.display = 'none';
1777
  });
1778
+ dishSuggestions.appendChild(suggestion);
1779
  });
1780
+ dishSuggestions.style.display = 'block';
1781
  }
1782
  }
1783
+ }
1784
+
1785
+ // Autocomplete for dish name
1786
+ dishNameInput.addEventListener('input', function () {
1787
+ updateSuggestions(this.value);
1788
  });
1789
+
1790
+ // Close suggestions when clicking outside
1791
+ document.addEventListener('click', function (e) {
1792
+ if (e.target !== dishNameInput && !dishSuggestions.contains(e.target)) {
1793
+ dishSuggestions.style.display = 'none';
1794
  }
1795
  });
 
1796
 
1797
+ // Veg/Non-Veg button handlers
1798
+ vegBtn.addEventListener('click', function () {
1799
+ filterType = 'veg';
1800
+ vegBtn.classList.remove('btn-outline-success');
1801
+ vegBtn.classList.add('btn-success');
1802
+ nonVegBtn.classList.remove('btn-danger');
1803
+ nonVegBtn.classList.add('btn-outline-danger');
1804
+ updateSuggestions(dishNameInput.value);
1805
+ });
1806
+
1807
+ nonVegBtn.addEventListener('click', function () {
1808
+ filterType = 'non-veg';
1809
+ nonVegBtn.classList.remove('btn-outline-danger');
1810
+ nonVegBtn.classList.add('btn-danger');
1811
+ vegBtn.classList.remove('btn-success');
1812
+ vegBtn.classList.add('btn-outline-success');
1813
+ updateSuggestions(dishNameInput.value);
1814
+ });
1815
+
1816
+ // Chat bar handler
1817
+ sendBtn.addEventListener('click', function () {
1818
+ const input = sanitizeInput(chatInput.value.trim().toLowerCase());
1819
+ chatFeedback.innerHTML = '';
1820
+
1821
+ if (input === 'hi') {
1822
+ fetch('/salesforce/get_customer_name', {
1823
+ method: 'GET',
1824
+ headers: { 'Content-Type': 'application/json' }
1825
+ })
1826
+ .then(response => response.json())
1827
+ .then(data => {
1828
+ const customerName = data.name || 'Customer';
1829
+ chatFeedback.innerHTML = `Welcome ${customerName}!`;
1830
+ chatFeedback.className = 'text-success';
1831
+ })
1832
+ .catch(error => {
1833
+ console.error('Error fetching customer name:', error);
1834
+ chatFeedback.innerHTML = 'Welcome Guest!';
1835
+ chatFeedback.className = 'text-success';
1836
+ });
1837
+ } else if (input === 'veg') {
1838
+ filterType = 'veg';
1839
+ vegBtn.click();
1840
+ chatFeedback.innerHTML = 'Showing vegetarian items.';
1841
+ chatFeedback.className = 'text-success';
1842
+ } else if (input === 'non-veg' || input === 'non veg') {
1843
+ filterType = 'non-veg';
1844
+ nonVegBtn.click();
1845
+ chatFeedback.innerHTML = 'Showing non-vegetarian items.';
1846
+ chatFeedback.className = 'text-success';
1847
+ } else {
1848
+ chatFeedback.innerHTML = 'Please type "hi", "veg", or "non-veg".';
1849
+ chatFeedback.className = 'text-danger';
1850
  }
1851
+ chatInput.value = '';
1852
+ });
1853
+
1854
+ // Form submission to Salesforce
1855
+ customDishForm.addEventListener('submit', function (event) {
1856
  event.preventDefault();
1857
+
1858
+ formFeedback.style.display = 'none';
1859
+ formFeedback.innerHTML = '';
1860
+
1861
+ const dishName = sanitizeInput(dishNameInput.value.trim());
1862
+ const ingredients = sanitizeInput(dishIngredients.value.trim());
1863
+ const description = sanitizeInput(dishDescription.value.trim());
1864
+ const price = parseFloat(dishPrice.value);
1865
+
1866
+ if (!dishName) {
1867
+ showFeedback('Please enter a dish name.', 'text-danger');
1868
+ return;
1869
+ }
1870
+ if (!ingredients) {
1871
+ showFeedback('Please enter ingredients.', 'text-danger');
1872
+ return;
1873
+ }
1874
+ if (!description) {
1875
+ showFeedback('Please enter a description.', 'text-danger');
1876
+ return;
1877
+ }
1878
+ if (isNaN(price) || price <= 0) {
1879
+ showFeedback('Please enter a valid price greater than 0.', 'text-danger');
1880
+ return;
1881
+ }
1882
+
1883
+ const submitBtn = document.getElementById('submitCustomDish');
1884
+ submitBtn.disabled = true;
1885
+ submitBtn.innerText = 'Submitting...';
1886
+
1887
+ const formData = {
1888
+ name: dishName,
1889
+ ingredients: ingredients,
1890
+ description: description,
1891
+ price: price,
1892
+ type: filterType === 'veg' ? 'Vegetarian' : 'Non-Vegetarian'
1893
+ };
1894
+
1895
+ fetch('/salesforce/submit_custom_dish', {
1896
  method: 'POST',
1897
  headers: {
1898
+ 'Content-Type': 'application/json',
1899
  },
1900
+ body: JSON.stringify(formData)
1901
+ })
1902
+ .then(response => {
1903
+ if (!response.ok) {
1904
+ throw new Error(`Server error: ${response.status}`);
1905
+ }
1906
+ return response.json();
1907
  })
 
1908
  .then(data => {
1909
  if (data.success) {
1910
+ showFeedback('Custom dish submitted to Salesforce successfully!', 'text-success');
1911
+ customDishForm.reset();
1912
+ itemDetails.style.display = 'none';
1913
+ setTimeout(() => {
1914
+ window.location.reload();
1915
+ }, 1500);
1916
  } else {
1917
+ throw new Error(data.error || 'Submission failed');
1918
  }
1919
  })
1920
  .catch(error => {
1921
  console.error('Error submitting custom dish:', error);
1922
+ showFeedback(`Error: ${error.message}. Please try again.`, 'text-danger');
1923
+ })
1924
+ .finally(() => {
1925
+ submitBtn.disabled = false;
1926
+ submitBtn.innerText = 'Submit to Salesforce';
1927
  });
1928
  });
 
1929
 
1930
+ // Show feedback to user
1931
+ function showFeedback(message, className) {
1932
+ formFeedback.innerHTML = message;
1933
+ formFeedback.className = `mt-3 ${className}`;
1934
+ formFeedback.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1935
  }
1936
+ }
1937
 
1938
+ // Voice Search Functionality
1939
  const micIcon = document.getElementById('micIcon');
 
1940
  const micPopup = document.getElementById('micPopup');
1941
  const micPopupText = document.getElementById('micPopupText');
1942
  const micPopupCancel = document.getElementById('micPopupCancel');
1943
+ const micUnsupported = document.getElementById('micUnsupported');
1944
+
1945
+ if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
1946
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1947
  const recognition = new SpeechRecognition();
1948
  recognition.continuous = false;
1949
+ recognition.interimResults = false;
1950
  recognition.lang = 'en-US';
1951
+
1952
+ micIcon.addEventListener('click', function () {
 
1953
  micPopup.classList.add('active');
1954
+ recognition.start();
1955
+ });
1956
+
1957
+ recognition.onstart = function () {
1958
  micPopupText.textContent = 'Listening...';
1959
+ micIcon.classList.add('active');
1960
  };
1961
+
1962
+ recognition.onresult = function (event) {
1963
+ const transcript = event.results[0][0].transcript.trim().toLowerCase();
1964
+ micPopupText.textContent = `You said: "${transcript}"`;
1965
+ setTimeout(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1966
  micPopup.classList.remove('active');
1967
+ searchBar.value = transcript;
1968
+ performSearch(transcript);
1969
+ micIcon.classList.remove('active');
1970
+ }, 1000);
 
 
 
 
 
 
1971
  };
1972
+
1973
+ recognition.onerror = function (event) {
1974
+ micPopupText.textContent = 'Error occurred in recognition: ' + event.error;
 
1975
  setTimeout(() => {
1976
  micPopup.classList.remove('active');
1977
+ micIcon.classList.remove('active');
1978
  }, 2000);
 
1979
  };
1980
+
1981
+ recognition.onend = function () {
1982
+ if (micPopup.classList.contains('active')) {
1983
+ micPopupText.textContent = 'Recognition ended.';
 
 
1984
  setTimeout(() => {
1985
  micPopup.classList.remove('active');
1986
+ micIcon.classList.remove('active');
1987
+ }, 1000);
1988
  }
1989
+ };
1990
+
1991
+ micPopupCancel.addEventListener('click', function () {
1992
  recognition.stop();
1993
  micPopup.classList.remove('active');
1994
  micIcon.classList.remove('active');
 
1998
  micUnsupported.style.display = 'block';
1999
  }
2000
 
2001
+ function performSearch(query) {
2002
+ const lowerQuery = query.toLowerCase();
2003
+ const matchingSection = sections.find(section => section.toLowerCase().includes(lowerQuery));
2004
+ if (matchingSection) {
2005
+ selectedCategoryInput.value = matchingSection;
2006
+ categoryForm.submit();
2007
+ } else {
2008
+ const matchingItem = menuItems.find(item => item.toLowerCase().includes(lowerQuery));
2009
+ if (matchingItem) {
2010
+ const itemCard = document.querySelector(`[data-item-name="${matchingItem}"]`);
2011
+ if (itemCard) {
2012
+ itemCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
2013
+ itemCard.classList.add('highlight');
2014
+ setTimeout(() => itemCard.classList.remove('highlight'), 2000);
2015
+ }
2016
+ } else {
2017
+ alert('No matching items or sections found.');
2018
+ }
2019
+ }
2020
+ }
2021
 
2022
+ function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, category) {
2023
+ document.getElementById('modal-name').textContent = name;
2024
+ document.getElementById('modal-price').textContent = `$${price}`;
2025
+ document.getElementById('modal-img').src = image;
2026
+ document.getElementById('modal-description').textContent = description;
2027
+ document.getElementById('modal-ingredients').textContent = ingredients;
2028
+ document.getElementById('modal-nutrition').textContent = nutrition;
2029
+ document.getElementById('modal-allergens').textContent = allergens;
2030
+ document.getElementById('modal-section').dataset.section = section;
2031
+ document.getElementById('modal-section').dataset.category = category;
2032
+ document.getElementById('quantityInput').value = '1';
2033
+ document.getElementById('modal-instructions').value = '';
2034
 
2035
+ fetchAddons(name, section);
2036
+ }
2037
+
2038
+ function fetchAddons(itemName, section) {
2039
+ const addonsList = document.getElementById('addons-list');
2040
+ addonsList.innerHTML = 'Loading customization options...';
2041
+
2042
+ fetch(`/menu/addons?item_name=${encodeURIComponent(itemName)}&section=${encodeURIComponent(section)}`)
2043
+ .then(response => response.json())
2044
+ .then(data => {
2045
+ addonsList.innerHTML = '';
2046
+ if (data.addons && data.addons.length > 0) {
2047
+ data.addons.forEach(addon => {
2048
+ const addonDiv = document.createElement('div');
2049
+ addonDiv.className = 'addon-section mb-3';
2050
+ addonDiv.innerHTML = `
2051
+ <h6>${addon.name}</h6>
2052
+ ${addon.options.map(option => `
2053
+ <div class="form-check">
2054
+ <input class="form-check-input" type="${addon.type === 'single' ? 'radio' : 'checkbox'}"
2055
+ name="${addon.name}" id="${option.id}" value="${option.name}"
2056
+ data-price="${option.price}">
2057
+ <label class="form-check-label" for="${option.id}">
2058
+ ${option.name} ${option.price > 0 ? '(+$' + option.price.toFixed(2) + ')' : ''}
2059
+ </label>
2060
+ </div>
2061
+ `).join('')}
2062
+ `;
2063
+ addonsList.appendChild(addonDiv);
2064
+ });
2065
  } else {
2066
+ addonsList.innerHTML = '<p>No customization options available.</p>';
 
2067
  }
2068
+ })
2069
+ .catch(error => {
2070
+ console.error('Error fetching addons:', error);
2071
+ addonsList.innerHTML = '<p>Error loading customization options.</p>';
2072
+ });
2073
  }
 
2074
 
2075
+ function addToCartFromModal() {
2076
+ if (isProcessingRequest) return;
2077
+ isProcessingRequest = true;
2078
+
2079
+ const itemName = sanitizeInput(document.getElementById('modal-name').textContent);
2080
+ const itemPrice = parseFloat(document.getElementById('modal-price').textContent.replace('$', ''));
2081
+ const itemImage = document.getElementById('modal-img').src;
2082
+ const section = sanitizeInput(document.getElementById('modal-section').dataset.section);
2083
+ const category = sanitizeInput(document.getElementById('modal-section').dataset.category);
2084
+ const quantity = parseInt(document.getElementById('quantityInput').value);
2085
+ const instructions = sanitizeInput(document.getElementById('modal-instructions').value);
2086
+
2087
+ if (quantity < 1 || quantity > 99) {
2088
+ alert('Quantity must be between 1 and 99.');
2089
+ isProcessingRequest = false;
2090
+ return;
 
 
 
 
 
 
 
 
2091
  }
2092
+
2093
+ const addons = [];
2094
+ document.querySelectorAll('#addons-list .form-check-input:checked').forEach(input => {
2095
+ const addonName = input.getAttribute('name');
2096
+ const optionName = input.value;
2097
+ const optionPrice = parseFloat(input.getAttribute('data-price')) || 0;
2098
+ addons.push({ addonName, optionName, optionPrice });
 
 
2099
  });
 
 
2100
 
2101
+ const cartPayload = {
2102
+ itemName: itemName,
2103
+ itemPrice: itemPrice,
2104
+ itemImage: itemImage,
2105
+ section: section,
2106
+ category: category,
2107
+ addons: addons,
2108
+ instructions: instructions,
2109
+ quantity: quantity,
2110
+ timestamp: new Date().toISOString()
2111
+ };
2112
+
2113
+ fetch('/cart/add', {
2114
+ method: 'POST',
2115
+ headers: {
2116
+ 'Content-Type': 'application/json',
2117
+ },
2118
+ body: JSON.stringify(cartPayload)
2119
+ })
2120
  .then(response => response.json())
2121
  .then(data => {
2122
+ if (data.success) {
2123
+ alert('Item added to cart successfully!');
2124
+ updateCartUI(data.cart);
2125
+ bootstrap.Modal.getInstance(document.getElementById('itemModal')).hide();
2126
+ } else {
2127
+ throw new Error(data.error || 'Failed to add item to cart');
2128
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2129
  })
2130
  .catch(err => {
2131
+ console.error('Error adding item to cart:', err);
2132
+ alert('Error adding item to cart. Using local storage as fallback.');
2133
+ const cart = addToCartLocalStorage(cartPayload);
2134
+ updateCartUI(cart);
2135
+ bootstrap.Modal.getInstance(document.getElementById('itemModal')).hide();
2136
+ })
2137
+ .finally(() => {
2138
+ isProcessingRequest = false;
2139
  });
 
 
 
 
 
2140
  }
 
2141
 
2142
+ document.getElementById('increaseQuantity').addEventListener('click', function () {
2143
+ let quantity = parseInt(document.getElementById('quantityInput').value);
2144
+ if (quantity < 99) {
2145
+ document.getElementById('quantityInput').value = quantity + 1;
2146
+ }
2147
+ });
 
 
 
 
2148
 
2149
+ document.getElementById('decreaseQuantity').addEventListener('click', function () {
2150
+ let quantity = parseInt(document.getElementById('quantityInput').value);
2151
+ if (quantity > 1) {
2152
+ document.getElementById('quantityInput').value = quantity - 1;
2153
+ }
2154
+ });
 
 
 
 
 
 
2155
 
2156
+ document.getElementById('soft-drink-increase').addEventListener('click', function () {
2157
+ let quantity = parseInt(document.getElementById('soft-drink-quantity').value);
2158
+ if (quantity < 99) {
2159
+ document.getElementById('soft-drink-quantity').value = quantity + 1;
2160
+ }
2161
+ });
 
 
 
 
2162
 
2163
+ document.getElementById('soft-drink-decrease').addEventListener('click', function () {
2164
+ let quantity = parseInt(document.getElementById('soft-drink-quantity').value);
2165
+ if (quantity > 1) {
2166
+ document.getElementById('soft-drink-quantity').value = quantity - 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2167
  }
 
 
 
 
 
 
 
 
 
2168
  });
2169
+
2170
+ const initialCart = getCartLocalStorage();
2171
+ updateCartUI(initialCart);
2172
+ });
2173
  </script>
2174
  </body>
2175
+ </html>