lokesh341 commited on
Commit
50de36b
·
verified ·
1 Parent(s): d454c12

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. templates/menu.html +794 -310
templates/menu.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
@@ -26,7 +26,6 @@
26
  }
27
  .container {
28
  max-width: 900px;
29
- padding: 0 15px;
30
  }
31
  .menu-card {
32
  max-width: 350px;
@@ -37,12 +36,11 @@
37
  display: flex;
38
  flex-direction: column;
39
  opacity: 0;
40
- transition: opacity 0.3s ease-in-out, transform 0.3s ease;
41
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
42
  }
43
  .menu-card.visible {
44
  opacity: 1;
45
- transform: translateY(0);
46
  }
47
  .menu-video {
48
  height: 200px;
@@ -50,13 +48,14 @@
50
  object-fit: cover;
51
  border-radius: 15px 15px 0 0;
52
  opacity: 0;
53
- transition: opacity 0.5s ease-in-out, transform 0.2s ease;
54
  background-color: #000;
55
  }
56
  .menu-video.loaded {
57
  opacity: 1;
58
  }
59
  .menu-card:hover .menu-video {
 
60
  transform: scale(1.05);
61
  }
62
  .menu-card .card-body .card-title {
@@ -79,12 +78,11 @@
79
  font-weight: bold;
80
  border-radius: 5px;
81
  border: none;
82
- transition: background-color 0.3s ease, transform 0.2s ease;
83
  margin-left: 13px;
84
  }
85
  .addbutton .btn:hover {
86
  background-color: #218838;
87
- transform: scale(1.05);
88
  }
89
  .button-container {
90
  display: flex;
@@ -92,14 +90,6 @@
92
  align-items: center;
93
  justify-content: center;
94
  gap: 6px;
95
- border: 1px solid #ffd8b1;
96
- padding: 5px 0;
97
- z-index: 1000;
98
- animation: slideDown 0.2s ease-out;
99
- }
100
- @keyframes slideDown {
101
- from { opacity: 0; transform: translateY(-10px); }
102
- to { opacity: 1; transform: translateY(0); }
103
  }
104
  .customisable-text {
105
  color: #0FAA39;
@@ -156,6 +146,7 @@
156
  color: white;
157
  font-size: 20px;
158
  font-weight: bold;
 
159
  transition: transform 0.2s ease;
160
  }
161
  .avatar-icon:hover {
@@ -166,6 +157,10 @@
166
  height: 100%;
167
  object-fit: cover;
168
  border-radius: 50%;
 
 
 
 
169
  }
170
  .dropdown-menu {
171
  position: absolute;
@@ -185,14 +180,50 @@
185
  text-decoration: none;
186
  color: #333;
187
  font-size: 14px;
188
- transition: background-color 0.2s ease;
189
  cursor: pointer;
190
  }
191
  .dropdown-menu .dropdown-item:hover {
192
  background-color: #ffe4c4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  }
194
  .fixed-top-bar {
195
- position: fixed;
196
  top: 0;
197
  left: 0;
198
  width: 100%;
@@ -204,7 +235,6 @@
204
  justify-content: space-between;
205
  align-items: center;
206
  z-index: 1000;
207
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
208
  }
209
  .search-bar-container {
210
  position: absolute;
@@ -215,22 +245,18 @@
215
  align-items: center;
216
  width: 300px;
217
  max-width: 90%;
218
- background-color: #fff;
219
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
220
- border-radius: 25px;
221
  }
222
  .search-bar-container input {
223
  width: 100%;
224
- padding: 8px 40px;
225
  font-size: 16px;
226
  border-radius: 25px;
227
  border: none;
228
- background-color: transparent;
 
229
  outline: none;
230
  }
231
- .search-bar-container input:focus {
232
- box-shadow: 0 4px 8px rgba(0,0,0,0.15);
233
- }
234
  .search-bar-container input::placeholder {
235
  color: #888;
236
  }
@@ -246,7 +272,7 @@
246
  font-size: 18px;
247
  color: #888;
248
  cursor: pointer;
249
- transition: color 0.3s ease;
250
  }
251
  .mic-icon.active {
252
  color: #007bff;
@@ -257,6 +283,10 @@
257
  right: 15px;
258
  font-size: 14px;
259
  color: #888;
 
 
 
 
260
  }
261
  .autocomplete-suggestions {
262
  position: absolute;
@@ -301,6 +331,9 @@
301
  color: #343a40;
302
  }
303
  .addon-section .form-check-input {
 
 
 
304
  width: 20px;
305
  height: 20px;
306
  border: 2px solid #343a40;
@@ -323,8 +356,11 @@
323
  }
324
  .addon-section .form-check-label {
325
  font-size: 16px;
 
326
  margin-right: 15px;
327
  cursor: pointer;
 
 
328
  }
329
  form.text-center.mb-4 {
330
  display: flex;
@@ -351,6 +387,7 @@
351
  max-height: 400px;
352
  object-fit: contain;
353
  border-radius: 8px;
 
354
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
355
  }
356
  .modal-body #modal-img {
@@ -363,6 +400,7 @@
363
  .modal-body #modal-name {
364
  font-size: 20px;
365
  font-weight: bold;
 
366
  margin-bottom: 5px;
367
  color: #333333;
368
  }
@@ -370,6 +408,7 @@
370
  font-size: 16px;
371
  font-weight: 500;
372
  color: #000000;
 
373
  margin-bottom: 10px;
374
  }
375
  .modal-body #modal-description {
@@ -382,7 +421,8 @@
382
  color: #6c757d;
383
  margin-bottom: 10px;
384
  }
385
- .modal-body #modal-addons h6 {
 
386
  font-size: 14px;
387
  font-weight: bold;
388
  margin-bottom: 10px;
@@ -393,6 +433,11 @@
393
  justify-content: space-between;
394
  padding: 10px;
395
  }
 
 
 
 
 
396
  .modal-footer .btn {
397
  height: 40px;
398
  padding: 0 15px;
@@ -403,10 +448,18 @@
403
  text-align: center;
404
  }
405
  .modal-footer .btn-primary {
 
 
 
406
  padding: 10px 20px;
 
 
 
 
407
  width: auto;
408
  }
409
  .modal-footer .btn-outline-secondary {
 
410
  width: 40px;
411
  }
412
  .item-details {
@@ -414,6 +467,7 @@
414
  padding: 15px;
415
  background-color: #f8f9fa;
416
  border-radius: 8px;
 
417
  margin: 10px 15px;
418
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
419
  }
@@ -436,8 +490,12 @@
436
  cursor: pointer;
437
  color: #0FAA39;
438
  font-size: 0.9rem;
 
439
  padding: 5px 0;
440
  transition: color 0.3s ease;
 
 
 
441
  }
442
  .toggle-details:hover {
443
  color: #0D9232;
@@ -463,10 +521,41 @@
463
  .category-button.selected {
464
  background-color: #0FAA39;
465
  color: #fff;
 
466
  }
467
  .category-button:hover {
468
  background-color: #e6f4ea;
469
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  .modal-body::-webkit-scrollbar {
471
  width: 8px;
472
  }
@@ -481,52 +570,27 @@
481
  .modal-body::-webkit-scrollbar-thumb:hover {
482
  background: #0D9232;
483
  }
 
 
 
 
 
 
 
 
 
 
 
484
  #custom-dish-form {
485
  position: relative;
486
- padding: 20px;
487
- background: white;
488
- border-radius: 10px;
489
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
490
- margin-bottom: 20px;
491
- max-height: 70vh;
492
- overflow-y: auto;
493
- }
494
- #custom-dish-form h3 {
495
- font-size: 1.3rem;
496
- color: #0FAA39;
497
- margin-bottom: 20px;
498
- text-align: center;
499
  }
500
  #custom-dish-form .btn-primary {
501
- width: 100%;
502
- padding: 10px 20px;
503
- margin-top: 15px;
504
- }
505
- #custom-dish-form::-webkit-scrollbar {
506
- width: 8px;
507
- }
508
- #custom-dish-form::-webkit-scrollbar-track {
509
- background: #f1f1f1;
510
- border-radius: 10px;
511
- }
512
- #custom-dish-form::-webkit-scrollbar-thumb {
513
- background: #0FAA39;
514
- border-radius: 10px;
515
- }
516
- #custom-dish-form::-webkit-scrollbar-thumb:hover {
517
- background: #0D9232;
518
- }
519
- #descriptionSuggestions {
520
  position: absolute;
521
- width: calc(100% - 30px);
522
- max-height: 200px;
523
- overflow-y: auto;
524
- background: white;
525
- border: 1px solid #ddd;
526
- border-radius: 0 0 4px 4px;
527
- z-index: 1000;
528
- display: none;
529
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
530
  }
531
  .bottom-action-bar {
532
  position: fixed;
@@ -554,6 +618,9 @@
554
  display: flex;
555
  align-items: center;
556
  justify-content: center;
 
 
 
557
  }
558
  .bottom-action-bar .btn-order-history {
559
  background-color: #FFA07A;
@@ -597,6 +664,7 @@
597
  display: none;
598
  width: 300px;
599
  max-width: 90%;
 
600
  }
601
  .mic-popup.active {
602
  display: block;
@@ -610,6 +678,7 @@
610
  .mic-popup-text {
611
  font-size: 18px;
612
  margin-bottom: 15px;
 
613
  }
614
  .mic-popup-cancel {
615
  background-color: #ff4444;
@@ -626,97 +695,280 @@
626
  100% { transform: scale(1); }
627
  }
628
  @media (max-width: 576px) {
 
 
 
 
629
  .search-bar-container {
630
  width: 80%;
 
631
  left: 10px;
 
 
632
  }
633
- .menu-card {
634
- max-width: 100%;
 
 
635
  }
636
- .bottom-action-bar .btn {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  padding: 8px 10px;
 
639
  }
640
  .cart-icon-badge {
641
  width: 18px;
642
  height: 18px;
643
  font-size: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  }
645
  }
646
  </style>
647
  </head>
648
  <body>
649
- <div class="fixed-top-bar" role="banner">
650
  <div class="avatar-dropdown-container">
651
- <div class="avatar-icon" id="avatarIcon" role="button" aria-label="User Menu">
652
  {% if user_image %}
653
  <img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
654
  {% else %}
655
  <span>{{ first_letter }}</span>
656
  {% endif %}
657
  </div>
658
- <div class="dropdown-menu" id="avatarDropdown" role="menu">
659
  {% if user_image %}
660
- <div class="dropdown-item delete-item" id="deleteAvatar" role="menuitem">Delete Image</div>
661
- <div class="dropdown-item view-item" id="viewAvatar" role="menuitem">View Avatar</div>
662
  {% endif %}
663
- <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item" role="menuitem">Order History</a>
664
- <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item" role="menuitem">View Profile</a>
665
- <div class="dropdown-item upload-item" role="menuitem">
666
  <label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
667
  Upload Image
668
  </label>
669
- <input type="file" id="avatarUpload" accept="image/*" style="display: none;" aria-label="Upload Avatar">
670
  </div>
671
- <a href="{{ url_for('logout') }}" class="dropdown-item" role="menuitem">Logout</a>
 
 
672
  </div>
673
  </div>
 
674
  <div class="search-bar-container">
675
- <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off" aria-label="Search menu items">
676
  <i class="bi bi-search search-icon"></i>
677
- <i class="bi bi-mic mic-icon" id="micIcon" role="button" aria-label="Voice Search"></i>
678
  <span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
679
  <div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
680
  </div>
681
  </div>
682
 
683
- <form method="get" action="/menu" class="text-center mb-4" id="categoryForm" role="form">
684
- <label class="form-label fw-bold" for="selectedCategoryInput">Select a Category:</label>
685
  <div class="category-buttons">
686
  {% for category in categories %}
687
- <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>
688
  {% endfor %}
689
- <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>
690
  </div>
691
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
692
  </form>
693
 
694
- <div class="container mt-4" role="main">
695
  {% if selected_category == "Customized Dish" %}
696
  <div id="custom-dish-form" class="mt-4">
697
  <h3>Create Your Custom Dish</h3>
698
- <form method="POST" action="/customdish/generate_custom_dish" id="customDishForm" role="form">
699
  <div class="mb-3">
700
  <label for="custom-dish-name" class="form-label">Dish Name</label>
701
- <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">
702
  </div>
703
  <div class="mb-3 position-relative">
704
  <label for="custom-dish-description" class="form-label">Dish Description</label>
705
- <textarea class="form-control" id="custom-dish-description" name="description" required rows="3" placeholder="e.g., Chicken, Tomatoes, Spices" aria-label="Dish Description"></textarea>
706
  <div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
707
  </div>
708
- <button type="submit" class="btn btn-primary" aria-label="Submit Custom Dish">Submit Custom Dish</button>
709
  </form>
710
  </div>
711
  {% else %}
712
  {% if ordered_menu.items()|length == 0 %}
713
- <p class="text-center">No menu items available for this category.</p>
714
  {% else %}
715
  {% for section, items in ordered_menu.items() %}
716
  <h3>{{ section }}</h3>
717
  <div class="row">
718
  {% for item in items %}
719
- <div class="col-md-6 mb-4">
720
  <div class="card menu-card">
721
  <video
722
  class="card-img-top menu-video"
@@ -729,8 +981,7 @@
729
  height="200"
730
  onmouseover="this.play()"
731
  onmouseout="this.pause(); this.currentTime = 0;"
732
- onerror="this.poster='/static/placeholder.jpg';"
733
- aria-label="Video preview of {{ item.Name | default('Unnamed Item') }}">
734
  <source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
735
  Your browser does not support the video tag.
736
  </video>
@@ -740,7 +991,7 @@
740
  <h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
741
  <p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
742
  {% if item.Section__c != 'Soft Drinks' %}
743
- <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>
744
  {% endif %}
745
  </div>
746
  <div class="d-flex flex-column align-item-center justify-content-center">
@@ -751,13 +1002,12 @@
751
  data-item-section="{{ item.Section__c | default(section) }}"
752
  data-item-category="{{ selected_category }}">
753
  {% if item.Section__c == 'Soft Drinks' %}
754
- <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)" aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">ADD</button>
755
  {% else %}
756
  <button class="btn btn-primary"
757
  data-bs-toggle="modal"
758
  data-bs-target="#itemModal"
759
- 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 }}')"
760
- aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">
761
  ADD
762
  </button>
763
  {% endif %}
@@ -773,7 +1023,7 @@
773
  <h6>Description</h6>
774
  <p>{{ item.Description__c | default('No description available') }}</p>
775
  <h6>Ingredients Info</h6>
776
- <p>{{ item.Ingredientsinfo__c | default('Not specified') }}</p>
777
  <h6>Nutritional Info</h6>
778
  <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
779
  <h6>Allergens</h6>
@@ -789,11 +1039,11 @@
789
  {% endif %}
790
  </div>
791
 
792
- <div class="bottom-action-bar" role="navigation">
793
- <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history" aria-label="View Order History">
794
  <i class="bi bi-clock-history"></i> Order History
795
  </a>
796
- <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart" aria-label="View Cart">
797
  <i class="bi bi-cart"></i> View Cart
798
  <span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
799
  </a>
@@ -823,17 +1073,17 @@
823
  </div>
824
  <div class="mt-4">
825
  <h6>Custom Request</h6>
826
- <textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..." aria-label="Special Instructions"></textarea>
827
  </div>
828
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
829
  </div>
830
  <div class="modal-footer d-flex align-items-center justify-content-between">
831
  <div class="d-flex align-items-center gap-2">
832
- <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity" aria-label="Decrease Quantity">-</button>
833
- <input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;" aria-label="Quantity"/>
834
- <button type="button" class="btn btn-outline-secondary" id="increaseQuantity" aria-label="Increase Quantity">+</button>
835
  </div>
836
- <button type="button" class="btn btn-primary" onclick="addToCartFromModal()" aria-label="Add to Cart">Add to Cart</button>
837
  </div>
838
  </div>
839
  </div>
@@ -851,7 +1101,7 @@
851
  <img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
852
  </div>
853
  <div class="modal-footer">
854
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Close Avatar Modal">Close</button>
855
  </div>
856
  </div>
857
  </div>
@@ -873,26 +1123,26 @@
873
  </div>
874
  <div class="d-flex justify-content-center align-items-center mb-4">
875
  <div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
876
- <button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;" aria-label="Decrease Drink Quantity">-</button>
877
- <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">
878
- <button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;" aria-label="Increase Drink Quantity">+</button>
879
  </div>
880
  </div>
881
  </div>
882
  <div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
883
- <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>
884
  </div>
885
  </div>
886
  </div>
887
  </div>
888
 
889
  <!-- Mic Popup -->
890
- <div class="mic-popup" id="micPopup" role="alert" aria-live="polite">
891
  <div class="mic-popup-icon">
892
  <i class="bi bi-mic-fill"></i>
893
  </div>
894
  <div class="mic-popup-text" id="micPopupText">Listening...</div>
895
- <button class="mic-popup-cancel" id="micPopupCancel" aria-label="Cancel Voice Search">Cancel</button>
896
  </div>
897
 
898
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
@@ -908,12 +1158,6 @@
908
  {% endfor %}
909
  ];
910
 
911
- const sections = [
912
- {% for section in ordered_menu.keys() %}
913
- "{{ section | e }}",
914
- {% endfor %}
915
- ];
916
-
917
  const ingredientsList = [
918
  "Basmati Rice", "Bell Pepper", "Biryani Masala", "Butter", "Capsicum", "Cauliflower",
919
  "Chickpea Flour (Besan)", "Chickpea Flour (for batter)", "Chickpeas (Channa)", "Chili Powder",
@@ -930,6 +1174,7 @@
930
  "Whole Wheat Flour", "Yogurt (Curd)"
931
  ];
932
 
 
933
  function sanitizeInput(input) {
934
  const div = document.createElement('div');
935
  div.textContent = input;
@@ -944,7 +1189,7 @@
944
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
945
  );
946
  if (existingItem) {
947
- existingItem.quantity += payload.quantity;
948
  } else {
949
  cart.push(payload);
950
  }
@@ -952,16 +1197,34 @@
952
  return cart;
953
  }
954
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  function getCartLocalStorage() {
956
  return JSON.parse(localStorage.getItem('cart')) || [];
957
  }
958
 
959
- function updateCartUI(cart) {
960
- let totalQuantity = 0;
961
- cart.forEach(item => totalQuantity += item.quantity);
962
- const cartItemCount = document.getElementById('cart-item-count');
963
- cartItemCount.innerText = totalQuantity;
964
- cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
965
  }
966
 
967
  function showSoftDrinkModal(button) {
@@ -970,29 +1233,29 @@
970
  const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
971
  const itemPrice = buttonContainer.getAttribute('data-item-price');
972
  const itemImage = buttonContainer.getAttribute('data-item-image');
 
973
  document.getElementById('soft-drink-name').textContent = itemName;
974
  document.getElementById('soft-drink-price').textContent = `$${itemPrice}`;
975
  document.getElementById('soft-drink-quantity').value = '1';
976
- document.getElementById('soft-drink-image').src = itemImage || '/static/placeholder.jpg';
 
 
977
  const modal = new bootstrap.Modal(document.getElementById('softDrinkModal'));
978
  modal.show();
979
  }
980
 
981
  function addSoftDrinkToCart() {
982
- if (!currentSoftDrinkButton || isProcessingRequest) return;
983
- isProcessingRequest = true;
984
  const buttonContainer = currentSoftDrinkButton.closest('.button-container');
985
  const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
986
- if (quantity < 1 || quantity > 99) {
987
- alert('Quantity must be between 1 and 99.');
988
- isProcessingRequest = false;
989
- return;
990
- }
991
  const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
992
  const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
993
  const itemImage = buttonContainer.getAttribute('data-item-image');
994
  const section = sanitizeInput(buttonContainer.getAttribute('data-item-section'));
995
  const selectedCategory = sanitizeInput(buttonContainer.getAttribute('data-item-category'));
 
996
  const cartPayload = {
997
  itemName: itemName,
998
  itemPrice: itemPrice,
@@ -1001,12 +1264,14 @@
1001
  category: selectedCategory,
1002
  addons: [],
1003
  instructions: '',
1004
- quantity: quantity,
1005
- timestamp: new Date().toISOString()
1006
  };
 
1007
  fetch('/cart/add', {
1008
  method: 'POST',
1009
- headers: { 'Content-Type': 'application/json' },
 
 
1010
  body: JSON.stringify(cartPayload)
1011
  })
1012
  .then(response => response.json())
@@ -1014,29 +1279,56 @@
1014
  if (data.success) {
1015
  alert('Item added to cart successfully!');
1016
  updateCartUI(data.cart);
1017
- bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
 
1018
  } else {
1019
- throw new Error(data.error || 'Failed to add item to cart');
 
 
 
 
 
1020
  }
1021
  })
1022
  .catch(err => {
 
1023
  alert('Error adding item to cart. Using local storage as fallback.');
1024
  const cart = addToCartLocalStorage(cartPayload);
1025
  updateCartUI(cart);
1026
- bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
1027
- })
1028
- .finally(() => {
1029
- isProcessingRequest = false;
 
 
 
 
 
 
 
 
 
 
1030
  });
 
 
 
 
 
 
1031
  }
1032
 
1033
  document.addEventListener('DOMContentLoaded', function () {
 
 
1034
  const avatarIcon = document.getElementById('avatarIcon');
1035
- const dropdownMenu = document.getElementById('avatarDropdown');
1036
  const avatarUpload = document.getElementById('avatarUpload');
1037
  const deleteAvatar = document.getElementById('deleteAvatar');
1038
  const viewAvatar = document.getElementById('viewAvatar');
 
 
1039
 
 
1040
  function loadAvatar() {
1041
  const savedAvatar = localStorage.getItem('userAvatar');
1042
  const avatarImage = avatarIcon.querySelector('.avatar-image');
@@ -1045,17 +1337,23 @@
1045
  img.src = savedAvatar;
1046
  img.alt = 'User Avatar';
1047
  img.className = 'avatar-image';
 
1048
  avatarIcon.innerHTML = '';
1049
  avatarIcon.appendChild(img);
 
 
 
 
1050
  }
1051
  }
1052
 
 
1053
  function ensureAvatarOptions() {
1054
  if (!document.querySelector('#deleteAvatar')) {
1055
  const deleteItem = document.createElement('div');
1056
  deleteItem.className = 'dropdown-item delete-item';
1057
  deleteItem.id = 'deleteAvatar';
1058
- deleteItem.textContent = 'Delete Image';
1059
  dropdownMenu.insertBefore(deleteItem, dropdownMenu.firstChild);
1060
  addDeleteListener(deleteItem);
1061
  }
@@ -1063,12 +1361,14 @@
1063
  const viewItem = document.createElement('div');
1064
  viewItem.className = 'dropdown-item view-item';
1065
  viewItem.id = 'viewAvatar';
1066
- viewItem.textContent = 'View Avatar';
1067
- dropdownMenu.insertBefore(viewItem, dropdownMenu.children[1]);
 
1068
  addViewListener(viewItem);
1069
  }
1070
  }
1071
 
 
1072
  function removeAvatarOptions() {
1073
  const deleteItem = document.getElementById('deleteAvatar');
1074
  const viewItem = document.getElementById('viewAvatar');
@@ -1076,25 +1376,49 @@
1076
  if (viewItem) viewItem.remove();
1077
  }
1078
 
 
1079
  function saveAvatar(imageUrl) {
1080
  localStorage.setItem('userAvatar', imageUrl);
1081
  }
1082
 
 
1083
  function clearAvatar() {
1084
  localStorage.removeItem('userAvatar');
1085
  avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1086
  removeAvatarOptions();
1087
  }
1088
 
 
1089
  avatarIcon.addEventListener('click', function(event) {
1090
  event.stopPropagation();
1091
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1092
  });
1093
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
  avatarUpload.addEventListener('change', function(event) {
1095
  const file = event.target.files[0];
1096
  if (file) {
1097
- const maxSize = 15 * 1024 * 1024; // 15MB
1098
  if (file.size > maxSize) {
1099
  alert('Image size must not exceed 15MB.');
1100
  this.value = '';
@@ -1105,31 +1429,49 @@
1105
  const base64Image = e.target.result;
1106
  fetch('/upload_avatar', {
1107
  method: 'POST',
1108
- headers: { 'Content-Type': 'application/json' },
 
 
1109
  body: JSON.stringify({ image: base64Image })
1110
  })
1111
- .then(response => response.json())
 
 
 
1112
  .then(data => {
1113
  if (data.success) {
1114
  const img = document.createElement('img');
1115
- img.src = data.image;
1116
  img.alt = 'User Avatar';
1117
  img.className = 'avatar-image';
 
1118
  avatarIcon.innerHTML = '';
1119
  avatarIcon.appendChild(img);
1120
  saveAvatar(data.image);
1121
  ensureAvatarOptions();
1122
  dropdownMenu.style.display = 'none';
1123
  } else {
1124
- throw new Error(data.error || 'Upload failed');
 
 
 
 
 
 
 
 
 
 
1125
  }
1126
  })
1127
  .catch(error => {
1128
- alert('Error uploading image. Using default as fallback.');
 
1129
  const img = document.createElement('img');
1130
  img.src = '/static/default-avatar.jpg';
1131
  img.alt = 'User Avatar';
1132
  img.className = 'avatar-image';
 
1133
  avatarIcon.innerHTML = '';
1134
  avatarIcon.appendChild(img);
1135
  saveAvatar('/static/default-avatar.jpg');
@@ -1137,65 +1479,97 @@
1137
  dropdownMenu.style.display = 'none';
1138
  });
1139
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
1140
  reader.readAsDataURL(file);
1141
  }
1142
  });
1143
 
 
1144
  function addDeleteListener(deleteElement) {
1145
  deleteElement.addEventListener('click', function() {
1146
  fetch('/delete_avatar', {
1147
  method: 'POST',
1148
- headers: { 'Content-Type': 'application/json' }
 
 
 
 
 
 
1149
  })
1150
- .then(response => response.json())
1151
  .then(data => {
1152
  if (data.success) {
1153
  clearAvatar();
1154
  dropdownMenu.style.display = 'none';
1155
  } else {
1156
- throw new Error(data.error || 'Delete failed');
1157
  }
1158
  })
1159
  .catch(error => {
1160
- alert('Error deleting image. Cleared locally.');
1161
- clearAvatar();
1162
- dropdownMenu.style.display = 'none';
1163
  });
1164
  });
1165
  }
1166
 
 
1167
  function addViewListener(viewElement) {
1168
  viewElement.addEventListener('click', function() {
1169
  const avatarImage = avatarIcon.querySelector('.avatar-image');
1170
  if (avatarImage) {
1171
- document.getElementById('avatar-modal-img').src = avatarImage.src;
 
1172
  const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
1173
  modal.show();
 
 
1174
  }
 
1175
  });
1176
  }
1177
 
 
1178
  if (deleteAvatar) addDeleteListener(deleteAvatar);
1179
  if (viewAvatar) addViewListener(viewAvatar);
 
 
1180
  loadAvatar();
1181
 
 
1182
  document.addEventListener('click', function(event) {
1183
- if (!avatarIcon.contains(event.target) && !dropdownMenu.contains(event.target)) {
1184
  dropdownMenu.style.display = 'none';
 
1185
  }
1186
  });
1187
 
1188
  const menuCards = document.querySelectorAll('.menu-card');
1189
  const menuVideos = document.querySelectorAll('.menu-video');
1190
- const cardObserver = new IntersectionObserver((entries) => {
1191
  entries.forEach(entry => {
1192
  if (entry.isIntersecting) {
1193
  entry.target.classList.add('visible');
1194
- cardObserver.unobserve(entry.target);
1195
  }
1196
  });
1197
- }, { threshold: 0.1 });
1198
- const videoObserver = new IntersectionObserver((entries) => {
 
 
 
 
1199
  entries.forEach(entry => {
1200
  if (entry.isIntersecting) {
1201
  const video = entry.target;
@@ -1206,34 +1580,40 @@
1206
  video.load();
1207
  }
1208
  video.classList.add('loaded');
1209
- videoObserver.unobserve(video);
1210
  }
1211
  });
1212
- }, { rootMargin: '200px' });
 
 
 
 
1213
  menuCards.forEach(card => cardObserver.observe(card));
1214
  menuVideos.forEach(video => videoObserver.observe(video));
1215
 
1216
  const toggleLinks = document.querySelectorAll('.toggle-details');
1217
  toggleLinks.forEach(link => {
1218
- link.addEventListener('click', function() {
1219
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1220
  const detailsDiv = document.getElementById(`details-${itemName}`);
1221
  const isCurrentlyShown = detailsDiv.classList.contains('show');
 
1222
  document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1223
  if (otherDetails !== detailsDiv) {
1224
  otherDetails.classList.remove('show');
1225
  const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1226
- if (otherLink) otherLink.textContent = 'Show Details';
 
 
1227
  }
1228
  });
 
1229
  if (!isCurrentlyShown) {
1230
  detailsDiv.classList.add('show');
1231
- this.textContent = 'Hide Details';
1232
- this.setAttribute('aria-expanded', 'true');
1233
  } else {
1234
  detailsDiv.classList.remove('show');
1235
- this.textContent = 'Show Details';
1236
- this.setAttribute('aria-expanded', 'false');
1237
  }
1238
  });
1239
  });
@@ -1243,11 +1623,10 @@
1243
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
1244
  if (!selectedCategoryInput.value) {
1245
  selectedCategoryInput.value = "All";
1246
- const allBtn = document.querySelector('.category-button[data-category="All"]');
1247
- if (allBtn) allBtn.classList.add('selected');
1248
  }
1249
  categoryButtons.forEach(button => {
1250
- button.addEventListener('click', function() {
1251
  categoryButtons.forEach(btn => btn.classList.remove('selected'));
1252
  this.classList.add('selected');
1253
  selectedCategoryInput.value = this.getAttribute('data-category');
@@ -1257,55 +1636,63 @@
1257
 
1258
  const searchBar = document.getElementById('searchBar');
1259
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1260
- searchBar.addEventListener('input', function() {
 
 
 
 
 
 
 
 
1261
  const input = sanitizeInput(this.value.trim().toLowerCase());
1262
  suggestionsContainer.innerHTML = '';
1263
  suggestionsContainer.style.display = 'none';
1264
  if (input) {
1265
- const filteredItems = menuItems.filter(item => item.toLowerCase().includes(input));
 
 
1266
  if (filteredItems.length > 0) {
1267
  filteredItems.forEach(item => {
1268
  const suggestionDiv = document.createElement('div');
1269
  suggestionDiv.classList.add('suggestion-item');
1270
- suggestionDiv.textContent = item;
1271
- suggestionDiv.addEventListener('click', function() {
1272
  searchBar.value = item;
1273
  suggestionsContainer.style.display = 'none';
1274
- filterMenu();
1275
  });
1276
  suggestionsContainer.appendChild(suggestionDiv);
1277
  });
1278
  suggestionsContainer.style.display = 'block';
1279
  }
1280
  }
1281
- filterMenu();
1282
  });
1283
- document.addEventListener('click', function(event) {
 
1284
  if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
1285
  suggestionsContainer.style.display = 'none';
1286
  }
1287
  });
1288
 
1289
- const customDishForm = document.getElementById('customDishForm');
1290
- if (customDishForm) {
1291
- const descriptionTextarea = document.getElementById('custom-dish-description');
1292
- const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1293
  let usedIngredients = new Set();
1294
-
1295
  function updateUsedIngredients() {
1296
  const inputText = descriptionTextarea.value.trim();
1297
  usedIngredients.clear();
1298
  if (inputText) {
1299
  const words = inputText.split(/,\s*/).map(word => word.trim());
1300
  words.forEach(word => {
1301
- if (ingredientsList.includes(word)) {
1302
  usedIngredients.add(word);
1303
  }
1304
  });
1305
  }
1306
  }
1307
-
1308
- descriptionTextarea.addEventListener('input', function() {
1309
  const inputText = this.value.trim();
1310
  const words = inputText.split(/,\s*/);
1311
  const lastWord = words[words.length - 1].trim().toLowerCase();
@@ -1320,8 +1707,8 @@
1320
  filteredIngredients.forEach(ingredient => {
1321
  const suggestionDiv = document.createElement('div');
1322
  suggestionDiv.classList.add('suggestion-item');
1323
- suggestionDiv.textContent = ingredient;
1324
- suggestionDiv.addEventListener('click', function() {
1325
  const currentValue = descriptionTextarea.value;
1326
  const lastCommaIndex = currentValue.lastIndexOf(',');
1327
  const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
@@ -1336,25 +1723,33 @@
1336
  }
1337
  }
1338
  });
1339
-
1340
- document.addEventListener('click', function(event) {
1341
  if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1342
  descriptionSuggestions.style.display = 'none';
1343
  }
1344
  });
 
1345
 
 
 
1346
  customDishForm.addEventListener('submit', function(event) {
1347
- event.preventDefault();
1348
- const dishName = sanitizeInput(document.getElementById('custom-dish-name').value.trim());
1349
- const description = sanitizeInput(descriptionTextarea.value.trim());
1350
  if (!dishName || !description) {
 
1351
  alert('Please fill in both the dish name and description.');
1352
  return;
1353
  }
 
1354
  fetch('/customdish/generate_custom_dish', {
1355
  method: 'POST',
1356
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1357
- body: new URLSearchParams({ 'name': dishName, 'description': description })
 
 
 
 
 
1358
  })
1359
  .then(response => response.json())
1360
  .then(data => {
@@ -1362,41 +1757,105 @@
1362
  alert('Custom dish submitted successfully!');
1363
  window.location.reload();
1364
  } else {
1365
- throw new Error(data.error || 'Submission failed');
1366
  }
1367
  })
1368
  .catch(error => {
1369
- alert(`Error submitting custom dish: ${error.message}`);
 
1370
  });
1371
  });
1372
  }
1373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1374
  const micIcon = document.getElementById('micIcon');
 
1375
  const micPopup = document.getElementById('micPopup');
1376
  const micPopupText = document.getElementById('micPopupText');
1377
  const micPopupCancel = document.getElementById('micPopupCancel');
1378
- const micUnsupported = document.getElementById('micUnsupported');
1379
-
1380
- if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
1381
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1382
  const recognition = new SpeechRecognition();
1383
  recognition.continuous = false;
1384
  recognition.interimResults = true;
1385
  recognition.lang = 'en-US';
1386
-
1387
- micIcon.addEventListener('click', function() {
 
1388
  micPopup.classList.add('active');
1389
- recognition.start();
1390
- });
1391
-
1392
- recognition.onstart = function() {
1393
  micPopupText.textContent = 'Listening...';
1394
- micIcon.classList.add('active');
1395
  };
1396
-
1397
- recognition.onresult = function(event) {
1398
  let interimTranscript = '';
1399
  let finalTranscript = '';
 
1400
  for (let i = event.resultIndex; i < event.results.length; i++) {
1401
  const transcript = event.results[i][0].transcript;
1402
  if (event.results[i].isFinal) {
@@ -1405,36 +1864,49 @@
1405
  interimTranscript += transcript;
1406
  }
1407
  }
 
1408
  if (interimTranscript) {
1409
  micPopupText.textContent = interimTranscript;
1410
  }
 
1411
  if (finalTranscript) {
1412
  searchBar.value = sanitizeInput(finalTranscript.trim());
1413
- filterMenu();
1414
  micPopup.classList.remove('active');
1415
- micIcon.classList.remove('active');
1416
  }
1417
  };
1418
-
1419
- recognition.onerror = function(event) {
1420
- micPopupText.textContent = 'Error occurred: ' + event.error;
 
 
 
 
 
 
 
 
 
 
1421
  setTimeout(() => {
1422
  micPopup.classList.remove('active');
1423
- micIcon.classList.remove('active');
1424
  }, 2000);
 
1425
  };
1426
-
1427
- recognition.onend = function() {
1428
- if (micPopup.classList.contains('active')) {
1429
- micPopupText.textContent = 'Recognition ended.';
 
 
1430
  setTimeout(() => {
1431
  micPopup.classList.remove('active');
1432
- micIcon.classList.remove('active');
1433
- }, 1000);
1434
  }
1435
- };
1436
-
1437
- micPopupCancel.addEventListener('click', function() {
1438
  recognition.stop();
1439
  micPopup.classList.remove('active');
1440
  micIcon.classList.remove('active');
@@ -1444,42 +1916,47 @@
1444
  micUnsupported.style.display = 'block';
1445
  }
1446
 
1447
- document.getElementById('increaseQuantity').addEventListener('click', function() {
1448
- let quantity = parseInt(document.getElementById('quantityInput').value);
1449
- if (quantity < 99) document.getElementById('quantityInput').value = quantity + 1;
1450
- });
1451
-
1452
- document.getElementById('decreaseQuantity').addEventListener('click', function() {
1453
- let quantity = parseInt(document.getElementById('quantityInput').value);
1454
- if (quantity > 1) document.getElementById('quantityInput').value = quantity - 1;
1455
- });
1456
-
1457
- document.getElementById('soft-drink-increase').addEventListener('click', function() {
1458
- let quantity = parseInt(document.getElementById('soft-drink-quantity').value);
1459
- if (quantity < 99) document.getElementById('soft-drink-quantity').value = quantity + 1;
1460
- });
1461
-
1462
- document.getElementById('soft-drink-decrease').addEventListener('click', function() {
1463
- let quantity = parseInt(document.getElementById('soft-drink-quantity').value);
1464
- if (quantity > 1) document.getElementById('soft-drink-quantity').value = quantity - 1;
1465
- });
1466
 
1467
- const initialCart = getCartLocalStorage();
1468
- updateCartUI(initialCart);
 
 
1469
 
1470
- fetch('/cart/get')
1471
- .then(response => response.json())
1472
- .then(data => {
1473
- if (data.success) {
1474
- updateCartUI(data.cart);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1475
  }
1476
- })
1477
- .catch(() => {
1478
- updateCartUI(initialCart);
1479
- });
1480
  });
1481
-
1482
- function filterMenu() {
1483
  const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
1484
  const sections = document.querySelectorAll('h3');
1485
  const items = document.querySelectorAll('.menu-card');
@@ -1487,7 +1964,7 @@
1487
  items.forEach(item => {
1488
  const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1489
  const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1490
- if (itemName.includes(input) || itemSection.includes(input)) {
1491
  item.style.display = 'block';
1492
  item.classList.add('visible');
1493
  matchedSections.add(item.closest('.row'));
@@ -1518,26 +1995,25 @@
1518
  }
1519
 
1520
  function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
1521
- document.getElementById('modal-name').textContent = name;
1522
- document.getElementById('modal-price').textContent = `$${price}`;
1523
- document.getElementById('modal-img').src = image || '/static/placeholder.jpg';
1524
- document.getElementById('modal-description').textContent = description || 'No description available.';
1525
- document.getElementById('modal-ingredients').textContent = ingredients || 'Not specified';
1526
- document.getElementById('modal-nutrition').textContent = nutrition || 'Not available';
1527
- document.getElementById('modal-allergens').textContent = allergens || 'None listed';
1528
- document.getElementById('modal-section').dataset.section = section;
1529
- document.getElementById('modal-section').dataset.category = selectedCategory;
1530
- document.getElementById('quantityInput').value = '1';
1531
  document.getElementById('modal-instructions').value = '';
1532
- fetchAddons(name, section);
1533
- }
 
 
1534
 
1535
- function fetchAddons(itemName, section) {
1536
- const addonsList = document.getElementById('addons-list');
1537
- addonsList.innerHTML = 'Loading customization options...';
1538
- fetch(`/api/addons?item_name=${encodeURIComponent(itemName)}&item_section=${encodeURIComponent(section)}`)
1539
  .then(response => response.json())
1540
  .then(data => {
 
1541
  addonsList.innerHTML = '';
1542
  if (!data.success || !data.addons || data.addons.length === 0) {
1543
  addonsList.innerHTML = '<p>No customization options available.</p>';
@@ -1545,13 +2021,16 @@
1545
  }
1546
  data.addons.forEach(addon => {
1547
  const sectionDiv = document.createElement('div');
1548
- sectionDiv.className = 'addon-section';
1549
- sectionDiv.innerHTML = `<h6>${addon.name}</h6>`;
 
 
 
1550
  const optionsContainer = document.createElement('div');
1551
  addon.options.forEach((option, index) => {
1552
  const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
1553
  const listItem = document.createElement('div');
1554
- listItem.className = 'form-check';
1555
  listItem.innerHTML = `
1556
  <input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
1557
  data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
@@ -1565,8 +2044,9 @@
1565
  addonsList.appendChild(sectionDiv);
1566
  });
1567
  })
1568
- .catch(() => {
1569
- addonsList.innerHTML = '<p>Error loading customization options.</p>';
 
1570
  });
1571
  }
1572
 
@@ -1588,38 +2068,34 @@
1588
  }
1589
 
1590
  function addToCartFromModal() {
1591
- if (isProcessingRequest) return;
1592
- isProcessingRequest = true;
1593
- const itemName = sanitizeInput(document.getElementById('modal-name').textContent);
1594
- const itemPrice = parseFloat(document.getElementById('modal-price').textContent.replace('$', ''));
1595
  const itemImage = document.getElementById('modal-img').src;
1596
- const section = sanitizeInput(document.getElementById('modal-section').dataset.section);
1597
- const category = sanitizeInput(document.getElementById('modal-section').dataset.category);
1598
  const quantity = parseInt(document.getElementById('quantityInput').value);
1599
- const instructions = sanitizeInput(document.getElementById('modal-instructions').value);
1600
- if (quantity < 1 || quantity > 99) {
1601
- alert('Quantity must be between 1 and 99.');
1602
- isProcessingRequest = false;
1603
- return;
1604
- }
1605
  const addons = Array.from(document.querySelectorAll('.addon-option:checked')).map(cb => ({
1606
  name: cb.getAttribute('data-name'),
1607
  price: parseFloat(cb.getAttribute('data-price') || 0)
1608
  }));
 
1609
  const cartPayload = {
1610
  itemName: itemName,
1611
  itemPrice: itemPrice,
1612
  itemImage: itemImage,
1613
  section: section,
1614
- category: category,
1615
  addons: addons,
1616
  instructions: instructions,
1617
- quantity: quantity,
1618
- timestamp: new Date().toISOString()
1619
  };
 
1620
  fetch('/cart/add', {
1621
  method: 'POST',
1622
- headers: { 'Content-Type': 'application/json' },
 
 
1623
  body: JSON.stringify(cartPayload)
1624
  })
1625
  .then(response => response.json())
@@ -1627,21 +2103,29 @@
1627
  if (data.success) {
1628
  alert('Item added to cart successfully!');
1629
  updateCartUI(data.cart);
1630
- bootstrap.Modal.getInstance(document.getElementById('itemModal')).hide();
 
1631
  } else {
1632
- throw new Error(data.error || 'Failed to add item to cart');
 
 
 
 
 
 
1633
  }
1634
  })
1635
  .catch(err => {
 
1636
  alert('Error adding item to cart. Using local storage as fallback.');
1637
  const cart = addToCartLocalStorage(cartPayload);
1638
  updateCartUI(cart);
1639
- bootstrap.Modal.getInstance(document.getElementById('itemModal')).hide();
1640
- })
1641
- .finally(() => {
1642
- isProcessingRequest = false;
1643
  });
1644
  }
1645
  </script>
1646
  </body>
1647
- </html>
 
 
1
+ <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
 
26
  }
27
  .container {
28
  max-width: 900px;
 
29
  }
30
  .menu-card {
31
  max-width: 350px;
 
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;
 
48
  object-fit: cover;
49
  border-radius: 15px 15px 0 0;
50
  opacity: 0;
51
+ transition: opacity 0.5s ease-in-out;
52
  background-color: #000;
53
  }
54
  .menu-video.loaded {
55
  opacity: 1;
56
  }
57
  .menu-card:hover .menu-video {
58
+ opacity: 1;
59
  transform: scale(1.05);
60
  }
61
  .menu-card .card-body .card-title {
 
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;
 
90
  align-items: center;
91
  justify-content: center;
92
  gap: 6px;
 
 
 
 
 
 
 
 
93
  }
94
  .customisable-text {
95
  color: #0FAA39;
 
146
  color: white;
147
  font-size: 20px;
148
  font-weight: bold;
149
+ position: relative;
150
  transition: transform 0.2s ease;
151
  }
152
  .avatar-icon:hover {
 
157
  height: 100%;
158
  object-fit: cover;
159
  border-radius: 50%;
160
+ transition: transform 0.2s ease;
161
+ }
162
+ .avatar-icon img:hover {
163
+ transform: scale(1.1);
164
  }
165
  .dropdown-menu {
166
  position: absolute;
 
180
  text-decoration: none;
181
  color: #333;
182
  font-size: 14px;
183
+ transition: background-color 0.2s ease, color 0.2s ease;
184
  cursor: pointer;
185
  }
186
  .dropdown-menu .dropdown-item:hover {
187
  background-color: #ffe4c4;
188
+ color: #2c2c2c;
189
+ }
190
+ .upload-item,
191
+ .delete-item,
192
+ .view-item {
193
+ padding: 10px 16px;
194
+ text-decoration: none;
195
+ color: #333;
196
+ font-size: 14px;
197
+ transition: background-color 0.2s ease;
198
+ cursor: pointer;
199
+ }
200
+ .upload-item:hover,
201
+ .delete-item:hover,
202
+ .view-item:hover {
203
+ background-color: #ffe4c4;
204
+ color: #2c2c2c;
205
+ }
206
+ .submenu {
207
+ position: absolute;
208
+ left: 100%;
209
+ top: 0;
210
+ background-color: #fff8f0;
211
+ border-radius: 8px;
212
+ width: 250px;
213
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
214
+ display: none;
215
+ border: 1px solid #ffd8b1;
216
+ padding: 5px 0;
217
+ max-height: 300px;
218
+ overflow-y: auto;
219
+ z-index: 1001;
220
+ }
221
+ .menu-item-name {
222
+ padding: 8px 12px;
223
+ font-size: 13px;
224
  }
225
  .fixed-top-bar {
226
+ position: relative;
227
  top: 0;
228
  left: 0;
229
  width: 100%;
 
235
  justify-content: space-between;
236
  align-items: center;
237
  z-index: 1000;
 
238
  }
239
  .search-bar-container {
240
  position: absolute;
 
245
  align-items: center;
246
  width: 300px;
247
  max-width: 90%;
248
+ position: relative;
 
 
249
  }
250
  .search-bar-container input {
251
  width: 100%;
252
+ padding: 8px 40px 8px 40px;
253
  font-size: 16px;
254
  border-radius: 25px;
255
  border: none;
256
+ background-color: #fff;
257
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
258
  outline: none;
259
  }
 
 
 
260
  .search-bar-container input::placeholder {
261
  color: #888;
262
  }
 
272
  font-size: 18px;
273
  color: #888;
274
  cursor: pointer;
275
+ transition: Wcolor 0.3s ease;
276
  }
277
  .mic-icon.active {
278
  color: #007bff;
 
283
  right: 15px;
284
  font-size: 14px;
285
  color: #888;
286
+ background-color: #fff;
287
+ padding: 2px 8px;
288
+ border-radius: 10px;
289
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
290
  }
291
  .autocomplete-suggestions {
292
  position: absolute;
 
331
  color: #343a40;
332
  }
333
  .addon-section .form-check-input {
334
+ -webkit-appearance: none;
335
+ -moz-appearance: none;
336
+ appearance: none;
337
  width: 20px;
338
  height: 20px;
339
  border: 2px solid #343a40;
 
356
  }
357
  .addon-section .form-check-label {
358
  font-size: 16px;
359
+ margin-left: 5px;
360
  margin-right: 15px;
361
  cursor: pointer;
362
+ display: inline-block;
363
+ vertical-align: middle;
364
  }
365
  form.text-center.mb-4 {
366
  display: flex;
 
387
  max-height: 400px;
388
  object-fit: contain;
389
  border-radius: 8px;
390
+ margin: 0 auto;
391
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
392
  }
393
  .modal-body #modal-img {
 
400
  .modal-body #modal-name {
401
  font-size: 20px;
402
  font-weight: bold;
403
+ text-align: center;
404
  margin-bottom: 5px;
405
  color: #333333;
406
  }
 
408
  font-size: 16px;
409
  font-weight: 500;
410
  color: #000000;
411
+ text-align: center;
412
  margin-bottom: 10px;
413
  }
414
  .modal-body #modal-description {
 
421
  color: #6c757d;
422
  margin-bottom: 10px;
423
  }
424
+ .modal-body #modal-addons h6,
425
+ .modal-body #first-row h6 {
426
  font-size: 14px;
427
  font-weight: bold;
428
  margin-bottom: 10px;
 
433
  justify-content: space-between;
434
  padding: 10px;
435
  }
436
+ .modal-footer .d-flex {
437
+ display: flex;
438
+ align-items: center;
439
+ gap: 10px;
440
+ }
441
  .modal-footer .btn {
442
  height: 40px;
443
  padding: 0 15px;
 
448
  text-align: center;
449
  }
450
  .modal-footer .btn-primary {
451
+ background-color: #0FAA39;
452
+ border-color: #0FAA39;
453
+ font-weight: bold;
454
  padding: 10px 20px;
455
+ height: 40px;
456
+ display: flex;
457
+ justify-content: center;
458
+ align-items: center;
459
  width: auto;
460
  }
461
  .modal-footer .btn-outline-secondary {
462
+ height: 40px;
463
  width: 40px;
464
  }
465
  .item-details {
 
467
  padding: 15px;
468
  background-color: #f8f9fa;
469
  border-radius: 8px;
470
+ margin-top: 10px;
471
  margin: 10px 15px;
472
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
473
  }
 
490
  cursor: pointer;
491
  color: #0FAA39;
492
  font-size: 0.9rem;
493
+ text-align: left;
494
  padding: 5px 0;
495
  transition: color 0.3s ease;
496
+ display: block;
497
+ width: 100%;
498
+ margin-top: 5px;
499
  }
500
  .toggle-details:hover {
501
  color: #0D9232;
 
521
  .category-button.selected {
522
  background-color: #0FAA39;
523
  color: #fff;
524
+ border-color: #0FAA39;
525
  }
526
  .category-button:hover {
527
  background-color: #e6f4ea;
528
  }
529
+ .quantity-selector {
530
+ display: flex;
531
+ align-items: center;
532
+ gap: 5px;
533
+ }
534
+ .quantity-selector .btn {
535
+ width: 25px;
536
+ height: 25px;
537
+ padding: 0;
538
+ font-size: 12px;
539
+ line-height: 25px;
540
+ text-align: center;
541
+ }
542
+ .quantity-selector .quantity-display {
543
+ width: 25px;
544
+ text-align: center;
545
+ font-size: 12px;
546
+ font-weight: bold;
547
+ line-height: 25px;
548
+ }
549
+ .quantity-selector .quantity-to-add,
550
+ .quantity-selector .quantity-to-remove {
551
+ width: 45px;
552
+ height: 25px;
553
+ font-size: 12px;
554
+ padding: 0 5px;
555
+ }
556
+ .modal-dialog {
557
+ max-height: 90vh;
558
+ }
559
  .modal-body::-webkit-scrollbar {
560
  width: 8px;
561
  }
 
570
  .modal-body::-webkit-scrollbar-thumb:hover {
571
  background: #0D9232;
572
  }
573
+ .btn-primary:disabled {
574
+ opacity: 0.65;
575
+ cursor: not-allowed;
576
+ }
577
+ .quantity-selector select {
578
+ width: 60px;
579
+ height: 35px;
580
+ padding: 5px;
581
+ border-radius: 5px;
582
+ border: 1px solid #ced4da;
583
+ }
584
  #custom-dish-form {
585
  position: relative;
586
+ padding-bottom: 80px;
 
 
 
 
 
 
 
 
 
 
 
 
587
  }
588
  #custom-dish-form .btn-primary {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  position: absolute;
590
+ right: 15px;
591
+ bottom: 15px;
592
+ width: auto;
593
+ padding: 10px 20px;
 
 
 
 
 
594
  }
595
  .bottom-action-bar {
596
  position: fixed;
 
618
  display: flex;
619
  align-items: center;
620
  justify-content: center;
621
+ text-align: center;
622
+ min-width: 0;
623
+ white-space: nowrap;
624
  }
625
  .bottom-action-bar .btn-order-history {
626
  background-color: #FFA07A;
 
664
  display: none;
665
  width: 300px;
666
  max-width: 90%;
667
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
668
  }
669
  .mic-popup.active {
670
  display: block;
 
678
  .mic-popup-text {
679
  font-size: 18px;
680
  margin-bottom: 15px;
681
+ min-height: 24px;
682
  }
683
  .mic-popup-cancel {
684
  background-color: #ff4444;
 
695
  100% { transform: scale(1); }
696
  }
697
  @media (max-width: 576px) {
698
+ .fixed-top-bar {
699
+ height: 60px;
700
+ padding: 10px;
701
+ }
702
  .search-bar-container {
703
  width: 80%;
704
+ max-width: 100%;
705
  left: 10px;
706
+ top: 50%;
707
+ transform: translateY(-50%);
708
  }
709
+ .search-bar-container input {
710
+ padding: 6px 35px 6px 35px;
711
+ font-size: 14px;
712
+ border-radius: 20px;
713
  }
714
+ .search-icon {
715
+ left: 12px;
716
+ font-size: 16px;
717
+ }
718
+ .mic-icon {
719
+ right: 12px;
720
+ font-size: 16px;
721
+ }
722
+ .avatar-dropdown-container {
723
+ right: 10px;
724
+ }
725
+ .avatar-icon {
726
+ width: 40px;
727
+ height: 40px;
728
+ font-size: 20px;
729
+ }
730
+ .dropdown-menu {
731
+ width: 180px;
732
+ }
733
+ .dropdown-menu .dropdown-item {
734
+ padding: 8px 12px;
735
+ font-size: 13px;
736
+ }
737
+ .upload-item,
738
+ .delete-item,
739
+ .view-item {
740
+ padding: 8px 12px;
741
+ font-size: 13px;
742
+ }
743
+ .category-buttons {
744
+ gap: 8px;
745
+ }
746
+ .category-button {
747
+ padding: 4px 12px;
748
+ font-size: 0.85rem;
749
+ }
750
+ .modal-dialog {
751
+ max-width: 96%;
752
+ margin: 5px auto;
753
+ }
754
+ .modal-header {
755
+ padding: 5px 10px;
756
+ }
757
+ .modal-title {
758
+ font-size: 14px;
759
+ }
760
+ .modal-body {
761
+ max-height: 50vh;
762
+ padding: 8px;
763
+ }
764
+ .modal-body #avatar-modal-img {
765
+ max-height: 300px;
766
+ }
767
+ .modal-body #modal-img {
768
+ max-height: 150px;
769
+ width: 100%;
770
+ max-width: 150px;
771
+ margin: 0 auto 5px;
772
+ display: block;
773
+ }
774
+ .modal-body #modal-name {
775
+ font-size: 18px;
776
+ margin-bottom: 3px;
777
+ }
778
+ .modal-body #modal-price {
779
  font-size: 14px;
780
+ margin-bottom: 5px;
781
+ }
782
+ .modal-body #modal-description {
783
+ font-size: 12px;
784
+ margin-bottom: 5px;
785
+ }
786
+ .modal-body .nutritional-info {
787
+ font-size: 10px;
788
+ margin-bottom: 5px;
789
+ }
790
+ .modal-body #modal-addons h6,
791
+ .modal-body #first-row h6 {
792
+ font-size: 12px;
793
+ margin-bottom: 5px;
794
+ }
795
+ .modal-footer {
796
+ padding: 5px;
797
+ }
798
+ .modal-footer .btn {
799
+ height: 30px;
800
+ padding: 0 10px;
801
+ }
802
+ .modal-footer .form-control {
803
+ width: 30px;
804
+ height: 30px;
805
+ font-size: 12px;
806
+ font-weight: bold;
807
+ }
808
+ .modal-footer .btn-outline-secondary {
809
+ width: 25px;
810
+ height: 25px;
811
+ font-size: 12px;
812
+ line-height: 25px;
813
+ }
814
+ .modal-footer .btn-primary {
815
+ font-size: 12px;
816
+ height: 30px;
817
+ padding: 0 15px;
818
+ border-radius: 5px;
819
+ }
820
+ .btn-primary {
821
+ font-size: 10px;
822
+ width: 50px;
823
+ height: 25px;
824
+ }
825
+ .customisable-text {
826
+ font-size: 8px;
827
+ }
828
+ .button-container {
829
+ gap: 3px;
830
+ }
831
+ .quantity-selector .btn {
832
+ width: 18px;
833
+ height: 18px;
834
+ font-size: 9px;
835
+ line-height: 18px;
836
+ }
837
+ .quantity-selector .quantity-display {
838
+ width: 18px;
839
+ font-size: 9px;
840
+ line-height: 18px;
841
+ }
842
+ .quantity-selector .quantity-to-add,
843
+ .quantity-selector .quantity-to-remove {
844
+ width: 35px;
845
+ height: 18px;
846
+ font-size: 9px;
847
+ }
848
+ .quantity-selector select {
849
+ width: 50px;
850
+ height: 30px;
851
+ font-size: 12px;
852
+ }
853
+ .bottom-action-bar {
854
+ padding: 8px 10px;
855
+ }
856
+ .bottom-action-bar .btn {
857
  padding: 8px 10px;
858
+ font-size: 14px;
859
  }
860
  .cart-icon-badge {
861
  width: 18px;
862
  height: 18px;
863
  font-size: 10px;
864
+ margin-left: 5px;
865
+ }
866
+ .item-details {
867
+ padding: 10px;
868
+ margin: 5px 10px;
869
+ }
870
+ .item-details h6 {
871
+ font-size: 0.95rem;
872
+ }
873
+ .item-details p {
874
+ font-size: 0.8rem;
875
+ }
876
+ .toggle-details {
877
+ font-size: 0.8rem;
878
+ }
879
+ .mic-popup {
880
+ padding: 20px;
881
+ width: 280px;
882
+ }
883
+ .mic-popup-icon {
884
+ font-size: 36px;
885
+ margin-bottom: 15px;
886
+ }
887
+ .mic-popup-text {
888
+ font-size: 16px;
889
+ }
890
+ .mic-popup-cancel {
891
+ padding: 6px 16px;
892
+ font-size: 14px;
893
  }
894
  }
895
  </style>
896
  </head>
897
  <body>
898
+ <div class="fixed-top-bar">
899
  <div class="avatar-dropdown-container">
900
+ <div class="avatar-icon" id="avatarIcon">
901
  {% if user_image %}
902
  <img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
903
  {% else %}
904
  <span>{{ first_letter }}</span>
905
  {% endif %}
906
  </div>
907
+ <div class="dropdown-menu" id="avatarDropdown">
908
  {% if user_image %}
909
+ <div class="dropdown-item delete-item" id="deleteAvatar">Delete Image</div>
910
+ <div class="dropdown-item view-item" id="viewAvatar">View Avatar</div>
911
  {% endif %}
912
+ <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item">Order History</a>
913
+ <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item">View Profile</a>
914
+ <div class="dropdown-item upload-item">
915
  <label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
916
  Upload Image
917
  </label>
918
+ <input type="file" id="avatarUpload" accept="image/*" style="display: none;">
919
  </div>
920
+ <!-- New Dropdown Item for All Menu Items with Modal Trigger -->
921
+ <div class="dropdown-item" id="allMenuItems" data-bs-toggle="modal" data-bs-target="#allMenuModal">All Menu Items</div>
922
+ <a href="{{ url_for('logout') }}" class="dropdown-item">Logout</a>
923
  </div>
924
  </div>
925
+
926
  <div class="search-bar-container">
927
+ <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off">
928
  <i class="bi bi-search search-icon"></i>
929
+ <i class="bi bi-mic mic-icon" id="micIcon"></i>
930
  <span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
931
  <div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
932
  </div>
933
  </div>
934
 
935
+ <form method="get" action="/menu" class="text-center mb-4" id="categoryForm">
936
+ <label class="form-label fw-bold">Select a Category:</label>
937
  <div class="category-buttons">
938
  {% for category in categories %}
939
+ <button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}">{{ category }}</button>
940
  {% endfor %}
941
+ <button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish">Customized Dish</button>
942
  </div>
943
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
944
  </form>
945
 
946
+ <div class="container mt-4">
947
  {% if selected_category == "Customized Dish" %}
948
  <div id="custom-dish-form" class="mt-4">
949
  <h3>Create Your Custom Dish</h3>
950
+ <form method="POST" action="/customdish/generate_custom_dish" id="customDishForm">
951
  <div class="mb-3">
952
  <label for="custom-dish-name" class="form-label">Dish Name</label>
953
+ <input type="text" class="form-control" id="custom-dish-name" name="name" required>
954
  </div>
955
  <div class="mb-3 position-relative">
956
  <label for="custom-dish-description" class="form-label">Dish Description</label>
957
+ <textarea class="form-control" id="custom-dish-description" name="description" required></textarea>
958
  <div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
959
  </div>
960
+ <button type="submit" class="btn btn-primary">Submit Custom Dish</button>
961
  </form>
962
  </div>
963
  {% else %}
964
  {% if ordered_menu.items()|length == 0 %}
965
+ <p>No menu items available for this category.</p>
966
  {% else %}
967
  {% for section, items in ordered_menu.items() %}
968
  <h3>{{ section }}</h3>
969
  <div class="row">
970
  {% for item in items %}
971
+ <div class="col-md-6 mb-4" data-item-name="{{ item.Name | default('Unnamed Item') | e }}">
972
  <div class="card menu-card">
973
  <video
974
  class="card-img-top menu-video"
 
981
  height="200"
982
  onmouseover="this.play()"
983
  onmouseout="this.pause(); this.currentTime = 0;"
984
+ onerror="this.poster='/static/placeholder.jpg';">
 
985
  <source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
986
  Your browser does not support the video tag.
987
  </video>
 
991
  <h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
992
  <p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
993
  {% if item.Section__c != 'Soft Drinks' %}
994
+ <div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}">Show Details</div>
995
  {% endif %}
996
  </div>
997
  <div class="d-flex flex-column align-item-center justify-content-center">
 
1002
  data-item-section="{{ item.Section__c | default(section) }}"
1003
  data-item-category="{{ selected_category }}">
1004
  {% if item.Section__c == 'Soft Drinks' %}
1005
+ <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)">ADD</button>
1006
  {% else %}
1007
  <button class="btn btn-primary"
1008
  data-bs-toggle="modal"
1009
  data-bs-target="#itemModal"
1010
+ 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 }}')">
 
1011
  ADD
1012
  </button>
1013
  {% endif %}
 
1023
  <h6>Description</h6>
1024
  <p>{{ item.Description__c | default('No description available') }}</p>
1025
  <h6>Ingredients Info</h6>
1026
+ <p>{{ item.IngredientsInfo__c | default('Not specified') }}</p>
1027
  <h6>Nutritional Info</h6>
1028
  <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
1029
  <h6>Allergens</h6>
 
1039
  {% endif %}
1040
  </div>
1041
 
1042
+ <div class="bottom-action-bar">
1043
+ <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history">
1044
  <i class="bi bi-clock-history"></i> Order History
1045
  </a>
1046
+ <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart">
1047
  <i class="bi bi-cart"></i> View Cart
1048
  <span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
1049
  </a>
 
1073
  </div>
1074
  <div class="mt-4">
1075
  <h6>Custom Request</h6>
1076
+ <textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..."></textarea>
1077
  </div>
1078
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
1079
  </div>
1080
  <div class="modal-footer d-flex align-items-center justify-content-between">
1081
  <div class="d-flex align-items-center gap-2">
1082
+ <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity">-</button>
1083
+ <input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;"/>
1084
+ <button type="button" class="btn btn-outline-secondary" id="increaseQuantity">+</button>
1085
  </div>
1086
+ <button type="button" class="btn btn-primary" onclick="addToCartFromModal()">Add to Cart</button>
1087
  </div>
1088
  </div>
1089
  </div>
 
1101
  <img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
1102
  </div>
1103
  <div class="modal-footer">
1104
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
1105
  </div>
1106
  </div>
1107
  </div>
 
1123
  </div>
1124
  <div class="d-flex justify-content-center align-items-center mb-4">
1125
  <div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
1126
+ <button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;">-</button>
1127
+ <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;">
1128
+ <button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;">+</button>
1129
  </div>
1130
  </div>
1131
  </div>
1132
  <div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
1133
+ <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>
1134
  </div>
1135
  </div>
1136
  </div>
1137
  </div>
1138
 
1139
  <!-- Mic Popup -->
1140
+ <div class="mic-popup" id="micPopup">
1141
  <div class="mic-popup-icon">
1142
  <i class="bi bi-mic-fill"></i>
1143
  </div>
1144
  <div class="mic-popup-text" id="micPopupText">Listening...</div>
1145
+ <button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
1146
  </div>
1147
 
1148
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
 
1158
  {% endfor %}
1159
  ];
1160
 
 
 
 
 
 
 
1161
  const ingredientsList = [
1162
  "Basmati Rice", "Bell Pepper", "Biryani Masala", "Butter", "Capsicum", "Cauliflower",
1163
  "Chickpea Flour (Besan)", "Chickpea Flour (for batter)", "Chickpeas (Channa)", "Chili Powder",
 
1174
  "Whole Wheat Flour", "Yogurt (Curd)"
1175
  ];
1176
 
1177
+ // Utility function to sanitize input to prevent XSS
1178
  function sanitizeInput(input) {
1179
  const div = document.createElement('div');
1180
  div.textContent = input;
 
1189
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
1190
  );
1191
  if (existingItem) {
1192
+ existingItem.quantity = payload.quantity;
1193
  } else {
1194
  cart.push(payload);
1195
  }
 
1197
  return cart;
1198
  }
1199
 
1200
+ function removeFromCartLocalStorage(itemName, quantityToRemove, instructions, addons) {
1201
+ let cart = JSON.parse(localStorage.getItem('cart')) || [];
1202
+ const itemIndex = cart.findIndex(item =>
1203
+ item.itemName === itemName &&
1204
+ item.instructions === instructions &&
1205
+ JSON.stringify(item.addons) === JSON.stringify(addons)
1206
+ );
1207
+ if (itemIndex !== -1) {
1208
+ if (quantityToRemove >= cart[itemIndex].quantity) {
1209
+ cart.splice(itemIndex, 1);
1210
+ } else {
1211
+ cart[itemIndex].quantity -= quantityToRemove;
1212
+ }
1213
+ }
1214
+ localStorage.setItem('cart', JSON.stringify(cart));
1215
+ return cart;
1216
+ }
1217
+
1218
  function getCartLocalStorage() {
1219
  return JSON.parse(localStorage.getItem('cart')) || [];
1220
  }
1221
 
1222
+ function debounce(func, wait) {
1223
+ let timeout;
1224
+ return function (...args) {
1225
+ clearTimeout(timeout);
1226
+ timeout = setTimeout(() => func.apply(this, args), wait);
1227
+ };
1228
  }
1229
 
1230
  function showSoftDrinkModal(button) {
 
1233
  const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
1234
  const itemPrice = buttonContainer.getAttribute('data-item-price');
1235
  const itemImage = buttonContainer.getAttribute('data-item-image');
1236
+
1237
  document.getElementById('soft-drink-name').textContent = itemName;
1238
  document.getElementById('soft-drink-price').textContent = `$${itemPrice}`;
1239
  document.getElementById('soft-drink-quantity').value = '1';
1240
+ const softDrinkImage = document.getElementById('soft-drink-image');
1241
+ softDrinkImage.src = itemImage || '/static/placeholder.jpg';
1242
+
1243
  const modal = new bootstrap.Modal(document.getElementById('softDrinkModal'));
1244
  modal.show();
1245
  }
1246
 
1247
  function addSoftDrinkToCart() {
1248
+ if (!currentSoftDrinkButton) return;
1249
+
1250
  const buttonContainer = currentSoftDrinkButton.closest('.button-container');
1251
  const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
1252
+
 
 
 
 
1253
  const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
1254
  const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
1255
  const itemImage = buttonContainer.getAttribute('data-item-image');
1256
  const section = sanitizeInput(buttonContainer.getAttribute('data-item-section'));
1257
  const selectedCategory = sanitizeInput(buttonContainer.getAttribute('data-item-category'));
1258
+
1259
  const cartPayload = {
1260
  itemName: itemName,
1261
  itemPrice: itemPrice,
 
1264
  category: selectedCategory,
1265
  addons: [],
1266
  instructions: '',
1267
+ quantity: quantity
 
1268
  };
1269
+
1270
  fetch('/cart/add', {
1271
  method: 'POST',
1272
+ headers: {
1273
+ 'Content-Type': 'application/json',
1274
+ },
1275
  body: JSON.stringify(cartPayload)
1276
  })
1277
  .then(response => response.json())
 
1279
  if (data.success) {
1280
  alert('Item added to cart successfully!');
1281
  updateCartUI(data.cart);
1282
+ const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
1283
+ modal.hide();
1284
  } else {
1285
+ console.error('Failed to add item to cart:', data.error);
1286
+ alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
1287
+ const cart = addToCartLocalStorage(cartPayload);
1288
+ updateCartUI(cart);
1289
+ const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
1290
+ modal.hide();
1291
  }
1292
  })
1293
  .catch(err => {
1294
+ console.error('Error adding item to cart:', err);
1295
  alert('Error adding item to cart. Using local storage as fallback.');
1296
  const cart = addToCartLocalStorage(cartPayload);
1297
  updateCartUI(cart);
1298
+ const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
1299
+ modal.hide();
1300
+ });
1301
+ }
1302
+
1303
+ function updateCartUI(cart) {
1304
+ if (!Array.isArray(cart)) {
1305
+ console.error('Invalid cart data:', cart);
1306
+ return;
1307
+ }
1308
+
1309
+ let totalQuantity = 0;
1310
+ cart.forEach(item => {
1311
+ totalQuantity += item.quantity;
1312
  });
1313
+
1314
+ const cartItemCount = document.getElementById('cart-item-count');
1315
+ if (cartItemCount) {
1316
+ cartItemCount.innerText = totalQuantity;
1317
+ cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
1318
+ }
1319
  }
1320
 
1321
  document.addEventListener('DOMContentLoaded', function () {
1322
+ const avatarContainer = document.querySelector('.avatar-dropdown-container');
1323
+ const dropdownMenu = document.querySelector('#avatarDropdown');
1324
  const avatarIcon = document.getElementById('avatarIcon');
 
1325
  const avatarUpload = document.getElementById('avatarUpload');
1326
  const deleteAvatar = document.getElementById('deleteAvatar');
1327
  const viewAvatar = document.getElementById('viewAvatar');
1328
+ const allMenuItems = document.getElementById('allMenuItems');
1329
+ const menuItemsSubmenu = document.getElementById('menuItemsSubmenu');
1330
 
1331
+ // Load avatar from localStorage if available
1332
  function loadAvatar() {
1333
  const savedAvatar = localStorage.getItem('userAvatar');
1334
  const avatarImage = avatarIcon.querySelector('.avatar-image');
 
1337
  img.src = savedAvatar;
1338
  img.alt = 'User Avatar';
1339
  img.className = 'avatar-image';
1340
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1341
  avatarIcon.innerHTML = '';
1342
  avatarIcon.appendChild(img);
1343
+ ensureAvatarOptions();
1344
+ } else if (!savedAvatar && avatarImage) {
1345
+ avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1346
+ removeAvatarOptions();
1347
  }
1348
  }
1349
 
1350
+ // Ensure avatar options (Delete and View) are present only once
1351
  function ensureAvatarOptions() {
1352
  if (!document.querySelector('#deleteAvatar')) {
1353
  const deleteItem = document.createElement('div');
1354
  deleteItem.className = 'dropdown-item delete-item';
1355
  deleteItem.id = 'deleteAvatar';
1356
+ deleteItem.innerText = 'Delete Image';
1357
  dropdownMenu.insertBefore(deleteItem, dropdownMenu.firstChild);
1358
  addDeleteListener(deleteItem);
1359
  }
 
1361
  const viewItem = document.createElement('div');
1362
  viewItem.className = 'dropdown-item view-item';
1363
  viewItem.id = 'viewAvatar';
1364
+ viewItem.innerText = 'View Avatar';
1365
+ const firstChild = dropdownMenu.firstChild;
1366
+ if (firstChild) dropdownMenu.insertBefore(viewItem, firstChild.nextSibling);
1367
  addViewListener(viewItem);
1368
  }
1369
  }
1370
 
1371
+ // Remove avatar options
1372
  function removeAvatarOptions() {
1373
  const deleteItem = document.getElementById('deleteAvatar');
1374
  const viewItem = document.getElementById('viewAvatar');
 
1376
  if (viewItem) viewItem.remove();
1377
  }
1378
 
1379
+ // Save avatar to localStorage
1380
  function saveAvatar(imageUrl) {
1381
  localStorage.setItem('userAvatar', imageUrl);
1382
  }
1383
 
1384
+ // Clear avatar from localStorage
1385
  function clearAvatar() {
1386
  localStorage.removeItem('userAvatar');
1387
  avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1388
  removeAvatarOptions();
1389
  }
1390
 
1391
+ // Avatar click handler
1392
  avatarIcon.addEventListener('click', function(event) {
1393
  event.stopPropagation();
1394
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1395
  });
1396
 
1397
+ // Show/hide submenu on hover for "All Menu Items"
1398
+ allMenuItems.addEventListener('mouseenter', function() {
1399
+ menuItemsSubmenu.style.display = 'block';
1400
+ });
1401
+ allMenuItems.addEventListener('mouseleave', function() {
1402
+ setTimeout(() => {
1403
+ if (!menuItemsSubmenu.matches(':hover') && !allMenuItems.matches(':hover')) {
1404
+ menuItemsSubmenu.style.display = 'none';
1405
+ }
1406
+ }, 100);
1407
+ });
1408
+ menuItemsSubmenu.addEventListener('mouseenter', function() {
1409
+ menuItemsSubmenu.style.display = 'block';
1410
+ });
1411
+ menuItemsSubmenu.addEventListener('mouseleave', function() {
1412
+ if (!allMenuItems.matches(':hover')) {
1413
+ menuItemsSubmenu.style.display = 'none';
1414
+ }
1415
+ });
1416
+
1417
+ // Handle image upload with 15MB limit and error handling
1418
  avatarUpload.addEventListener('change', function(event) {
1419
  const file = event.target.files[0];
1420
  if (file) {
1421
+ const maxSize = 15 * 1024 * 1024; // 15MB in bytes
1422
  if (file.size > maxSize) {
1423
  alert('Image size must not exceed 15MB.');
1424
  this.value = '';
 
1429
  const base64Image = e.target.result;
1430
  fetch('/upload_avatar', {
1431
  method: 'POST',
1432
+ headers: {
1433
+ 'Content-Type': 'application/json',
1434
+ },
1435
  body: JSON.stringify({ image: base64Image })
1436
  })
1437
+ .then(response => {
1438
+ if (!response.ok) throw new Error('Upload failed');
1439
+ return response.json();
1440
+ })
1441
  .then(data => {
1442
  if (data.success) {
1443
  const img = document.createElement('img');
1444
+ img.src = data.image || '/static/default-avatar.jpg';
1445
  img.alt = 'User Avatar';
1446
  img.className = 'avatar-image';
1447
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1448
  avatarIcon.innerHTML = '';
1449
  avatarIcon.appendChild(img);
1450
  saveAvatar(data.image);
1451
  ensureAvatarOptions();
1452
  dropdownMenu.style.display = 'none';
1453
  } else {
1454
+ alert('Failed to upload image: ' + (data.error || 'Unknown error. Using default image.'));
1455
+ const img = document.createElement('img');
1456
+ img.src = '/static/default-avatar.jpg';
1457
+ img.alt = 'User Avatar';
1458
+ img.className = 'avatar-image';
1459
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1460
+ avatarIcon.innerHTML = '';
1461
+ avatarIcon.appendChild(img);
1462
+ saveAvatar('/static/default-avatar.jpg');
1463
+ ensureAvatarOptions();
1464
+ dropdownMenu.style.display = 'none';
1465
  }
1466
  })
1467
  .catch(error => {
1468
+ console.error('Upload error:', error);
1469
+ alert('Error uploading image. Using default image as fallback.');
1470
  const img = document.createElement('img');
1471
  img.src = '/static/default-avatar.jpg';
1472
  img.alt = 'User Avatar';
1473
  img.className = 'avatar-image';
1474
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1475
  avatarIcon.innerHTML = '';
1476
  avatarIcon.appendChild(img);
1477
  saveAvatar('/static/default-avatar.jpg');
 
1479
  dropdownMenu.style.display = 'none';
1480
  });
1481
  };
1482
+ reader.onerror = function() {
1483
+ alert('Error reading the image file. Using default image as fallback.');
1484
+ const img = document.createElement('img');
1485
+ img.src = '/static/default-avatar.jpg';
1486
+ img.alt = 'User Avatar';
1487
+ img.className = 'avatar-image';
1488
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1489
+ avatarIcon.innerHTML = '';
1490
+ avatarIcon.appendChild(img);
1491
+ saveAvatar('/static/default-avatar.jpg');
1492
+ ensureAvatarOptions();
1493
+ dropdownMenu.style.display = 'none';
1494
+ };
1495
  reader.readAsDataURL(file);
1496
  }
1497
  });
1498
 
1499
+ // Handle image deletion
1500
  function addDeleteListener(deleteElement) {
1501
  deleteElement.addEventListener('click', function() {
1502
  fetch('/delete_avatar', {
1503
  method: 'POST',
1504
+ headers: {
1505
+ 'Content-Type': 'application/json'
1506
+ }
1507
+ })
1508
+ .then(response => {
1509
+ if (!response.ok) throw new Error('Delete failed');
1510
+ return response.json();
1511
  })
 
1512
  .then(data => {
1513
  if (data.success) {
1514
  clearAvatar();
1515
  dropdownMenu.style.display = 'none';
1516
  } else {
1517
+ alert('Failed to delete image: ' + (data.error || 'Unknown error'));
1518
  }
1519
  })
1520
  .catch(error => {
1521
+ console.error('Delete error:', error);
1522
+ alert('Error deleting image. Please try again.');
 
1523
  });
1524
  });
1525
  }
1526
 
1527
+ // Handle avatar view
1528
  function addViewListener(viewElement) {
1529
  viewElement.addEventListener('click', function() {
1530
  const avatarImage = avatarIcon.querySelector('.avatar-image');
1531
  if (avatarImage) {
1532
+ const modalImg = document.getElementById('avatar-modal-img');
1533
+ modalImg.src = avatarImage.src;
1534
  const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
1535
  modal.show();
1536
+ } else {
1537
+ alert('No avatar image to view.');
1538
  }
1539
+ dropdownMenu.style.display = 'none';
1540
  });
1541
  }
1542
 
1543
+ // Add listeners if elements exist
1544
  if (deleteAvatar) addDeleteListener(deleteAvatar);
1545
  if (viewAvatar) addViewListener(viewAvatar);
1546
+
1547
+ // Load avatar on page load
1548
  loadAvatar();
1549
 
1550
+ // Close dropdown when clicking outside
1551
  document.addEventListener('click', function(event) {
1552
+ if (!avatarContainer.contains(event.target)) {
1553
  dropdownMenu.style.display = 'none';
1554
+ menuItemsSubmenu.style.display = 'none';
1555
  }
1556
  });
1557
 
1558
  const menuCards = document.querySelectorAll('.menu-card');
1559
  const menuVideos = document.querySelectorAll('.menu-video');
1560
+ const cardObserver = new IntersectionObserver((entries, observer) => {
1561
  entries.forEach(entry => {
1562
  if (entry.isIntersecting) {
1563
  entry.target.classList.add('visible');
1564
+ observer.unobserve(entry.target);
1565
  }
1566
  });
1567
+ }, {
1568
+ root: null,
1569
+ rootMargin: '0px',
1570
+ threshold: 0.1
1571
+ });
1572
+ const videoObserver = new IntersectionObserver((entries, observer) => {
1573
  entries.forEach(entry => {
1574
  if (entry.isIntersecting) {
1575
  const video = entry.target;
 
1580
  video.load();
1581
  }
1582
  video.classList.add('loaded');
1583
+ observer.unobserve(video);
1584
  }
1585
  });
1586
+ }, {
1587
+ root: null,
1588
+ rootMargin: '200px',
1589
+ threshold: 0.01
1590
+ });
1591
  menuCards.forEach(card => cardObserver.observe(card));
1592
  menuVideos.forEach(video => videoObserver.observe(video));
1593
 
1594
  const toggleLinks = document.querySelectorAll('.toggle-details');
1595
  toggleLinks.forEach(link => {
1596
+ link.addEventListener('click', function () {
1597
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1598
  const detailsDiv = document.getElementById(`details-${itemName}`);
1599
  const isCurrentlyShown = detailsDiv.classList.contains('show');
1600
+
1601
  document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1602
  if (otherDetails !== detailsDiv) {
1603
  otherDetails.classList.remove('show');
1604
  const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1605
+ if (otherLink) {
1606
+ otherLink.innerText = 'Show Details';
1607
+ }
1608
  }
1609
  });
1610
+
1611
  if (!isCurrentlyShown) {
1612
  detailsDiv.classList.add('show');
1613
+ this.innerText = 'Hide Details';
 
1614
  } else {
1615
  detailsDiv.classList.remove('show');
1616
+ this.innerText = 'Show Details';
 
1617
  }
1618
  });
1619
  });
 
1623
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
1624
  if (!selectedCategoryInput.value) {
1625
  selectedCategoryInput.value = "All";
1626
+ document.querySelector('.category-button[data-category="All"]').classList.add('selected');
 
1627
  }
1628
  categoryButtons.forEach(button => {
1629
+ button.addEventListener('click', function () {
1630
  categoryButtons.forEach(btn => btn.classList.remove('selected'));
1631
  this.classList.add('selected');
1632
  selectedCategoryInput.value = this.getAttribute('data-category');
 
1636
 
1637
  const searchBar = document.getElementById('searchBar');
1638
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1639
+ const debouncedFilterMenu = debounce(filterMenu, 300);
1640
+
1641
+ // Redirect to search page on click
1642
+ searchBar.addEventListener('click', function (event) {
1643
+ event.stopPropagation();
1644
+ window.location.href = '/search';
1645
+ });
1646
+
1647
+ searchBar.addEventListener('input', function () {
1648
  const input = sanitizeInput(this.value.trim().toLowerCase());
1649
  suggestionsContainer.innerHTML = '';
1650
  suggestionsContainer.style.display = 'none';
1651
  if (input) {
1652
+ const filteredItems = menuItems.filter(item =>
1653
+ item.toLowerCase().includes(input)
1654
+ );
1655
  if (filteredItems.length > 0) {
1656
  filteredItems.forEach(item => {
1657
  const suggestionDiv = document.createElement('div');
1658
  suggestionDiv.classList.add('suggestion-item');
1659
+ suggestionDiv.innerText = item;
1660
+ suggestionDiv.addEventListener('click', function () {
1661
  searchBar.value = item;
1662
  suggestionsContainer.style.display = 'none';
1663
+ debouncedFilterMenu();
1664
  });
1665
  suggestionsContainer.appendChild(suggestionDiv);
1666
  });
1667
  suggestionsContainer.style.display = 'block';
1668
  }
1669
  }
1670
+ debouncedFilterMenu();
1671
  });
1672
+
1673
+ document.addEventListener('click', function (event) {
1674
  if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
1675
  suggestionsContainer.style.display = 'none';
1676
  }
1677
  });
1678
 
1679
+ const descriptionTextarea = document.getElementById('custom-dish-description');
1680
+ const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1681
+ if (descriptionTextarea && descriptionSuggestions) {
 
1682
  let usedIngredients = new Set();
 
1683
  function updateUsedIngredients() {
1684
  const inputText = descriptionTextarea.value.trim();
1685
  usedIngredients.clear();
1686
  if (inputText) {
1687
  const words = inputText.split(/,\s*/).map(word => word.trim());
1688
  words.forEach(word => {
1689
+ if (word && ingredientsList.includes(word)) {
1690
  usedIngredients.add(word);
1691
  }
1692
  });
1693
  }
1694
  }
1695
+ descriptionTextarea.addEventListener('input', function () {
 
1696
  const inputText = this.value.trim();
1697
  const words = inputText.split(/,\s*/);
1698
  const lastWord = words[words.length - 1].trim().toLowerCase();
 
1707
  filteredIngredients.forEach(ingredient => {
1708
  const suggestionDiv = document.createElement('div');
1709
  suggestionDiv.classList.add('suggestion-item');
1710
+ suggestionDiv.innerText = ingredient;
1711
+ suggestionDiv.addEventListener('click', function () {
1712
  const currentValue = descriptionTextarea.value;
1713
  const lastCommaIndex = currentValue.lastIndexOf(',');
1714
  const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
 
1723
  }
1724
  }
1725
  });
1726
+ document.addEventListener('click', function (event) {
 
1727
  if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1728
  descriptionSuggestions.style.display = 'none';
1729
  }
1730
  });
1731
+ }
1732
 
1733
+ const customDishForm = document.getElementById('customDishForm');
1734
+ if (customDishForm) {
1735
  customDishForm.addEventListener('submit', function(event) {
1736
+ const dishName = document.getElementById('custom-dish-name').value.trim();
1737
+ const description = document.getElementById('custom-dish-description').value.trim();
 
1738
  if (!dishName || !description) {
1739
+ event.preventDefault();
1740
  alert('Please fill in both the dish name and description.');
1741
  return;
1742
  }
1743
+ event.preventDefault();
1744
  fetch('/customdish/generate_custom_dish', {
1745
  method: 'POST',
1746
+ headers: {
1747
+ 'Content-Type': 'application/x-www-form-urlencoded',
1748
+ },
1749
+ body: new URLSearchParams({
1750
+ 'name': dishName,
1751
+ 'description': description
1752
+ })
1753
  })
1754
  .then(response => response.json())
1755
  .then(data => {
 
1757
  alert('Custom dish submitted successfully!');
1758
  window.location.reload();
1759
  } else {
1760
+ alert('Failed to submit custom dish: ' + (data.error || 'Unknown error'));
1761
  }
1762
  })
1763
  .catch(error => {
1764
+ console.error('Error submitting custom dish:', error);
1765
+ alert('Error submitting custom dish. Please try again.');
1766
  });
1767
  });
1768
  }
1769
 
1770
+ fetch('/cart/get')
1771
+ .then(response => {
1772
+ if (!response.ok) {
1773
+ throw new Error(`HTTP error! Status: ${response.status}`);
1774
+ }
1775
+ return response.json();
1776
+ })
1777
+ .then(data => {
1778
+ if (data.success) {
1779
+ updateCartUI(data.cart);
1780
+ } else {
1781
+ console.error('Failed to fetch cart:', data.error);
1782
+ const cart = getCartLocalStorage();
1783
+ updateCartUI(cart);
1784
+ }
1785
+ })
1786
+ .catch(err => {
1787
+ console.error('Error fetching cart:', err);
1788
+ const cart = getCartLocalStorage();
1789
+ updateCartUI(cart);
1790
+ });
1791
+
1792
+ const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
1793
+ preloadedVideos.forEach(link => {
1794
+ const video = document.createElement('video');
1795
+ video.src = link.href;
1796
+ video.preload = 'auto';
1797
+ });
1798
+
1799
+ const decreaseBtn = document.getElementById('decreaseQuantity');
1800
+ const increaseBtn = document.getElementById('increaseQuantity');
1801
+ const quantityInput = document.getElementById('quantityInput');
1802
+ decreaseBtn.addEventListener('click', function () {
1803
+ let currentQuantity = parseInt(quantityInput.value);
1804
+ if (currentQuantity > 1) {
1805
+ currentQuantity--;
1806
+ quantityInput.value = currentQuantity;
1807
+ }
1808
+ });
1809
+ increaseBtn.addEventListener('click', function () {
1810
+ let currentQuantity = parseInt(quantityInput.value);
1811
+ currentQuantity++;
1812
+ quantityInput.value = currentQuantity;
1813
+ });
1814
+
1815
+ const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
1816
+ const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
1817
+ const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
1818
+
1819
+ softDrinkDecreaseBtn.addEventListener('click', function() {
1820
+ let currentQuantity = parseInt(softDrinkQuantityInput.value);
1821
+ if (currentQuantity > 1) {
1822
+ currentQuantity--;
1823
+ softDrinkQuantityInput.value = currentQuantity;
1824
+ }
1825
+ });
1826
+
1827
+ softDrinkIncreaseBtn.addEventListener('click', function() {
1828
+ let currentQuantity = parseInt(softDrinkQuantityInput.value);
1829
+ if (currentQuantity < 1000) {
1830
+ currentQuantity++;
1831
+ softDrinkQuantityInput.value = currentQuantity;
1832
+ }
1833
+ });
1834
+
1835
+ // Mic Popup Functionality
1836
  const micIcon = document.getElementById('micIcon');
1837
+ const micUnsupported = document.getElementById('micUnsupported');
1838
  const micPopup = document.getElementById('micPopup');
1839
  const micPopupText = document.getElementById('micPopupText');
1840
  const micPopupCancel = document.getElementById('micPopupCancel');
1841
+
1842
+ if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
 
1843
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1844
  const recognition = new SpeechRecognition();
1845
  recognition.continuous = false;
1846
  recognition.interimResults = true;
1847
  recognition.lang = 'en-US';
1848
+
1849
+ recognition.onstart = () => {
1850
+ micIcon.classList.add('active');
1851
  micPopup.classList.add('active');
 
 
 
 
1852
  micPopupText.textContent = 'Listening...';
 
1853
  };
1854
+
1855
+ recognition.onresult = (event) => {
1856
  let interimTranscript = '';
1857
  let finalTranscript = '';
1858
+
1859
  for (let i = event.resultIndex; i < event.results.length; i++) {
1860
  const transcript = event.results[i][0].transcript;
1861
  if (event.results[i].isFinal) {
 
1864
  interimTranscript += transcript;
1865
  }
1866
  }
1867
+
1868
  if (interimTranscript) {
1869
  micPopupText.textContent = interimTranscript;
1870
  }
1871
+
1872
  if (finalTranscript) {
1873
  searchBar.value = sanitizeInput(finalTranscript.trim());
1874
+ debouncedFilterMenu();
1875
  micPopup.classList.remove('active');
 
1876
  }
1877
  };
1878
+
1879
+ recognition.onend = () => {
1880
+ micIcon.classList.remove('active');
1881
+ if (micPopup.classList.contains('active')) {
1882
+ setTimeout(() => {
1883
+ micPopup.classList.remove('active');
1884
+ }, 1000);
1885
+ }
1886
+ };
1887
+
1888
+ recognition.onerror = (event) => {
1889
+ micIcon.classList.remove('active');
1890
+ micPopupText.textContent = 'Error: ' + event.error;
1891
  setTimeout(() => {
1892
  micPopup.classList.remove('active');
 
1893
  }, 2000);
1894
+ console.error('Speech error:', event.error);
1895
  };
1896
+
1897
+ micIcon.addEventListener('click', () => {
1898
+ try {
1899
+ recognition.start();
1900
+ } catch (e) {
1901
+ micPopupText.textContent = 'Error starting microphone';
1902
  setTimeout(() => {
1903
  micPopup.classList.remove('active');
1904
+ }, 2000);
1905
+ console.error('Recognition start error:', e);
1906
  }
1907
+ });
1908
+
1909
+ micPopupCancel.addEventListener('click', () => {
1910
  recognition.stop();
1911
  micPopup.classList.remove('active');
1912
  micIcon.classList.remove('active');
 
1916
  micUnsupported.style.display = 'block';
1917
  }
1918
 
1919
+ // Highlight and open modal for item from search redirect
1920
+ const highlightItem = "{{ highlight_item | e }}";
1921
+ if (highlightItem) {
1922
+ const itemElement = document.querySelector(`[data-item-name="${highlightItem}"]`);
1923
+ if (itemElement) {
1924
+ itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
1925
+ itemElement.style.backgroundColor = '#FFFFE0'; // Light yellow highlight
1926
+ setTimeout(() => {
1927
+ itemElement.style.backgroundColor = ''; // Remove highlight after 2 seconds
1928
+ }, 2000);
 
 
 
 
 
 
 
 
 
1929
 
1930
+ // Find the ADD button and trigger the appropriate modal
1931
+ const addButton = itemElement.querySelector('.btn-primary');
1932
+ const buttonContainer = addButton.closest('.button-container');
1933
+ const section = buttonContainer.getAttribute('data-item-section');
1934
 
1935
+ if (section === 'Soft Drinks') {
1936
+ showSoftDrinkModal(addButton);
1937
+ } else {
1938
+ const name = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
1939
+ const price = buttonContainer.getAttribute('data-item-price');
1940
+ const image = buttonContainer.getAttribute('data-item-image');
1941
+ const category = buttonContainer.getAttribute('data-item-category');
1942
+ // Use data from the button's onclick attribute or fetch from server
1943
+ const onclickAttr = addButton.getAttribute('onclick');
1944
+ if (onclickAttr) {
1945
+ const argsMatch = onclickAttr.match(/showItemDetails\((.*?)\)/);
1946
+ if (argsMatch) {
1947
+ const args = argsMatch[1].split(',').map(arg => arg.trim().replace(/['"]/g, ''));
1948
+ const [name, price, image, description, ingredients, nutrition, allergens, section, category] = args;
1949
+ showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, category);
1950
+ }
1951
+ } else {
1952
+ // Fallback if onclick isn't sufficient
1953
+ showItemDetails(name, price, image, 'No description available', 'Not specified', 'Not available', 'None listed', section, category);
1954
+ }
1955
  }
1956
+ }
1957
+ }
 
 
1958
  });
1959
+ function filterMenu() {
 
1960
  const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
1961
  const sections = document.querySelectorAll('h3');
1962
  const items = document.querySelectorAll('.menu-card');
 
1964
  items.forEach(item => {
1965
  const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1966
  const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1967
+ if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
1968
  item.style.display = 'block';
1969
  item.classList.add('visible');
1970
  matchedSections.add(item.closest('.row'));
 
1995
  }
1996
 
1997
  function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
1998
+ document.getElementById('modal-name').innerText = name;
1999
+ document.getElementById('modal-price').innerText = `$${price}`;
2000
+ const modalImg = document.getElementById('modal-img');
2001
+ modalImg.src = image || '/static/placeholder.jpg';
2002
+ document.getElementById('modal-description').innerText = description || 'No description available.';
2003
+ document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
2004
+ document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
2005
+ document.getElementById('modal-allergens').innerText = allergens || 'None listed';
2006
+ document.getElementById('addons-list').innerHTML = 'Loading customization options...';
 
2007
  document.getElementById('modal-instructions').value = '';
2008
+ const modalSectionEl = document.getElementById('modal-section');
2009
+ modalSectionEl.setAttribute('data-section', section);
2010
+ modalSectionEl.setAttribute('data-category', selectedCategory);
2011
+ document.getElementById('quantityInput').value = 1;
2012
 
2013
+ fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
 
 
 
2014
  .then(response => response.json())
2015
  .then(data => {
2016
+ const addonsList = document.getElementById('addons-list');
2017
  addonsList.innerHTML = '';
2018
  if (!data.success || !data.addons || data.addons.length === 0) {
2019
  addonsList.innerHTML = '<p>No customization options available.</p>';
 
2021
  }
2022
  data.addons.forEach(addon => {
2023
  const sectionDiv = document.createElement('div');
2024
+ sectionDiv.classList.add('addon-section');
2025
+ const title = document.createElement('h6');
2026
+
2027
+ title.innerText = addon.name;
2028
+ sectionDiv.appendChild(title);
2029
  const optionsContainer = document.createElement('div');
2030
  addon.options.forEach((option, index) => {
2031
  const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
2032
  const listItem = document.createElement('div');
2033
+ listItem.classList.add('form-check');
2034
  listItem.innerHTML = `
2035
  <input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
2036
  data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
 
2044
  addonsList.appendChild(sectionDiv);
2045
  });
2046
  })
2047
+ .catch(err => {
2048
+ console.error('Error fetching add-ons:', err);
2049
+ document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
2050
  });
2051
  }
2052
 
 
2068
  }
2069
 
2070
  function addToCartFromModal() {
2071
+ const itemName = document.getElementById('modal-name').innerText;
2072
+ const itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
 
 
2073
  const itemImage = document.getElementById('modal-img').src;
2074
+ const instructions = document.getElementById('modal-instructions').value.trim();
 
2075
  const quantity = parseInt(document.getElementById('quantityInput').value);
2076
+ const section = document.getElementById('modal-section').getAttribute('data-section');
2077
+ const selectedCategory = document.getElementById('modal-section').getAttribute('data-category');
 
 
 
 
2078
  const addons = Array.from(document.querySelectorAll('.addon-option:checked')).map(cb => ({
2079
  name: cb.getAttribute('data-name'),
2080
  price: parseFloat(cb.getAttribute('data-price') || 0)
2081
  }));
2082
+
2083
  const cartPayload = {
2084
  itemName: itemName,
2085
  itemPrice: itemPrice,
2086
  itemImage: itemImage,
2087
  section: section,
2088
+ category: selectedCategory,
2089
  addons: addons,
2090
  instructions: instructions,
2091
+ quantity: quantity
 
2092
  };
2093
+
2094
  fetch('/cart/add', {
2095
  method: 'POST',
2096
+ headers: {
2097
+ 'Content-Type': 'application/json',
2098
+ },
2099
  body: JSON.stringify(cartPayload)
2100
  })
2101
  .then(response => response.json())
 
2103
  if (data.success) {
2104
  alert('Item added to cart successfully!');
2105
  updateCartUI(data.cart);
2106
+ const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
2107
+ modal.hide();
2108
  } else {
2109
+ console.error('Failed to add item to cart:', data.error);
2110
+ alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
2111
+ const cart = addToCartLocalStorage(cartPayload);
2112
+ updateCartUI(cart);
2113
+ const modal = document.getElementById('itemModal');
2114
+ const modalInstance = bootstrap.Modal.getInstance(modal);
2115
+ modalInstance.hide();
2116
  }
2117
  })
2118
  .catch(err => {
2119
+ console.error('Error adding item to cart:', err);
2120
  alert('Error adding item to cart. Using local storage as fallback.');
2121
  const cart = addToCartLocalStorage(cartPayload);
2122
  updateCartUI(cart);
2123
+ const modal = document.getElementById('itemModal');
2124
+ const modalInstance = bootstrap.Modal.getInstance(modal);
2125
+ modalInstance.hide();
 
2126
  });
2127
  }
2128
  </script>
2129
  </body>
2130
+ </html>
2131
+