(function() { 'use strict'; var BASE_URL = 'https://orderingwebsite.io'; var RESTAURANT_ID = '6936f5f2b75a14aac7c87e14'; var DISPLAY_MODE = 'button'; var EMBEDDED_RESTAURANT = {"name":"Bella Isabella","description":"","logo_url":"","cover_image_url":"","cuisine_type":"italian","address":"1 zone 1, 246 Barangay Palina E Rd, Urdaneta City, Pangasinan, Philippines","location":{"lat":15.9509355,"lng":120.5551862},"phone":" 09917049488​","email":"jeffblyth1959@gmail.com","currency":"PHP","timezone":"UTC","delivery_hours":{"monday":{"closed":false,"shifts":[{"open":"21:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"tuesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"wednesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"thursday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"friday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"saturday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"sunday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]}},"pickup_hours":{"monday":{"closed":false,"shifts":[{"open":"21:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"tuesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"wednesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"thursday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"friday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"saturday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"sunday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]}},"dine_in_hours":{"monday":{"closed":false,"shifts":[{"open":"21:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"tuesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"wednesday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"thursday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"friday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"saturday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]},"sunday":{"closed":false,"shifts":[{"open":"09:30","close":"12:30"},{"open":"03:30","close":"21:30"}]}},"delivery_enabled":true,"delivery_disabled_until":null,"pickup_enabled":true,"pickup_disabled_until":null,"dine_in_enabled":false,"dine_in_disabled_until":"2026-01-01T23:59:59.999Z","dine_in_ordering_mode":"online_menu_only","reservations_enabled":false,"delivery_minimum_order":0,"pickup_minimum_order":0,"dine_in_minimum_order":0,"max_delivery_distance":10,"delivery_fee":5,"delivery_fee_method":"distance_based","delivery_rate_per_mile":1,"delivery_base_fee":0,"delivery_tiers":[{"min_distance":0,"max_distance":1.8,"fee":60},{"min_distance":1.8,"max_distance":3.7,"fee":100}],"use_tiered_delivery":true,"delivery_zones":[],"estimated_delivery_time":30,"estimated_pickup_time":15,"estimated_dine_in_time":20,"other_fees":{"enabled":false,"items":[]},"tips_settings":{"enabled":true,"delivery_enabled":true,"pickup_enabled":true,"dine_in_enabled":true,"percentage_enabled":true,"flat_amount_enabled":true,"default_percentages":[10,15,20,25],"default_flat_amounts":[2,5,10]},"vat_settings":{"enabled":false,"rate":0,"price_inclusive":false,"applies_to_products":true,"applies_to_fees":false,"applies_to_delivery":false,"applies_to_tips":false},"customer_print_template":{"include_logo":false,"include_qr_code":true,"include_restaurant_info":true,"include_customer_info":true,"include_special_notes":true,"include_header":false,"include_footer":true,"include_order_id":true,"include_order_type":true,"include_requested_time":true,"include_accepted_time":true,"include_expected_time":true,"include_payment_status":true,"include_payment_method":true,"include_distance":true,"include_travel_time":true,"include_items":true,"include_totals":true,"include_placed_at":true,"include_not_paid_stamp":true,"include_scheduled_stamp":true,"not_paid_stamp_size":"medium","scheduled_stamp_size":"medium","paper_size":"80mm","header_font_size":"large","order_info_font_size":"medium","customer_info_font_size":"medium","special_notes_font_size":"medium","items_font_size":"large","item_options_font_size":"large","item_notes_font_size":"large","totals_font_size":"medium","footer_font_size":"small","order_id_font_size":"medium","order_type_font_size":"medium","requested_time_font_size":"medium","accepted_time_font_size":"medium","expected_time_font_size":"medium","payment_status_font_size":"medium","payment_method_font_size":"medium","distance_font_size":"medium","travel_time_font_size":"medium","restaurant_info_font_size":"medium","placed_at_font_size":"medium","header_text":"","footer_text":"Thank you for your order!","section_order":["logo","header","restaurant_info","order_info","times","payment","distance","customer_info","special_notes","items","totals","qr_code","footer"]},"kitchen_print_template":{"include_logo":false,"include_qr_code":true,"include_restaurant_info":true,"include_customer_info":true,"include_special_notes":true,"include_special_instructions":true,"include_header":false,"include_footer":true,"include_order_id":true,"include_order_type":true,"include_requested_time":true,"include_accepted_time":true,"include_expected_time":true,"include_payment_status":true,"include_payment_method":true,"include_distance":true,"include_travel_time":true,"include_items":true,"include_totals":true,"include_placed_at":true,"include_not_paid_stamp":true,"include_scheduled_stamp":true,"not_paid_stamp_size":"medium","scheduled_stamp_size":"medium","paper_size":"80mm","header_font_size":"large","order_info_font_size":"medium","customer_info_font_size":"medium","special_notes_font_size":"large","items_font_size":"xlarge","item_options_font_size":"large","item_notes_font_size":"xlarge","totals_font_size":"medium","footer_font_size":"small","order_id_font_size":"medium","order_type_font_size":"medium","requested_time_font_size":"medium","accepted_time_font_size":"medium","expected_time_font_size":"medium","payment_status_font_size":"medium","payment_method_font_size":"medium","distance_font_size":"medium","travel_time_font_size":"medium","restaurant_info_font_size":"medium","placed_at_font_size":"medium","header_text":"","footer_text":"","section_order":["logo","header","restaurant_info","order_info","times","payment","distance","customer_info","special_notes","items","totals","qr_code","footer"]},"dietary_options":[],"specialties":[],"amenities":[],"average_rating":0,"total_reviews":0,"price_range":"$$","payment_gateways":{"stripe":{"enabled":false,"publishable_key":"","secret_key":"","webhook_secret":"","mode":"test","reservations":true,"delivery":true,"pickup":true,"dine_in":true},"paypal":{"enabled":false,"client_id":"","client_secret":"","mode":"sandbox","reservations":true,"delivery":true,"pickup":true,"dine_in":true},"square":null,"gocardless":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"worldpay":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"revolut":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"verifone":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"wonderful":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"razorpay":{"enabled":false,"key_id":"","key_secret":"","reservations":true,"delivery":true,"pickup":true,"dine_in":true},"alipay":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"airwallex":{"enabled":false,"access_token":"","environment":"sandbox","reservations":false,"delivery":true,"pickup":true,"dine_in":true},"pay_cash_on_pickup":{"enabled":true,"pickup":true,"dine_in":true,"reservations":false},"pay_card_on_pickup":{"enabled":true,"pickup":true,"dine_in":true,"reservations":false},"pay_cash_on_delivery":{"enabled":true},"pay_card_on_delivery":{"enabled":true}},"max_party_size":10,"advance_booking_days":30,"reservation_deposit":5,"require_online_reservation_deposit":true,"booking_fee_enabled":true,"booking_fee_amount":5,"table_time_limit":90,"auto_confirm_reservations":false,"reservation_notes":"","reservation_slot_interval":30,"reservation_hours":{"monday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"tuesday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"wednesday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"thursday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"friday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"saturday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]},"sunday":{"closed":false,"shifts":[{"open":"11:00","close":"22:00"}]}},"floor_plan_design":null,"status":"active","white_label_partner_id":null,"managed_by_partner":false,"theme_header_background_color":"#FFFFFF","theme_header_text_color":"#d52a3b","theme_background_color":"#F8FAFC","theme_text_color":"#1E293B","theme_button_color":"#d52a3b","theme_button_text_color":"#FFFFFF","theme_category_section_background_color":"#FFFFFF","theme_category_background_color":"#ffffff","theme_category_text_color":"#1E293B","theme_item_card_background_color":"#FFFFFF","theme_cart_background_color":"#FFFFFF","reservation_theme_header_background_color":"#FFFFFF","reservation_theme_header_text_color":"#1E293B","reservation_theme_background_color":"#F8FAFC","reservation_theme_text_color":"#1E293B","reservation_theme_button_color":"#FF6B35","reservation_theme_button_text_color":"#FFFFFF","embedder_button_text":"Order Online","embedder_button_color":"#e04242","embedder_button_text_color":"#0f172a","embedder_button_border_radius":20,"reservation_button_text":"Book a Table","reservation_button_color":"#1e3a8a","reservation_button_text_color":"#0f172a","reservation_button_border_radius":20,"id":"6936f5f2b75a14aac7c87e14","created_date":"2025-12-08T15:59:46.499000","updated_date":"2026-01-20T00:32:15.777000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false}; var EMBEDDED_MENU_ITEMS = [{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Soft Drinks 237ml","description":"Small soft drink","image_url":null,"category":"drinks","original_category":null,"price":15,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":20,"options":[],"id":"693ed81723d3d0e0a65ff436","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:08.650000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Drinks Litre","description":"1 liter soft drink","image_url":null,"category":"drinks","original_category":null,"price":45,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":27,"options":[],"id":"693ed81723d3d0e0a65ff437","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:07.671000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Milkshake Cappuccino","description":"Coffee flavored milkshake","image_url":null,"category":"drinks","original_category":null,"price":140,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":13,"options":[],"id":"693ed81723d3d0e0a65ff435","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:07.766000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Milkshake Strawberry","description":"Creamy strawberry milkshake","image_url":null,"category":"drinks","original_category":null,"price":140,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":2,"options":[],"id":"693ed81723d3d0e0a65ff433","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:14.677000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Milkshake Chocolate","description":"Rich chocolate milkshake","image_url":null,"category":"drinks","original_category":null,"price":140,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":6,"options":[],"id":"693ed81723d3d0e0a65ff434","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:08.694000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Red Horse","description":"Red Horse beer","image_url":null,"category":"drinks","original_category":null,"price":65,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":40,"options":[],"id":"693ed81723d3d0e0a65ff43a","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:07.376000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Coffee","description":"Freshly brewed coffee","image_url":null,"category":"drinks","original_category":null,"price":60,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":32,"options":[],"id":"693ed81723d3d0e0a65ff438","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:07.784000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cappuccino","description":"Italian style cappuccino","image_url":null,"category":"drinks","original_category":null,"price":100,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":37,"options":[],"id":"693ed81723d3d0e0a65ff439","created_date":"2025-12-14T15:30:31.545000","updated_date":"2025-12-16T15:40:07.775000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Garlic Bread Mexicana","description":"Served with pizza sauce, onions spicy","image_url":null,"category":"deleted_items","original_category":"starters","price":350,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":21,"options":[],"id":"693ed7dd54067ac0b3ede550","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-17T17:48:03.699000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Meatballs","description":"Oven baked with mozzarella","image_url":null,"category":"starters","original_category":null,"price":250,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":33,"options":[],"id":"693ed7dd54067ac0b3ede552","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-16T15:40:07.741000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cheesy Fries","description":"Oven baked with mozzarella","image_url":null,"category":"starters","original_category":null,"price":140,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":7,"options":[],"id":"693ed7dd54067ac0b3ede54e","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-16T15:40:14.663000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"French Fries","description":"Crispy golden fries","image_url":null,"category":"starters","original_category":null,"price":100,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":0,"options":[],"id":"693ed7dd54067ac0b3ede54d","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-16T15:40:14.615000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Garlic Bread Cheese 14\"","description":"14 inch garlic bread with cheese","image_url":null,"category":"starters","original_category":null,"price":300,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":14,"options":[],"id":"693ed7dd54067ac0b3ede54f","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-16T15:40:07.772000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Chicken Parmigiana","description":"Breaded chicken filet served in tomato sauce, mozzarella and parmesan cheese oven baked with pasta or French fries as a side dish","image_url":null,"category":"deleted_items","original_category":"starters","price":440,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":28,"options":[],"id":"693ed7dd54067ac0b3ede551","created_date":"2025-12-14T15:29:33.492000","updated_date":"2025-12-17T17:48:21.924000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Hamburger","description":"Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"burgers","original_category":null,"price":170,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":1,"options":[],"id":"693ed7ba00cd9b6f7bcf360a","created_date":"2025-12-14T15:28:58.473000","updated_date":"2025-12-16T15:40:14.586000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cheese Burger","description":"Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"deleted_items","original_category":"burgers","price":190,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":8,"options":[],"id":"693ed7ba00cd9b6f7bcf360b","created_date":"2025-12-14T15:28:58.473000","updated_date":"2025-12-17T17:45:29.269000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Double Cheese Burger","description":"Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"burgers","original_category":null,"price":250,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":15,"options":[],"id":"693ed7ba00cd9b6f7bcf360c","created_date":"2025-12-14T15:28:58.473000","updated_date":"2025-12-16T15:40:07.757000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Chicken Burger","description":"Home made chicken burger served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"deleted_items","original_category":"burgers","price":270,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":22,"options":[],"id":"693ed7ba00cd9b6f7bcf360d","created_date":"2025-12-14T15:28:58.473000","updated_date":"2025-12-17T17:45:42.003000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cannelloni","description":"Filled pasta tubes in a meaty sauce","image_url":null,"category":"pasta","original_category":null,"price":300,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":41,"options":[],"id":"693ed78df9645cee78c1837b","created_date":"2025-12-14T15:28:13.672000","updated_date":"2025-12-16T15:40:07.746000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"French Fries","description":"Crispy golden fries","image_url":null,"category":"deleted_items","original_category":"starters","price":100,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":6,"options":[],"id":"693ed7424217b05c7775af0a","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-17T17:47:52.110000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cheesy Fries","description":"Oven baked with mozzarella","image_url":null,"category":"deleted_items","original_category":"starters","price":140,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":9,"options":[],"id":"693ed7424217b05c7775af0b","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-17T17:47:58.570000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Garlic Bread Cheese 14\"","description":"14 inch garlic bread with cheese","image_url":null,"category":"deleted_items","original_category":"starters","price":300,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":16,"options":[],"id":"693ed7424217b05c7775af0c","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-17T17:48:14.785000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Chicken Parmigiana","description":"Breaded chicken filet served in tomato sauce, mozzarella and parmesan cheese oven baked with pasta or French fries as a side dish","image_url":null,"category":"starters","original_category":null,"price":440,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":true,"sort_order":29,"options":[{"name":"Side Dish","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"Pasta","price_modifier":0},{"name":"French Fries","price_modifier":0}]}],"id":"693ed7424217b05c7775af0e","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-16T15:40:07.708000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Garlic Bread Mexicana","description":"Served with pizza sauce, onions - spicy","image_url":null,"category":"starters","original_category":null,"price":350,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":23,"options":[],"id":"693ed7424217b05c7775af0d","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-16T15:40:07.842000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Meatballs","description":"Oven baked with mozzarella","image_url":null,"category":"deleted_items","original_category":"starters","price":250,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":34,"options":[],"id":"693ed7424217b05c7775af0f","created_date":"2025-12-14T15:26:58.069000","updated_date":"2025-12-17T17:48:35.904000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Double Cheese Burger","description":"Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"deleted_items","original_category":"burgers","price":250,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":17,"options":[],"id":"693ed7424217b05c7775af08","created_date":"2025-12-14T15:26:58.043000","updated_date":"2025-12-17T17:45:34.394000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Cheese Burger","description":"Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"burgers","original_category":null,"price":190,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":10,"options":[],"id":"693ed7424217b05c7775af07","created_date":"2025-12-14T15:26:58.043000","updated_date":"2025-12-16T15:40:07.751000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Chicken Burger","description":"Home made - Served with lettuce, tomatoes, French fries and a small soft drink","image_url":null,"category":"burgers","original_category":null,"price":270,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":24,"options":[],"id":"693ed7424217b05c7775af09","created_date":"2025-12-14T15:26:58.043000","updated_date":"2025-12-16T15:40:07.746000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Bolognese with Meat Balls","description":"Bolognese with meatballs","image_url":null,"category":"pasta","original_category":null,"price":350,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":18,"options":[],"id":"693ed7424217b05c7775af00","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:07.829000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Pollo","description":"Served with chicken and creamy sauce","image_url":null,"category":"pasta","original_category":null,"price":350,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":35,"options":[],"id":"693ed7424217b05c7775af03","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:07.822000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Bolognese","description":"Classic meat sauce","image_url":null,"category":"pasta","original_category":null,"price":250,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":11,"options":[],"id":"693ed7424217b05c7775aeff","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:08.771000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Carbonara","description":"Creamy carbonara sauce","image_url":null,"category":"pasta","original_category":null,"price":270,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":25,"options":[],"id":"693ed7424217b05c7775af01","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:07.881000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Lasagne","description":"Layers of pasta in a meaty sauce","image_url":null,"category":"pasta","original_category":null,"price":300,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":38,"options":[],"id":"693ed7424217b05c7775af04","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:07.429000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Napoli","description":"With tomato sauce","image_url":null,"category":"pasta","original_category":null,"price":230,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":3,"options":[],"id":"693ed7424217b05c7775aefe","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:14.697000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Spaghetti Amatriciana","description":"With bacon, onions and tomato sauce","image_url":null,"category":"pasta","original_category":null,"price":270,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":30,"options":[],"id":"693ed7424217b05c7775af02","created_date":"2025-12-14T15:26:58.025000","updated_date":"2025-12-16T15:40:07.840000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Pizza Isabella Special","description":"Double decker pizza in 14\" - Serves 2 to 3 people","image_url":null,"category":"pizza","original_category":null,"price":850,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":true,"sort_order":45,"options":[],"id":"693ed6afcc2395b7d2f42ad5","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.799000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Pizza Tonno","description":"Pizza with tuna and onions (Only available in 14\")","image_url":null,"category":"pizza","original_category":null,"price":450,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":42,"options":[],"id":"693ed6afcc2395b7d2f42ad2","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.796000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Margherita M","description":"Classic pizza with mozzarella and pizza sauce","image_url":null,"category":"pizza","original_category":null,"price":350,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":4,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"12\"","price_modifier":0},{"name":"14\"","price_modifier":30}]}],"id":"693ed6afcc2395b7d2f42acb","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-31T13:26:25.476000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Pepperoni","description":"Classic pepperoni pizza","image_url":null,"category":"pizza","original_category":null,"price":380,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":12,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"12\"","price_modifier":0},{"name":"14\"","price_modifier":30}]}],"id":"693ed6afcc2395b7d2f42acc","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.800000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Meat Feast","description":"Loaded with assorted meats (Only available in 14\")","image_url":null,"category":"pizza","original_category":null,"price":450,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":39,"options":[],"id":"693ed6afcc2395b7d2f42ad1","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.773000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Special Pizza","description":"Ham, Mushrooms, Pepperoni, Onions and Peppers (Only available in 14\")","image_url":null,"category":"pizza","original_category":null,"price":450,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":43,"options":[],"id":"693ed6afcc2395b7d2f42ad3","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.860000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Hawaiian","description":"Hawaiian pizza with ham and pineapple","image_url":null,"category":"pizza","original_category":null,"price":400,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":36,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"12\"","price_modifier":0},{"name":"14\"","price_modifier":20}]}],"id":"693ed6afcc2395b7d2f42ad0","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.719000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Pepperoni & Mushrooms","description":"Pepperoni and mushrooms pizza","image_url":null,"category":"pizza","original_category":null,"price":400,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":26,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"12\"","price_modifier":0},{"name":"14\"","price_modifier":20}]}],"id":"693ed6afcc2395b7d2f42ace","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.690000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Ham & Mushrooms","description":"Ham and mushrooms pizza","image_url":null,"category":"pizza","original_category":null,"price":400,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":31,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":1,"choices":[{"name":"12\"","price_modifier":10},{"name":"14\"","price_modifier":20}]}],"id":"693ed6afcc2395b7d2f42acf","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-31T12:46:10.009000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Ham","description":"Pizza topped with ham","image_url":null,"category":"pizza","original_category":null,"price":380,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":19,"options":[{"name":"Size","type":"single_select","required":true,"min_selections":null,"max_selections":null,"choices":[{"name":"12\"","price_modifier":0},{"name":"14\"","price_modifier":30}]}],"id":"693ed6afcc2395b7d2f42acd","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.741000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false},{"restaurant_id":"6936f5f2b75a14aac7c87e14","name":"Panzarotti","description":"Folded fried pizza (Only available in 14\")","image_url":null,"category":"pizza","original_category":null,"price":480,"preparation_time":null,"available":true,"not_available_until":null,"dietary_tags":[],"popular":false,"sort_order":44,"options":[],"id":"693ed6afcc2395b7d2f42ad4","created_date":"2025-12-14T15:24:31.033000","updated_date":"2025-12-16T15:40:07.806000","created_by_id":"69112422010ec07981f799b8","created_by":"orderingwebsite@gmail.com","is_sample":false}]; var FETCH_ERROR = null; var GOOGLE_MAPS_API_KEY = "AIzaSyAwMyNA5izay3wtbTrYpDPKSHhwUUHQPTk"; var STRIPE_PUBLISHABLE_KEY = null; console.log('[Embedder] Initializing - Mode:', DISPLAY_MODE, 'Restaurant:', RESTAURANT_ID); console.log('[Embedder] Restaurant data:', EMBEDDED_RESTAURANT ? 'loaded' : 'null'); console.log('[Embedder] Menu items:', EMBEDDED_MENU_ITEMS ? EMBEDDED_MENU_ITEMS.length : 0); console.log('[Embedder] Fetch error:', FETCH_ERROR); function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) : '249, 115, 22'; } var styles = ` .base44-menu-button { display: inline-flex !important; align-items: center !important; justify-content: center !important; padding: 12px 24px !important; background: linear-gradient(135deg, #f97316 0%, #fb923c 100%) !important; color: white !important; font-weight: 600 !important; font-size: 16px !important; border: none !important; border-radius: 8px !important; cursor: pointer !important; text-decoration: none !important; transition: all 0.3s ease !important; box-shadow: 0 4px 6px rgba(249, 115, 22, 0.2) !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; line-height: 1.5 !important; text-transform: none !important; letter-spacing: normal !important; } .base44-menu-button:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 12px rgba(249, 115, 22, 0.3) !important; background: linear-gradient(135deg, #ea580c 0%, #f97316 100%) !important; color: white !important; } .base44-menu-button:active { transform: translateY(0) !important; } .base44-menu-button svg { margin-right: 8px !important; width: 20px !important; height: 20px !important; } .base44-modal-overlay { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background: rgba(0, 0, 0, 0.75) !important; display: flex !important; align-items: center !important; justify-content: center !important; z-index: 999999 !important; padding: 0 !important; animation: base44FadeIn 0.3s ease !important; overflow: hidden !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; } .address-autocomplete-suggestions { position: absolute !important; top: 100% !important; left: 0 !important; right: 0 !important; background: #fff !important; border: 2px solid #e2e8f0 !important; border-top: none !important; border-radius: 0 0 10px 10px !important; max-height: 250px !important; overflow-y: auto !important; z-index: 9999999 !important; box-shadow: 0 8px 20px rgba(0,0,0,0.25) !important; margin-top: 2px !important; } .address-suggestion-item { padding: 12px 16px !important; cursor: pointer !important; border-bottom: 1px solid #f1f5f9 !important; transition: background 0.15s !important; } .address-suggestion-item:last-child { border-bottom: none !important; } .address-suggestion-item:hover { background: #f8fafc !important; } .address-suggestion-main { font-weight: 600 !important; color: #1e293b !important; font-size: 14px !important; margin-bottom: 2px !important; } .address-suggestion-secondary { color: #64748b !important; font-size: 12px !important; } .base44-modal-overlay * { box-sizing: border-box !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; } .base44-modal-overlay h1, .base44-modal-overlay h2, .base44-modal-overlay h3, .base44-modal-overlay h4, .base44-modal-overlay h5, .base44-modal-overlay h6 { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; line-height: 1.3 !important; letter-spacing: normal !important; text-transform: none !important; } .base44-modal-overlay p, .base44-modal-overlay span, .base44-modal-overlay div, .base44-modal-overlay label { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; line-height: 1.5 !important; letter-spacing: normal !important; } .base44-modal-overlay button { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; cursor: pointer !important; } .base44-modal-overlay input, .base44-modal-overlay textarea, .base44-modal-overlay select { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; font-size: 16px !important; color: #1e293b !important; background-color: #fff !important; } .base44-modal-overlay input::placeholder, .base44-modal-overlay textarea::placeholder { color: #64748b !important; opacity: 1 !important; } @keyframes base44FadeIn { from { opacity: 0; } to { opacity: 1; } } .base44-modal-container { position: relative !important; width: 100% !important; max-width: 100% !important; height: 100vh !important; background: white !important; border-radius: 0 !important; overflow: hidden !important; box-shadow: none !important; animation: base44SlideUp 0.3s ease !important; display: flex !important; flex-direction: column !important; } @keyframes base44SlideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes base44Pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(213, 42, 59, 0.9); transform: scale(1); } 50% { box-shadow: 0 0 0 16px rgba(213, 42, 59, 0); transform: scale(1.005); } } @keyframes base44NewItemPulse { 0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(213, 42, 59, 0.7); } 50% { transform: scale(1.02); box-shadow: 0 0 20px rgba(213, 42, 59, 0.4); } } .base44-newest-item { animation: base44NewItemPulse 0.5s ease-in-out 8 !important; } .base44-pulse-field { animation: base44Pulse 2s ease-in-out infinite !important; } .base44-modal-header { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 16px 24px !important; background: #FFFFFF !important; color: #d52a3b !important; flex-shrink: 0 !important; } .base44-modal-title { font-size: 20px !important; font-weight: 700 !important; margin: 0 !important; color: #d52a3b !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; flex: 1 !important; min-width: 0 !important; } .base44-modal-close { background: rgba(255, 255, 255, 0.2) !important; border: none !important; color: #d52a3b !important; width: 36px !important; height: 36px !important; border-radius: 8px !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: background 0.2s !important; font-size: 24px !important; line-height: 1 !important; font-weight: 300 !important; } .base44-modal-close:hover { background: rgba(255, 255, 255, 0.3) !important; color: #d52a3b !important; } .base44-modal-content { flex: 1 !important; overflow-y: auto !important; overflow-x: hidden !important; -webkit-overflow-scrolling: touch !important; background: #f8fafc !important; } @media (max-width: 768px) { .base44-modal-container { max-width: 100% !important; width: 100% !important; height: 100vh !important; border-radius: 0 !important; } .base44-modal-header { padding-top: calc(env(safe-area-inset-top) + 16px) !important; padding-top: max(calc(env(safe-area-inset-top) + 16px), 32px) !important; padding-left: 12px !important; padding-right: 12px !important; gap: 8px !important; } .base44-modal-title { font-size: 15px !important; } .base44-modal-track-btn { font-size: 12px !important; padding: 6px 10px !important; } .locate-btn .locate-text { display: none !important; } .locate-btn { padding: 12px !important; } } `; function injectStyles() { if (document.getElementById('base44-embedder-styles')) return; var styleEl = document.createElement('style'); styleEl.id = 'base44-embedder-styles'; styleEl.textContent = styles; document.head.appendChild(styleEl); } // Store the current modal container for the inline menu var currentModalContent = null; var currentOverlay = null; async function createModal(restaurantId, restaurantName) { // Prevent body scroll document.body.style.overflow = 'hidden'; var overlay = document.createElement('div'); overlay.className = 'base44-modal-overlay'; currentOverlay = overlay; var container = document.createElement('div'); container.className = 'base44-modal-container'; var header = document.createElement('div'); header.className = 'base44-modal-header'; var title = document.createElement('h2'); title.className = 'base44-modal-title'; // Use placeholder name initially title.textContent = restaurantName || EMBEDDED_RESTAURANT?.name || 'Order Online'; var headerActions = document.createElement('div'); headerActions.style.cssText = 'display: flex; align-items: center; gap: 12px;'; var trackOrdersBtn = document.createElement('button'); trackOrdersBtn.className = 'base44-modal-track-btn'; var headerTextColor = EMBEDDED_RESTAURANT?.theme_header_text_color || '#1E293B'; trackOrdersBtn.innerHTML = 'My Orders'; trackOrdersBtn.style.cssText = 'background: rgba(255,255,255,0.2); border: none; color: ' + headerTextColor + '; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; transition: background 0.2s; white-space: nowrap; flex-shrink: 0; display: flex; align-items: center;'; trackOrdersBtn.onmouseover = function() { this.style.background = 'rgba(255,255,255,0.3)'; }; trackOrdersBtn.onmouseout = function() { this.style.background = 'rgba(255,255,255,0.2)'; }; trackOrdersBtn.onclick = function() { openAccount(); }; var closeBtn = document.createElement('button'); closeBtn.className = 'base44-modal-close'; closeBtn.innerHTML = '×'; closeBtn.onclick = function() { closeModal(); }; header.appendChild(title); headerActions.appendChild(trackOrdersBtn); headerActions.appendChild(closeBtn); header.appendChild(headerActions); var content = document.createElement('div'); content.className = 'base44-modal-content'; content.id = 'base44-modal-inline-menu'; content.innerHTML = '
⏳ Loading latest menu...
'; currentModalContent = content; container.appendChild(header); container.appendChild(content); overlay.appendChild(container); var escapeHandler = function(e) { if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', escapeHandler); } }; document.addEventListener('keydown', escapeHandler); overlay._escapeHandler = escapeHandler; document.body.appendChild(overlay); // Fetch fresh data try { var response = await fetch(BASE_URL + '/api/functions/getPublicMenuData?rid=' + restaurantId); if (!response.ok) throw new Error('Network response was not ok'); var data = await response.json(); if (!data.success) throw new Error(data.error || 'Could not load data'); // Update global variables restaurant = data.restaurant; menuItems = (data.menuItems || []).filter(function(item) { return item.available !== false; }); EMBEDDED_RESTAURANT = data.restaurant; // Keep this consistent EMBEDDED_MENU_ITEMS = menuItems; // Update header with fresh data title.textContent = restaurant.name || 'Order Online'; header.style.background = restaurant.theme_header_background_color || '#FFFFFF'; header.style.color = restaurant.theme_header_text_color || '#1E293B'; closeBtn.style.color = restaurant.theme_header_text_color || '#1E293B'; // Initialize menu with fresh data initializeInlineMenu(content); } catch (error) { console.error('[Embedder] Failed to fetch fresh data on modal open:', error); content.innerHTML = '
⚠️ Error: Could not load menu. Please try again.
'; } } function closeModal() { if (currentOverlay) { if (currentOverlay._escapeHandler) { document.removeEventListener('keydown', currentOverlay._escapeHandler); } if (document.body.contains(currentOverlay)) { document.body.removeChild(currentOverlay); } currentOverlay = null; currentModalContent = null; document.body.style.overflow = ''; } } // ===================================================== // INLINE MENU LOGIC (from embedderInline) // ===================================================== var cart = []; var menuItems = EMBEDDED_MENU_ITEMS; var restaurant = EMBEDDED_RESTAURANT; var categories = []; var selectedCategory = 'all'; var editingInstructions = {}; var searchQuery = ''; // Load cart from localStorage and merge duplicates try { var savedCart = localStorage.getItem('base44_embed_cart_' + RESTAURANT_ID); if (savedCart) { var loadedCart = JSON.parse(savedCart); // Merge duplicate items var mergedCart = []; loadedCart.forEach(function(item) { var itemOptions = item.selected_options; var itemHasOptions = itemOptions && Object.keys(itemOptions).length > 0; var optionsKey = itemHasOptions ? JSON.stringify(itemOptions) : ''; var existingIndex = -1; for (var i = 0; i < mergedCart.length; i++) { var mergedOptions = mergedCart[i].selected_options; var mergedHasOptions = mergedOptions && Object.keys(mergedOptions).length > 0; var mergedOptionsKey = mergedHasOptions ? JSON.stringify(mergedOptions) : ''; if (mergedCart[i].id === item.id && mergedOptionsKey === optionsKey) { existingIndex = i; break; } } if (existingIndex !== -1) { mergedCart[existingIndex].quantity += item.quantity; } else { mergedCart.push(item); } }); cart = mergedCart; saveCart(); } } catch (e) { console.log('Failed to load cart from storage'); } function saveCart() { try { if (cart.length > 0) { localStorage.setItem('base44_embed_cart_' + RESTAURANT_ID, JSON.stringify(cart)); } else { localStorage.removeItem('base44_embed_cart_' + RESTAURANT_ID); } } catch (e) { console.log('Failed to save cart'); } } function getCurrencySymbol(code) { var symbols = { USD: '$', EUR: '€', GBP: '£', JPY: '¥', CNY: '¥', AUD: '$', CAD: '$', CHF: 'Fr', INR: '₹', MXN: '$', BRL: 'R$', ZAR: 'R', RUB: '₽', KRW: '₩', SGD: '$', HKD: '$', NOK: 'kr', SEK: 'kr', DKK: 'kr', PLN: 'zł', THB: '฿', IDR: 'Rp', MYR: 'RM', PHP: '₱', TRY: '₺', AED: 'د.إ', SAR: '﷼', ILS: '₪', EGP: '£', NGN: '₦' }; return symbols[code?.toUpperCase()] || code || '$'; } function getUniqueCategories() { var cats = {}; menuItems.forEach(function(item) { // Only include category if the item is not in a deleted category // Items in deleted categories should not appear if (item.category && !item.original_category) { cats[item.category] = true; } }); return ['all'].concat(Object.keys(cats)); } function formatCategory(cat) { if (cat === 'all') return 'All Items'; return cat.replace(/_/g, ' ').split(' ').map(function(word) { return word.charAt(0).toUpperCase() + word.slice(1); }).join(' '); } function getFilteredItems() { // Filter out items that are in deleted categories (marked by original_category) var items = menuItems.filter(function(item) { return !item.original_category; }); if (selectedCategory !== 'all') { items = items.filter(function(item) { return item.category === selectedCategory; }); } if (searchQuery && searchQuery.trim() !== '') { var query = searchQuery.toLowerCase().trim(); items = items.filter(function(item) { return (item.name && item.name.toLowerCase().indexOf(query) !== -1) || (item.description && item.description.toLowerCase().indexOf(query) !== -1); }); } return items; } var optionsModalOpen = false; var selectedItemForOptions = null; var selectedOptions = {}; var optionsSpecialInstructions = ''; var optionsQuantity = 1; var itemQuantities = {}; var cartOpen = false; var checkoutOpen = false; var confirmationOpen = false; var trackingOpen = false; var currentOrderId = null; var currentOrderData = null; var previousOrderData = null; var accountOpen = false; var isUserLoggedIn = false; var currentUser = null; var checkoutData = { name: '', phone: '', email: '', orderType: '', timingType: null, // 'asap' or 'scheduled' address: '', notes: '', scheduledTime: '', tipType: 'percentage', tipPercentage: 0, tipFlatAmount: 0, paymentMethod: '', deliveryLocation: null, marketingOptIn: true, partySize: null, showDatePicker: false, showTimePicker: false, dineInInitialized: false, promoCode: '', appliedPromo: null, openSection: null // Tracks which accordion section is manually open }; // Check for logged-in user on load and auto-fill checkout (async function() { var token = null; try { token = localStorage.getItem('base44_embedder_token'); } catch(e) {} if (token) { try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'verify', token: token }) }); var data = await response.json(); if (data.success && data.user) { currentUser = data.user; isUserLoggedIn = true; // Auto-fill all forms with user data checkoutData.name = data.user.full_name || ''; checkoutData.email = data.user.email || ''; checkoutData.phone = data.user.phone || ''; console.log('[Embedder] User auto-logged in and details autofilled:', data.user.email); } } catch(e) { console.log('[Embedder] Token verification failed'); } } })(); var isSubmitting = false; var calculatedDeliveryFee = 0; var deliveryFeeData = null; var deliveryFeeError = ''; var deliveryFeeLoading = false; var stripeInstance = null; var stripeElements = null; var cardElement = null; var stripePaymentProcessing = false; var stripeError = ''; var googleMapsLoaded = false; var autocompleteService = null; var placesService = null; var autocompleteSessionToken = null; var addressSuggestions = []; var showAddressSuggestions = false; var gettingLocation = false; var selectedAddressFromList = false; function addToCart(item, options, specialInstructions) { // Normalize options - treat null, undefined, and empty object as same var normalizedOptions = (options && Object.keys(options).length > 0) ? options : null; var optionsKey = normalizedOptions ? JSON.stringify(normalizedOptions) : ''; var instructions = specialInstructions || ''; // Check if same item with same options and instructions already exists in cart var existingIndex = -1; for (var i = 0; i < cart.length; i++) { var cartItemOptions = cart[i].selected_options; var cartItemHasOptions = cartItemOptions && Object.keys(cartItemOptions).length > 0; var cartItemOptionsKey = cartItemHasOptions ? JSON.stringify(cartItemOptions) : ''; var cartItemInstructions = cart[i].special_instructions || ''; if (cart[i].id === item.id && cartItemOptionsKey === optionsKey && cartItemInstructions === instructions) { existingIndex = i; break; } } if (existingIndex !== -1) { // Increase quantity of existing item cart[existingIndex].quantity += 1; // Move the updated item to the end of the cart array to reflect it as the "last added" var updatedItem = cart.splice(existingIndex, 1)[0]; cart.push(updatedItem); } else { // Find the original menu item to get base price var originalMenuItem = menuItems.find(function(mi) { return mi.id === item.id; }); var basePrice = originalMenuItem ? originalMenuItem.price : item.price; // Add new item to cart var cartItem = Object.assign({}, item, { quantity: 1, cartId: Date.now() + Math.random(), special_instructions: instructions, selected_options: normalizedOptions || {}, price: basePrice // Start with base price }); // Calculate price with options modifiers if (normalizedOptions && item.options) { item.options.forEach(function(optGroup) { var selectedChoices = normalizedOptions[optGroup.name]; if (selectedChoices && optGroup.choices) { // Normalize selected choices to array for uniform processing var choicesArray = Array.isArray(selectedChoices) ? selectedChoices : [selectedChoices]; choicesArray.forEach(function(selectedChoice) { var choice = optGroup.choices.find(function(c) { return c.name === selectedChoice; }); if (choice && choice.price_modifier) { cartItem.price += choice.price_modifier; } }); } }); } cart.push(cartItem); } saveCart(); renderMenu(); } function handleItemClick(item) { if (item.options && item.options.length > 0) { selectedItemForOptions = item; selectedOptions = {}; optionsQuantity = 1; item.options.forEach(function(opt) { if (opt.type === 'single_select' && opt.choices && opt.choices.length > 0) { selectedOptions[opt.name] = [opt.choices[0].name]; } else if (opt.type === 'multi_select') { selectedOptions[opt.name] = []; } }); optionsModalOpen = true; renderMenu(); } else { addToCart(item); } } function closeOptionsModal() { optionsModalOpen = false; selectedItemForOptions = null; selectedOptions = {}; optionsSpecialInstructions = ''; optionsQuantity = 1; renderMenu(); } function addItemWithOptions() { if (!selectedItemForOptions) return; var valid = true; if (selectedItemForOptions.options) { selectedItemForOptions.options.forEach(function(opt) { if (opt.required) { var sel = selectedOptions[opt.name]; if (opt.type === 'single_select' && !sel) { valid = false; } else if (opt.type === 'multi_select') { var minSel = opt.min_selections || 0; if (!sel || sel.length < minSel) { valid = false; } } } }); } if (!valid) { alert('Please select all required options'); return; } // Get the special instructions from the textarea var instructionsTextarea = currentModalContent.querySelector('#options-special-instructions'); var instructions = instructionsTextarea ? instructionsTextarea.value : optionsSpecialInstructions; // Convert selectedOptions to the format needed for addToCart var optionsForCart = {}; Object.keys(selectedOptions).forEach(function(key) { var val = selectedOptions[key]; if (Array.isArray(val) && val.length === 1) { optionsForCart[key] = val[0]; // Convert single-item arrays to strings } else { optionsForCart[key] = val; } }); // Add to cart multiple times based on quantity for (var i = 0; i < optionsQuantity; i++) { addToCart(selectedItemForOptions, optionsForCart, instructions); } closeOptionsModal(); if (window.showCartPreviewBriefly) window.showCartPreviewBriefly(); } function getCartTotal() { return cart.reduce(function(sum, item) { return sum + (item.price * item.quantity); }, 0); } function getCartCount() { return cart.reduce(function(sum, item) { return sum + item.quantity; }, 0); } function getTipAmount() { if (!restaurant.tips_settings || !restaurant.tips_settings.enabled) return 0; var orderTypeKey = checkoutData.orderType === 'delivery' ? 'delivery_enabled' : checkoutData.orderType === 'pickup' ? 'pickup_enabled' : 'dine_in_enabled'; if (!restaurant.tips_settings[orderTypeKey]) return 0; var subtotal = getCartTotal(); if (checkoutData.tipType === 'percentage') { return subtotal * (checkoutData.tipPercentage / 100); } return checkoutData.tipFlatAmount || 0; } function getDeliveryFee() { if (checkoutData.orderType !== 'delivery') return 0; return calculatedDeliveryFee || 0; } function calculateDiscount() { if (!checkoutData.appliedPromo) return 0; var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); if (checkoutData.appliedPromo.discount_type === 'percentage') { return subtotal * (checkoutData.appliedPromo.discount_value / 100); } else if (checkoutData.appliedPromo.discount_type === 'fixed_amount') { return Math.min(checkoutData.appliedPromo.discount_value, subtotal); } else if (checkoutData.appliedPromo.discount_type === 'free_delivery') { return deliveryFee; } return 0; } function getFeeBreakdown() { var otherFees = restaurant.other_fees || { enabled: false, items: [] }; // console.log('[Embedder Fees] otherFees:', otherFees); // console.log('[Embedder Fees] orderType:', checkoutData.orderType); if (!otherFees.enabled || !otherFees.items || !checkoutData.orderType) return []; const onlinePaymentMethods = ['stripe', 'paypal', 'razorpay', 'square', 'gocardless', 'worldpay', 'revolut', 'verifone', 'wonderful', 'alipay', 'airwallex']; const offlinePaymentMethods = ['cash_on_delivery', 'pay_cash_on_pickup', 'pay_card_on_pickup', 'pay_cash_on_delivery', 'pay_card_on_delivery', 'gcash', 'bkash']; var subtotal = getCartTotal(); return otherFees.items.filter(function(fee) { if (!fee.enabled) return false; const orderTypes = fee.order_types || []; // Treat empty array as "None" - strict check if (orderTypes.length === 0) return false; if (!orderTypes.includes('all') && !orderTypes.includes(checkoutData.orderType)) { return false; } const paymentMethods = fee.payment_methods || []; // Treat empty array as "None" - strict check if (paymentMethods.length === 0) return false; if (paymentMethods.includes('all')) { return true; } // If specific payment methods are required but none selected yet, we can't show the fee if (!checkoutData.paymentMethod) return false; const isOnlinePayment = onlinePaymentMethods.includes(checkoutData.paymentMethod); const isOfflinePayment = offlinePaymentMethods.includes(checkoutData.paymentMethod); if (paymentMethods.includes('online') && isOnlinePayment) return true; if (paymentMethods.includes('offline') && isOfflinePayment) return true; if (paymentMethods.includes(checkoutData.paymentMethod)) return true; return false; }).map(function(fee){ var feeAmount = fee.fee_type === 'percentage' ? subtotal * (fee.fee_value / 100) : fee.fee_value; return { name: fee.fee_name || 'Fee', amount: feeAmount, type: fee.fee_type, value: fee.fee_value }; }); } function calculateOtherFees() { var feeBreakdown = getFeeBreakdown(); return feeBreakdown.reduce(function(total, fee) { return total + fee.amount; }, 0); } function formatOptionsForOrder(cartItem) { var formattedOptions = []; if (!cartItem.selected_options) return []; var originalItem = menuItems.find(function(mi) { return mi.id === cartItem.id; }); Object.keys(cartItem.selected_options).forEach(function(optName) { var selection = cartItem.selected_options[optName]; var choices = []; // Handle both array and string inputs var selectedNames = Array.isArray(selection) ? selection : [selection]; selectedNames.forEach(function(choiceName) { var priceMod = 0; // Try to find price modifier from original menu item if (originalItem && originalItem.options) { var optGroup = originalItem.options.find(function(og) { return og.name === optName; }); // Fallback if find fails or name slightly different if (!optGroup) optGroup = originalItem.options.find(function(og) { return og.name === optName; }); if (optGroup && optGroup.choices) { var choiceData = optGroup.choices.find(function(c) { return c.name === choiceName; }); if (choiceData) priceMod = choiceData.price_modifier || 0; } } choices.push({ choice_name: choiceName, price_modifier: priceMod }); }); if (choices.length > 0) { formattedOptions.push({ option_name: optName, selected_choices: choices }); } }); return formattedOptions; } function calculateVAT() { if (!restaurant.vat_settings || !restaurant.vat_settings.enabled || !restaurant.vat_settings.rate) { return { vat_amount: 0, vat_rate: 0, vat_on_products: 0, vat_on_delivery: 0, vat_on_fees: 0, vat_on_tips: 0 }; } var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); var otherFees = calculateOtherFees(); var tipAmount = getTipAmount(); var vatRate = restaurant.vat_settings.rate / 100; var vatOnProducts = 0; var vatOnDelivery = 0; var vatOnFees = 0; var vatOnTips = 0; if (restaurant.vat_settings.applies_to_products !== false) { vatOnProducts = subtotal * vatRate; } if (restaurant.vat_settings.applies_to_delivery && deliveryFee > 0) { vatOnDelivery = deliveryFee * vatRate; } if (restaurant.vat_settings.applies_to_fees && otherFees > 0) { vatOnFees = otherFees * vatRate; } if (restaurant.vat_settings.applies_to_tips && tipAmount > 0) { vatOnTips = tipAmount * vatRate; } var totalVat = vatOnProducts + vatOnDelivery + vatOnFees + vatOnTips; return { vat_amount: totalVat, vat_rate: restaurant.vat_settings.rate, vat_on_products: vatOnProducts, vat_on_delivery: vatOnDelivery, vat_on_fees: vatOnFees, vat_on_tips: vatOnTips }; } function getOrderTotal() { var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); var tipAmount = getTipAmount(); var otherFees = calculateOtherFees(); var discount = calculateDiscount(); var vatCalc = calculateVAT(); if (restaurant.vat_settings && restaurant.vat_settings.price_inclusive) { return subtotal + deliveryFee + tipAmount + otherFees - discount; } else { return subtotal + deliveryFee + tipAmount + otherFees - discount + vatCalc.vat_amount; } } function getAvailablePaymentMethods() { var methods = []; var pg = restaurant.payment_gateways || {}; // Check Stripe if (pg.stripe && pg.stripe.enabled) { var isAvailableForOrderType = (checkoutData.orderType === 'delivery' && pg.stripe.delivery !== false) || (checkoutData.orderType === 'pickup' && pg.stripe.pickup !== false) || (checkoutData.orderType === 'dine_in' && pg.stripe.dine_in !== false); if (isAvailableForOrderType) { methods.push({ id: 'stripe', name: 'Credit/Debit Card', icon: '💳', desc: 'Pay securely with Stripe' }); } } // Check PayPal if (pg.paypal && pg.paypal.enabled) { var isAvailableForOrderType = (checkoutData.orderType === 'delivery' && pg.paypal.delivery !== false) || (checkoutData.orderType === 'pickup' && pg.paypal.pickup !== false) || (checkoutData.orderType === 'dine_in' && pg.paypal.dine_in !== false); if (isAvailableForOrderType) { methods.push({ id: 'paypal', name: 'PayPal', icon: '💰', desc: 'Pay with PayPal' }); } } // Check Razorpay if (pg.razorpay && pg.razorpay.enabled) { var isAvailableForOrderType = (checkoutData.orderType === 'delivery' && pg.razorpay.delivery !== false) || (checkoutData.orderType === 'pickup' && pg.razorpay.pickup !== false) || (checkoutData.orderType === 'dine_in' && pg.razorpay.dine_in !== false); if (isAvailableForOrderType) { methods.push({ id: 'razorpay', name: 'Razorpay', icon: '💳', desc: 'UPI, Cards, Wallets' }); } } // Check offline payments for pickup and dine-in if (checkoutData.orderType === 'pickup' || checkoutData.orderType === 'dine_in') { if (pg.pay_cash_on_pickup && pg.pay_cash_on_pickup.enabled) { var isAvailableForOrderType = (checkoutData.orderType === 'pickup' && pg.pay_cash_on_pickup.pickup !== false) || (checkoutData.orderType === 'dine_in' && pg.pay_cash_on_pickup.dine_in !== false); if (isAvailableForOrderType) { methods.push({ id: 'cash_on_delivery', name: 'Pay Cash at Counter', icon: '💰', desc: 'Pay with cash at the counter' }); } } if (pg.pay_card_on_pickup && pg.pay_card_on_pickup.enabled) { var isAvailableForOrderType = (checkoutData.orderType === 'pickup' && pg.pay_card_on_pickup.pickup !== false) || (checkoutData.orderType === 'dine_in' && pg.pay_card_on_pickup.dine_in !== false); if (isAvailableForOrderType) { methods.push({ id: 'pay_card_on_pickup', name: 'Pay Card at Counter', icon: '💳', desc: 'Pay with card at the counter' }); } } } // Check offline payments for delivery if (checkoutData.orderType === 'delivery' && restaurant.delivery_enabled !== false) { if (pg.pay_cash_on_delivery && pg.pay_cash_on_delivery.enabled) { methods.push({ id: 'cash_on_delivery', name: 'Cash on Delivery', icon: '💰', desc: 'Pay with cash when delivered' }); } if (pg.pay_card_on_delivery && pg.pay_card_on_delivery.enabled) { methods.push({ id: 'pay_card_on_delivery', name: 'Card on Delivery', icon: '💳', desc: 'Pay with card on delivery' }); } } if (methods.length === 0) { methods.push({ id: 'cash_on_delivery', name: checkoutData.orderType === 'delivery' ? 'Cash on Delivery' : 'Pay at Counter', icon: '💰', desc: checkoutData.orderType === 'delivery' ? 'Pay when delivered' : 'Pay when you pick up' }); } return methods; } function openCart() { cartOpen = true; renderMenu(); } function closeCart() { cartOpen = false; renderMenu(); } async function goToCheckout() { var isDeliveryEnabled = restaurant.delivery_enabled !== false; var isPickupEnabled = restaurant.pickup_enabled !== false; var isDineInEnabled = restaurant.dine_in_enabled === true; var isAnyServiceEnabled = isDeliveryEnabled || isPickupEnabled || isDineInEnabled; if (!isAnyServiceEnabled) { alert('Sorry, this restaurant is currently not accepting orders.'); return; } // Auto-select order type logic removed - User must select manually if (!checkoutData.orderType) { checkoutData.orderType = ''; } cartOpen = false; checkoutOpen = true; confirmationOpen = false; trackingOpen = false; // Refresh restaurant data to ensure latest fees/tips/settings try { var resp = await fetch(BASE_URL + '/api/functions/getPublicMenuData?rid=' + RESTAURANT_ID); if (resp.ok) { var fresh = await resp.json(); if (fresh.success && fresh.restaurant) { restaurant = fresh.restaurant; EMBEDDED_RESTAURANT = fresh.restaurant; if (Array.isArray(fresh.menuItems)) { menuItems = fresh.menuItems.filter(function(item){ return item.available !== false; }); EMBEDDED_MENU_ITEMS = menuItems; } } } } catch (e) { console.warn('[Embedder] Could not refresh data on checkout:', e); } renderMenu(); } function closeCheckout() { checkoutOpen = false; renderMenu(); } function goToConfirmation(orderId) { currentOrderId = orderId; checkoutOpen = false; confirmationOpen = true; trackingOpen = false; // Show loading state immediately currentOrderData = null; previousOrderData = null; renderMenu(); // Then fetch actual order data fetchOrderData(orderId); } function closeConfirmation() { confirmationOpen = false; currentOrderId = null; currentOrderData = null; previousOrderData = null; console.log('[Embedder] Closed confirmation, stopped polling'); renderMenu(); } function openAccount() { // Check if user is already logged in via token var token = null; try { token = localStorage.getItem('base44_embedder_token'); } catch(e) {} if (token && !currentUser) { // Verify token and get user verifyTokenAndShowOrders(token); } else if (currentUser) { // Already logged in - show orders directly fetchUserOrders(); } else { // Not logged in - show login popup directly renderLoginPopup(); } } async function verifyTokenAndShowOrders(token) { try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'verify', token: token }) }); var data = await response.json(); if (data.success && data.user) { currentUser = data.user; isUserLoggedIn = true; fetchUserOrders(); } else { // Token invalid - clear and show login popup try { localStorage.removeItem('base44_embedder_token'); } catch(e) {} renderLoginPopup(); } } catch (err) { renderLoginPopup(); } } function renderLoginPopup() { if (!currentModalContent) return; var html = '
'; html += '
'; html += '
'; html += '
🔐
'; html += '

Welcome Back

'; html += '

Login to view your orders

'; html += '
'; // Login form html += '
'; html += ''; html += '
'; html += '
'; html += '
Forgot Password?
'; html += ''; html += '
Don\'t have an account? Sign Up
'; html += '
'; // Signup form (hidden by default) html += ''; // Forgot Password form (hidden by default) html += ''; html += ''; html += '
'; currentModalContent.innerHTML = html; // Attach all event listeners attachLoginPopupListeners(); } function attachLoginPopupListeners() { if (!currentModalContent) return; // Back button var backBtn = currentModalContent.querySelector('#login-back'); if (backBtn) { backBtn.onclick = function() { accountOpen = false; trackingOpen = false; renderMenu(); }; } // Toggle between login, signup, and forgot password var showSignupLink = currentModalContent.querySelector('#show-signup'); var showLoginLink = currentModalContent.querySelector('#show-login'); var showForgotPasswordLink = currentModalContent.querySelector('#show-forgot-password'); var showLoginFromForgotLink = currentModalContent.querySelector('#show-login-from-forgot'); var loginForm = currentModalContent.querySelector('#login-form-section'); var signupForm = currentModalContent.querySelector('#signup-form-section'); var forgotPasswordForm = currentModalContent.querySelector('#forgot-password-section'); if (showSignupLink) { showSignupLink.onclick = function() { loginForm.style.display = 'none'; signupForm.style.display = 'block'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; }; } if (showLoginLink) { showLoginLink.onclick = function() { signupForm.style.display = 'none'; loginForm.style.display = 'block'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; }; } if (showForgotPasswordLink) { showForgotPasswordLink.onclick = function() { loginForm.style.display = 'none'; signupForm.style.display = 'none'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'block'; }; } if (showLoginFromForgotLink) { showLoginFromForgotLink.onclick = function() { if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; loginForm.style.display = 'block'; signupForm.style.display = 'none'; }; } // Login submit var loginSubmitBtn = currentModalContent.querySelector('#login-submit'); if (loginSubmitBtn) { loginSubmitBtn.onclick = async function() { var email = currentModalContent.querySelector('#login-email').value; var password = currentModalContent.querySelector('#login-password').value; var errorDiv = currentModalContent.querySelector('#login-error'); if (!email || !password) { errorDiv.textContent = 'Please enter email and password'; errorDiv.style.display = 'block'; return; } loginSubmitBtn.textContent = 'Signing in...'; loginSubmitBtn.disabled = true; try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'login', email: email, password: password }) }); var data = await response.json(); if (data.success) { try { localStorage.setItem('base44_embedder_token', data.token); } catch(e) {} currentUser = data.user; isUserLoggedIn = true; // Immediately show orders view fetchUserOrders(); } else { errorDiv.textContent = data.error || 'Login failed'; errorDiv.style.display = 'block'; loginSubmitBtn.textContent = 'Sign In'; loginSubmitBtn.disabled = false; } } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; loginSubmitBtn.textContent = 'Sign In'; loginSubmitBtn.disabled = false; } }; } // Signup submit var signupSubmitBtn = currentModalContent.querySelector('#signup-submit'); if (signupSubmitBtn) { signupSubmitBtn.onclick = async function() { var name = currentModalContent.querySelector('#signup-name').value; var email = currentModalContent.querySelector('#signup-email').value; var phone = currentModalContent.querySelector('#signup-phone').value; var password = currentModalContent.querySelector('#signup-password').value; var passwordConfirm = currentModalContent.querySelector('#signup-password-confirm').value; var errorDiv = currentModalContent.querySelector('#signup-error'); if (!name || !email || !password || !passwordConfirm) { errorDiv.textContent = 'Please fill in all required fields'; errorDiv.style.display = 'block'; return; } if (password !== passwordConfirm) { errorDiv.textContent = 'Passwords do not match'; errorDiv.style.display = 'block'; return; } signupSubmitBtn.textContent = 'Creating account...'; signupSubmitBtn.disabled = true; try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'signup', email: email, password: password, full_name: name, phone: phone }) }); var data = await response.json(); if (data.success) { try { localStorage.setItem('base44_embedder_token', data.token); } catch(e) {} currentUser = data.user; isUserLoggedIn = true; // Immediately show orders view fetchUserOrders(); } else { errorDiv.textContent = data.error || 'Signup failed'; errorDiv.style.display = 'block'; signupSubmitBtn.textContent = 'Create Account'; signupSubmitBtn.disabled = false; } } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; signupSubmitBtn.textContent = 'Create Account'; signupSubmitBtn.disabled = false; } }; } // Forgot password submit var forgotSubmitBtn = currentModalContent.querySelector('#forgot-submit'); if (forgotSubmitBtn) { forgotSubmitBtn.onclick = async function() { var email = currentModalContent.querySelector('#forgot-email').value; var errorDiv = currentModalContent.querySelector('#forgot-error'); var successDiv = currentModalContent.querySelector('#forgot-success'); if (!email) { errorDiv.textContent = 'Please enter your email address'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; return; } forgotSubmitBtn.textContent = 'Sending...'; forgotSubmitBtn.disabled = true; errorDiv.style.display = 'none'; successDiv.style.display = 'none'; try { var response = await fetch(BASE_URL + '/api/functions/requestPasswordReset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }); var data = await response.json(); if (data.success) { successDiv.textContent = '✅ Reset link sent! Check your email.'; successDiv.style.display = 'block'; errorDiv.style.display = 'none'; } else { errorDiv.textContent = data.error || 'Failed to send reset link'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; } forgotSubmitBtn.textContent = 'Send Reset Link'; forgotSubmitBtn.disabled = false; } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; forgotSubmitBtn.textContent = 'Send Reset Link'; forgotSubmitBtn.disabled = false; } }; } } function closeAccount() { accountOpen = false; trackingOpen = false; renderMenu(); } function handleLogin() { // Show login inside modal accountOpen = false; renderLoginInModal(); } function renderLoginInModal() { if (!currentModalContent) return; var html = '
'; html += '
'; html += '
'; html += '
🔐
'; html += '

Welcome Back

'; html += '

Login to view your orders

'; html += '
'; // Native login form instead of iframe html += '
'; html += ''; html += '
'; html += '
'; html += '
Forgot Password?
'; html += ''; html += '
Don\'t have an account? Sign Up
'; html += '
'; // Signup form (hidden by default) html += ''; // Forgot Password form (hidden by default) html += ''; html += ''; html += '
'; currentModalContent.innerHTML = html; // Back button var backBtn = currentModalContent.querySelector('#login-back'); if (backBtn) { backBtn.onclick = function() { accountOpen = false; trackingOpen = false; renderMenu(); }; } // Toggle between login, signup, and forgot password var showSignupLink = currentModalContent.querySelector('#show-signup'); var showLoginLink = currentModalContent.querySelector('#show-login'); var showForgotPasswordLink = currentModalContent.querySelector('#show-forgot-password'); var showLoginFromForgotLink = currentModalContent.querySelector('#show-login-from-forgot'); var loginForm = currentModalContent.querySelector('#native-login-form'); var signupForm = currentModalContent.querySelector('#native-signup-form'); var forgotPasswordForm = currentModalContent.querySelector('#native-forgot-password'); if (showSignupLink) { showSignupLink.onclick = function() { loginForm.style.display = 'none'; signupForm.style.display = 'block'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; }; } if (showLoginLink) { showLoginLink.onclick = function() { signupForm.style.display = 'none'; loginForm.style.display = 'block'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; }; } if (showForgotPasswordLink) { showForgotPasswordLink.onclick = function() { loginForm.style.display = 'none'; signupForm.style.display = 'none'; if (forgotPasswordForm) forgotPasswordForm.style.display = 'block'; }; } if (showLoginFromForgotLink) { showLoginFromForgotLink.onclick = function() { if (forgotPasswordForm) forgotPasswordForm.style.display = 'none'; loginForm.style.display = 'block'; signupForm.style.display = 'none'; }; } // Login submit var loginSubmitBtn = currentModalContent.querySelector('#login-submit'); if (loginSubmitBtn) { loginSubmitBtn.onclick = async function() { var email = currentModalContent.querySelector('#login-email').value; var password = currentModalContent.querySelector('#login-password').value; var errorDiv = currentModalContent.querySelector('#login-error'); if (!email || !password) { errorDiv.textContent = 'Please enter email and password'; errorDiv.style.display = 'block'; return; } loginSubmitBtn.textContent = 'Signing in...'; loginSubmitBtn.disabled = true; try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'login', email: email, password: password }) }); var data = await response.json(); if (data.success) { try { localStorage.setItem('base44_embedder_token', data.token); } catch(e) {} currentUser = data.user; isUserLoggedIn = true; // Go to order tracking view trackingOpen = true; accountOpen = false; renderOrdersView(); } else { errorDiv.textContent = data.error || 'Login failed'; errorDiv.style.display = 'block'; loginSubmitBtn.textContent = 'Sign In'; loginSubmitBtn.disabled = false; } } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; loginSubmitBtn.textContent = 'Sign In'; loginSubmitBtn.disabled = false; } }; } // Signup submit var signupSubmitBtn = currentModalContent.querySelector('#signup-submit'); if (signupSubmitBtn) { signupSubmitBtn.onclick = async function() { var name = currentModalContent.querySelector('#signup-name').value; var email = currentModalContent.querySelector('#signup-email').value; var phone = currentModalContent.querySelector('#signup-phone').value; var password = currentModalContent.querySelector('#signup-password').value; var passwordConfirm = currentModalContent.querySelector('#signup-password-confirm').value; var errorDiv = currentModalContent.querySelector('#signup-error'); if (!name || !email || !password || !passwordConfirm) { errorDiv.textContent = 'Please fill in all required fields'; errorDiv.style.display = 'block'; return; } if (password !== passwordConfirm) { errorDiv.textContent = 'Passwords do not match'; errorDiv.style.display = 'block'; return; } signupSubmitBtn.textContent = 'Creating account...'; signupSubmitBtn.disabled = true; try { var response = await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'signup', email: email, password: password, full_name: name, phone: phone }) }); var data = await response.json(); if (data.success) { try { localStorage.setItem('base44_embedder_token', data.token); } catch(e) {} currentUser = data.user; isUserLoggedIn = true; trackingOpen = true; accountOpen = false; fetchUserOrders(); } else { errorDiv.textContent = data.error || 'Signup failed'; errorDiv.style.display = 'block'; signupSubmitBtn.textContent = 'Create Account'; signupSubmitBtn.disabled = false; } } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; signupSubmitBtn.textContent = 'Create Account'; signupSubmitBtn.disabled = false; } }; } // Forgot password submit var forgotSubmitBtn = currentModalContent.querySelector('#forgot-submit'); if (forgotSubmitBtn) { forgotSubmitBtn.onclick = async function() { var email = currentModalContent.querySelector('#forgot-email').value; var errorDiv = currentModalContent.querySelector('#forgot-error'); var successDiv = currentModalContent.querySelector('#forgot-success'); if (!email) { errorDiv.textContent = 'Please enter your email address'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; return; } forgotSubmitBtn.textContent = 'Sending...'; forgotSubmitBtn.disabled = true; errorDiv.style.display = 'none'; successDiv.style.display = 'none'; try { var response = await fetch(BASE_URL + '/api/functions/requestPasswordReset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }); var data = await response.json(); if (data.success) { successDiv.textContent = '✅ Reset link sent! Check your email.'; successDiv.style.display = 'block'; errorDiv.style.display = 'none'; } else { errorDiv.textContent = data.error || 'Failed to send reset link'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; } forgotSubmitBtn.textContent = 'Send Reset Link'; forgotSubmitBtn.disabled = false; } catch (err) { errorDiv.textContent = 'Connection error. Please try again.'; errorDiv.style.display = 'block'; successDiv.style.display = 'none'; forgotSubmitBtn.textContent = 'Send Reset Link'; forgotSubmitBtn.disabled = false; } }; } } var userOrders = []; var isLoadingUserOrders = false; async function fetchUserOrders() { if (!currentUser || !currentUser.email) { console.log('[Embedder] No currentUser, skipping fetch'); isLoadingUserOrders = false; userOrders = []; trackingOpen = true; accountOpen = false; renderOrdersView(); return; } isLoadingUserOrders = true; userOrders = []; trackingOpen = true; accountOpen = false; renderOrdersView(); try { console.log('[Embedder] Fetching orders for:', currentUser.email); var response = await fetch(BASE_URL + '/api/functions/findOrders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: currentUser.email, restaurantId: RESTAURANT_ID }) }); var data = await response.json(); if (data.orders) { userOrders = data.orders; console.log('[Embedder] SUCCESS - Got', userOrders.length, 'orders'); } else { userOrders = []; console.log('[Embedder] No orders found'); } } catch (error) { console.error('[Embedder] Fetch failed:', error); userOrders = []; } isLoadingUserOrders = false; renderOrdersView(); } function renderOrdersView() { if (!currentModalContent) return; var currency = getCurrencySymbol(restaurant.currency); var html = '
'; // Only show content if we have currentUser if (!currentUser) { html += '

Loading...

'; html += '
'; currentModalContent.innerHTML = html; return; } // Header with user info and logout - mobile responsive html += '
'; html += '
Welcome,' + (currentUser.full_name || currentUser.email) + '
'; html += '
'; html += ''; html += ''; html += '
'; html += '

📦 My Orders

'; if (isLoadingUserOrders) { html += '

Loading your orders...

'; } else if (userOrders.length === 0) { html += '
'; html += '
🛍️
'; html += '

No Orders Yet

'; html += '

Your order history will appear here

'; html += ''; html += '
'; } else { html += '
'; html += ''; html += '
'; userOrders.forEach(function(order) { var statusConfig = { pending: { icon: '⏳', title: 'Pending', color: '#d97706', bgColor: '#fef3c7' }, confirmed: { icon: '✅', title: 'Confirmed', color: '#059669', bgColor: '#d1fae5' }, preparing: { icon: '👨‍🍳', title: 'Preparing', color: '#ea580c', bgColor: '#ffedd5' }, ready: { icon: '🛍️', title: 'Ready', color: '#2563eb', bgColor: '#dbeafe' }, out_for_delivery: { icon: '🚗', title: 'On the Way', color: '#7c3aed', bgColor: '#ede9fe' }, completed: { icon: '✅', title: 'Completed', color: '#059669', bgColor: '#d1fae5' }, cancelled: { icon: '❌', title: 'Cancelled', color: '#dc2626', bgColor: '#fee2e2' } }; var status = statusConfig[order.status] || statusConfig.pending; html += '
'; html += '
'; html += '#' + order.id.slice(0, 8).toUpperCase() + ''; html += '' + status.icon + ' ' + status.title + ''; html += '
'; html += '
' + (order.order_type === 'delivery' ? '🚗 Delivery' : order.order_type === 'dine_in' ? '🍽️ Dine In' + (order.table_number ? ' (Table ' + order.table_number + ')' : '') : '🛍️ Pickup') + '
'; html += '
' + new Date(order.created_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }) + '
'; html += '
' + (order.items?.length || 0) + ' items
'; // Expected time with countdown if available if (order.estimated_ready_time && ['confirmed', 'preparing', 'ready', 'out_for_delivery'].includes(order.status)) { var expectedTime = new Date(order.estimated_ready_time); var formattedExpectedTime = expectedTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); var remaining = expectedTime.getTime() - Date.now(); if (remaining > 0) { var mins = Math.floor(remaining / 60000); html += '
⏱️ Ready in ~' + mins + ' min • Expected: ' + formattedExpectedTime + '
'; } else { html += '
⏰ Expected: ' + formattedExpectedTime + '
'; } } html += '
' + currency + (order.total || 0).toFixed(2) + '
'; html += '
'; }); html += '
'; } html += ''; currentModalContent.innerHTML = html; // Attach event listeners var backMenuBtn = currentModalContent.querySelector('#orders-back-menu'); if (backMenuBtn) { backMenuBtn.onclick = function() { trackingOpen = false; accountOpen = false; renderMenu(); }; } var logoutBtn = currentModalContent.querySelector('#orders-logout'); if (logoutBtn) { logoutBtn.onclick = async function() { var token = null; try { token = localStorage.getItem('base44_embedder_token'); } catch(e) {} if (token) { try { await fetch(BASE_URL + '/api/functions/embedderAuth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'logout', token: token }) }); } catch(e) {} } try { localStorage.removeItem('base44_embedder_token'); } catch(e) {} currentUser = null; isUserLoggedIn = false; userOrders = []; trackingOpen = false; accountOpen = false; renderMenu(); }; } var startShoppingBtn = currentModalContent.querySelector('#orders-start-shopping'); if (startShoppingBtn) { startShoppingBtn.onclick = function() { trackingOpen = false; accountOpen = false; renderMenu(); }; } // Order card clicks - show order details var orderCards = currentModalContent.querySelectorAll('.user-order-card'); orderCards.forEach(function(card) { card.onclick = function() { var orderId = card.dataset.orderId; currentOrderId = orderId; currentOrderData = null; trackingOpen = false; confirmationOpen = true; renderMenu(); // Start polling for real-time updates fetchOrderData(orderId); }; }); } function fetchOrderData(orderId) { console.log('[Embedder] Fetching order data for:', orderId); fetch(BASE_URL + '/api/functions/getOrderStatus?orderId=' + orderId + '&t=' + Date.now()) .then(function(res) { return res.json(); }) .then(function(data) { console.log('[Embedder] Got order data:', data); if (data.success && data.order) { // Check if data actually changed before re-rendering var hasChanged = !previousOrderData || previousOrderData.status !== data.order.status || JSON.stringify(previousOrderData.driver_location) !== JSON.stringify(data.order.driver_location) || previousOrderData.estimated_ready_time !== data.order.estimated_ready_time; currentOrderData = data.order; previousOrderData = JSON.parse(JSON.stringify(data.order)); // Deep copy console.log('[Embedder] Data changed:', hasChanged, 'Status:', data.order.status); if (confirmationOpen && hasChanged) { renderMenu(); } } // Continue polling every 3 seconds if still on confirmation page if (confirmationOpen && currentOrderId === orderId) { setTimeout(function() { fetchOrderData(orderId); }, 3000); } }) .catch(function(err) { console.error('[Embedder] Failed to fetch order:', err); // Continue polling even on error if (confirmationOpen && currentOrderId === orderId) { setTimeout(function() { fetchOrderData(orderId); }, 4000); } }); } function loadStripe() { if (!STRIPE_PUBLISHABLE_KEY || window.Stripe) { if (window.Stripe && STRIPE_PUBLISHABLE_KEY) { initStripe(); } return; } var script = document.createElement('script'); script.src = 'https://js.stripe.com/v3/'; script.async = true; script.onload = function() { initStripe(); }; document.head.appendChild(script); } function initStripe() { if (window.Stripe && STRIPE_PUBLISHABLE_KEY && !stripeInstance) { stripeInstance = window.Stripe(STRIPE_PUBLISHABLE_KEY); initPaymentRequest(); } } var paymentRequest = null; var canMakePaymentRequest = false; function initPaymentRequest() { if (!stripeInstance || !restaurant) return; var total = getOrderTotal(); var currencyCode = (restaurant.currency || 'USD').toLowerCase(); paymentRequest = stripeInstance.paymentRequest({ country: 'US', currency: currencyCode, total: { label: restaurant.name || 'Order Total', amount: Math.round(total * 100), }, requestPayerName: true, requestPayerEmail: true, requestPayerPhone: true, }); paymentRequest.canMakePayment().then(function(result) { if (result) { canMakePaymentRequest = true; console.log('Apple Pay / Google Pay available'); } }); paymentRequest.on('paymentmethod', async function(ev) { try { var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); var tipAmount = getTipAmount(); var total = subtotal + deliveryFee + tipAmount; var response = await fetch(BASE_URL + '/api/functions/createStripePaymentIntent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: total, currency: (restaurant.currency || 'USD').toLowerCase(), restaurant_id: RESTAURANT_ID, order_data: { customer_name: ev.payerName || checkoutData.name, customer_email: ev.payerEmail || checkoutData.email, restaurant_location: restaurant.location || null, delivery_location: checkoutData.deliveryLocation || null } }) }); var data = await response.json(); var clientSecret = data.client_secret || data.clientSecret; if (!clientSecret) { ev.complete('fail'); return; } var confirmResult = await stripeInstance.confirmCardPayment( clientSecret, { payment_method: ev.paymentMethod.id }, { handleActions: false } ); if (confirmResult.error) { ev.complete('fail'); return; } if (confirmResult.paymentIntent.status === 'requires_action') { var actionResult = await stripeInstance.confirmCardPayment(clientSecret); if (actionResult.error) { ev.complete('fail'); return; } } ev.complete('success'); submitOrderWithPayment(confirmResult.paymentIntent.id); } catch (err) { ev.complete('fail'); console.error('Payment Request error:', err); } }); } function updatePaymentRequestTotal() { if (paymentRequest) { var total = getOrderTotal(); paymentRequest.update({ total: { label: restaurant.name || 'Order Total', amount: Math.round(total * 100), } }); } } function mountPaymentRequestButton() { if (!stripeInstance || !paymentRequest || !canMakePaymentRequest || !currentModalContent) return; var prButtonContainer = currentModalContent.querySelector('#payment-request-button'); if (!prButtonContainer || prButtonContainer.dataset.mounted === 'true') return; updatePaymentRequestTotal(); var prButton = stripeInstance.elements().create('paymentRequestButton', { paymentRequest: paymentRequest, style: { paymentRequestButton: { type: 'default', theme: 'dark', height: '48px', }, }, }); prButton.mount(prButtonContainer); prButtonContainer.dataset.mounted = 'true'; } function mountStripeCard() { if (!stripeInstance || !currentModalContent) return; var cardContainer = currentModalContent.querySelector('#stripe-card-element'); if (!cardContainer) return; if (cardElement && cardContainer.children.length > 0) return; if (cardElement) { try { cardElement.unmount(); } catch(e) {} cardElement = null; stripeElements = null; } try { stripeElements = stripeInstance.elements(); cardElement = stripeElements.create('card', { style: { base: { fontSize: '16px', color: '#1e293b', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', '::placeholder': { color: '#94a3b8' } }, invalid: { color: '#ef4444' } } }); cardElement.mount(cardContainer); cardElement.on('change', function(event) { stripeError = event.error ? event.error.message : ''; var errorDisplay = currentModalContent.querySelector('#stripe-card-errors'); if (errorDisplay) errorDisplay.textContent = stripeError; }); } catch (err) { console.error('Error mounting Stripe card:', err); stripeError = 'Failed to load payment form: ' + err.message; } } async function processStripePayment() { if (stripePaymentProcessing || !stripeInstance || !cardElement) return; // Check minimum order based on order type var subtotal = getCartTotal(); var minimumOrder = checkoutData.orderType === 'delivery' ? (restaurant.delivery_minimum_order || 0) : checkoutData.orderType === 'pickup' ? (restaurant.pickup_minimum_order || 0) : (restaurant.dine_in_minimum_order || 0); if (minimumOrder > 0 && subtotal < minimumOrder) { var currency = getCurrencySymbol(restaurant.currency); alert('Minimum order of ' + currency + minimumOrder.toFixed(2) + ' required for ' + checkoutData.orderType.replace(/_/g, ' ')); return; } stripePaymentProcessing = true; stripeError = ''; var payBtn = currentModalContent.querySelector('#stripe-pay-btn'); if (payBtn) { payBtn.disabled = true; payBtn.textContent = '⏳ Processing...'; } try { var deliveryFee = getDeliveryFee(); var tipAmount = getTipAmount(); var total = subtotal + deliveryFee + tipAmount; var response = await fetch(BASE_URL + '/api/functions/createStripePaymentIntent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: total, currency: (restaurant.currency || 'USD').toLowerCase(), restaurant_id: RESTAURANT_ID, order_data: { customer_name: checkoutData.name, customer_email: checkoutData.email, restaurant_location: restaurant.location || null, delivery_location: checkoutData.deliveryLocation || null } }) }); var data = await response.json(); var clientSecret = data.client_secret || data.clientSecret; if (!clientSecret) { throw new Error(data.error || 'Failed to create payment intent'); } var result = await stripeInstance.confirmCardPayment(clientSecret, { payment_method: { card: cardElement, billing_details: { name: checkoutData.name, email: checkoutData.email || undefined, phone: checkoutData.phone } } }); if (result.error) { stripeError = result.error.message; stripePaymentProcessing = false; var errorDisplay = currentModalContent.querySelector('#stripe-card-errors'); if (errorDisplay) errorDisplay.textContent = stripeError; if (payBtn) { payBtn.disabled = false; payBtn.textContent = '💳 Pay with Card'; } } else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') { submitOrderWithPayment(result.paymentIntent.id); } } catch (err) { stripeError = err.message || 'Payment failed'; stripePaymentProcessing = false; var errorDisplay = currentModalContent.querySelector('#stripe-card-errors'); if (errorDisplay) errorDisplay.textContent = stripeError; if (payBtn) { payBtn.disabled = false; payBtn.textContent = '💳 Pay with Card'; } } } function submitOrderWithPayment(paymentId) { var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); var tipAmount = getTipAmount(); var otherFeesTotal = calculateOtherFees(); var discount = calculateDiscount(); var vatCalc = calculateVAT(); var total = restaurant.vat_settings && restaurant.vat_settings.price_inclusive ? subtotal + deliveryFee + tipAmount + otherFeesTotal - discount : subtotal + deliveryFee + tipAmount + otherFeesTotal - discount + vatCalc.vat_amount; // Use logged-in user's email if available for order matching var orderCustomerEmail = (currentUser && currentUser.email) ? currentUser.email : checkoutData.email; var orderData = { restaurant_id: RESTAURANT_ID, restaurant_location: restaurant.location || null, customer_name: checkoutData.name, customer_phone: checkoutData.phone, customer_email: orderCustomerEmail, order_type: checkoutData.orderType, delivery_address: checkoutData.orderType === 'delivery' ? checkoutData.address : '', delivery_location: checkoutData.deliveryLocation || null, special_notes: checkoutData.notes, scheduled_time: checkoutData.scheduledTime || null, items: cart.map(function(item) { return { item_id: item.id, item_name: item.name, quantity: item.quantity, price: item.price, special_instructions: item.special_instructions || '', selected_options: formatOptionsForOrder(item) }; }), subtotal: subtotal, delivery_fee: deliveryFee, other_fees: otherFeesTotal, other_fees_breakdown: getFeeBreakdown().map(function(f) { return { name: f.name, amount: f.amount }; }), tip_amount: tipAmount, vat_amount: vatCalc.vat_amount, vat_rate: vatCalc.vat_rate, vat_on_products: vatCalc.vat_on_products, vat_on_delivery: vatCalc.vat_on_delivery, vat_on_fees: vatCalc.vat_on_fees, vat_on_tips: vatCalc.vat_on_tips, total: total, promotion_code: checkoutData.appliedPromo ? checkoutData.appliedPromo.promo_code : null, discount_amount: discount, status: 'pending', payment_method: 'stripe', payment_status: 'paid', payment_id: paymentId }; fetch(BASE_URL + '/api/functions/createEmbedOrder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) }) .then(function(res) { return res.json(); }) .then(function(data) { stripePaymentProcessing = false; if (data.success) { cart = []; saveCart(); checkoutData = { name: '', phone: '', email: '', orderType: 'pickup', address: '', notes: '', scheduledTime: '', tipType: 'percentage', tipPercentage: 0, tipFlatAmount: 0, paymentMethod: '', partySize: null }; cardElement = null; stripeElements = null; goToConfirmation(data.orderId); } else { stripeError = 'Order creation failed: ' + (data.error || 'Unknown error'); renderMenu(); } }) .catch(function(err) { stripePaymentProcessing = false; stripeError = 'Order creation failed'; renderMenu(); }); } async function submitOrder() { console.log('[Embedder submitOrder] Called - isSubmitting:', isSubmitting); console.log('[Embedder submitOrder] checkoutData:', JSON.stringify(checkoutData)); if (isSubmitting) { console.log('[Embedder submitOrder] Already submitting, returning'); return; } if (!checkoutData.name || !checkoutData.phone || !checkoutData.email) { alert('Please fill in your name, phone number, and email address'); checkoutData.openSection = 'contact'; renderMenu(); return; } if (checkoutData.orderType === 'delivery' && !checkoutData.address) { alert('Please enter your delivery address'); checkoutData.openSection = 'address'; renderMenu(); return; } if (checkoutData.orderType === 'delivery' && (!deliveryFeeData || !deliveryFeeData.success)) { alert('❌ ' + (deliveryFeeError || 'Please verify your delivery address is within our delivery range')); checkoutData.openSection = 'address'; renderMenu(); return; } // Validate timing for delivery and pickup if (checkoutData.orderType !== 'dine_in') { if (!checkoutData.timingType) { alert('Please select when you want your order (ASAP or Schedule)'); checkoutData.openSection = 'time'; renderMenu(); return; } if (checkoutData.timingType === 'scheduled' && (!checkoutData.scheduledTime || checkoutData.scheduledTime.indexOf(':') === -1)) { alert('Please select a date and time for your scheduled order'); checkoutData.openSection = 'time'; renderMenu(); return; } } if (checkoutData.orderType === 'dine_in') { if (!checkoutData.partySize || checkoutData.partySize < 1) { alert('Please enter the party size'); checkoutData.openSection = 'dine_in_party'; renderMenu(); return; } if (!checkoutData.scheduledTime || checkoutData.scheduledTime.indexOf(':') === -1) { alert('Please select a date and time for your reservation'); checkoutData.openSection = 'dine_in_time'; renderMenu(); return; } } // Check minimum order based on order type var subtotal = getCartTotal(); var minimumOrder = checkoutData.orderType === 'delivery' ? (restaurant.delivery_minimum_order || 0) : checkoutData.orderType === 'pickup' ? (restaurant.pickup_minimum_order || 0) : (restaurant.dine_in_minimum_order || 0); if (minimumOrder > 0 && subtotal < minimumOrder) { var currency = getCurrencySymbol(restaurant.currency); alert('Minimum order of ' + currency + minimumOrder.toFixed(2) + ' required for ' + checkoutData.orderType.replace(/_/g, ' ')); return; } console.log('[Embedder submitOrder] All validations passed, setting isSubmitting = true'); isSubmitting = true; renderMenu(); try { // For dine-in: Reservation creation is now handled by createEmbedOrder to prevent duplicates var subtotal = getCartTotal(); var deliveryFee = getDeliveryFee(); var tipAmount = getTipAmount(); var otherFeesTotal = calculateOtherFees(); var discount = calculateDiscount(); var vatCalc = calculateVAT(); var total = restaurant.vat_settings && restaurant.vat_settings.price_inclusive ? subtotal + deliveryFee + tipAmount + otherFeesTotal - discount : subtotal + deliveryFee + tipAmount + otherFeesTotal - discount + vatCalc.vat_amount; // Use logged-in user's email if available for order matching var submitOrderEmail = (currentUser && currentUser.email) ? currentUser.email : checkoutData.email; var orderData = { restaurant_id: RESTAURANT_ID, restaurant_location: restaurant.location || null, customer_name: checkoutData.name, customer_phone: checkoutData.phone, customer_email: submitOrderEmail, order_type: checkoutData.orderType, reservation_details: (checkoutData.orderType === 'dine_in') ? { party_size: checkoutData.partySize, reservation_date: checkoutData.scheduledTime ? new Date(checkoutData.scheduledTime).toISOString() : new Date().toISOString(), special_requests: checkoutData.notes || '' } : null, delivery_address: checkoutData.orderType === 'delivery' ? checkoutData.address : '', delivery_location: checkoutData.deliveryLocation || null, special_notes: checkoutData.notes, scheduled_time: checkoutData.scheduledTime || null, items: cart.map(function(item) { return { item_id: item.id, item_name: item.name, quantity: item.quantity, price: item.price, special_instructions: item.special_instructions || '', selected_options: formatOptionsForOrder(item) }; }), subtotal: subtotal, delivery_fee: deliveryFee, other_fees: otherFeesTotal, other_fees_breakdown: getFeeBreakdown().map(function(f) { return { name: f.name, amount: f.amount }; }), tip_amount: tipAmount, vat_amount: vatCalc.vat_amount, vat_rate: vatCalc.vat_rate, vat_on_products: vatCalc.vat_on_products, vat_on_delivery: vatCalc.vat_on_delivery, vat_on_fees: vatCalc.vat_on_fees, vat_on_tips: vatCalc.vat_on_tips, total: total, promotion_code: checkoutData.appliedPromo ? checkoutData.appliedPromo.promo_code : null, discount_amount: discount, status: 'pending', payment_method: checkoutData.paymentMethod || 'cash_on_delivery', payment_status: 'pending' }; fetch(BASE_URL + '/api/functions/createEmbedOrder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) }) .then(function(res) { return res.json(); }) .then(function(data) { isSubmitting = false; if (data.success) { cart = []; saveCart(); checkoutData = { name: '', phone: '', email: '', orderType: 'pickup', address: '', notes: '', scheduledTime: '', tipType: 'percentage', tipPercentage: 0, tipFlatAmount: 0, paymentMethod: '', partySize: null }; goToConfirmation(data.orderId); } else { alert('Failed to place order: ' + (data.error || 'Unknown error')); renderMenu(); } }) .catch(function(err) { isSubmitting = false; alert('Failed to place order. Please try again.'); renderMenu(); }); } catch (err) { isSubmitting = false; console.error('Order submission error:', err); alert(err.message || 'An unexpected error occurred while placing your order.'); renderMenu(); } } // Load Stripe if available if (STRIPE_PUBLISHABLE_KEY) { loadStripe(); } function initGoogleServices() { if (window.google && window.google.maps && window.google.maps.places && !autocompleteService) { autocompleteService = new window.google.maps.places.AutocompleteService(); var dummyDiv = document.createElement('div'); placesService = new window.google.maps.places.PlacesService(dummyDiv); autocompleteSessionToken = new window.google.maps.places.AutocompleteSessionToken(); console.log('Google Maps initialized in embedder'); } } function searchAddresses(query) { if (!query || query.length < 2) { addressSuggestions = []; showAddressSuggestions = false; var suggestionsEl = currentModalContent ? currentModalContent.querySelector('.address-autocomplete-suggestions') : null; if (suggestionsEl) suggestionsEl.remove(); return; } if (!autocompleteService) { console.log('Autocomplete service not ready, initializing...'); if (window.google && window.google.maps && window.google.maps.places) { initGoogleServices(); } return; } var request = { input: query, sessionToken: autocompleteSessionToken, types: ['geocode'] }; if (restaurant.location) { request.location = new window.google.maps.LatLng(restaurant.location.lat, restaurant.location.lng); request.radius = 50000; } autocompleteService.getPlacePredictions(request, function(predictions, status) { if (status === window.google.maps.places.PlacesServiceStatus.OK && predictions) { addressSuggestions = predictions.map(function(p) { return { placeId: p.place_id, description: p.description, mainText: p.structured_formatting ? p.structured_formatting.main_text : p.description, secondaryText: p.structured_formatting ? p.structured_formatting.secondary_text : '' }; }); showAddressSuggestions = true; updateAddressSuggestions(); } else { addressSuggestions = []; showAddressSuggestions = false; var suggestionsEl = currentModalContent ? currentModalContent.querySelector('.address-autocomplete-suggestions') : null; if (suggestionsEl) suggestionsEl.remove(); } }); } function updateAddressSuggestions() { if (!currentModalContent) return; var addressWrapper = currentModalContent.querySelector('#address-field-wrapper'); if (!addressWrapper) return; var existing = addressWrapper.querySelector('.address-autocomplete-suggestions'); if (existing) existing.remove(); if (!showAddressSuggestions || addressSuggestions.length === 0) return; var suggestionsHtml = '
'; addressSuggestions.forEach(function(s, idx) { suggestionsHtml += '
'; suggestionsHtml += '
' + s.mainText + '
'; if (s.secondaryText) suggestionsHtml += '
' + s.secondaryText + '
'; suggestionsHtml += '
'; }); suggestionsHtml += '
'; addressWrapper.insertAdjacentHTML('beforeend', suggestionsHtml); var suggestionItems = addressWrapper.querySelectorAll('.address-suggestion-item'); suggestionItems.forEach(function(item) { item.onclick = function(e) { e.preventDefault(); var idx = parseInt(item.dataset.suggestionIdx); if (addressSuggestions[idx]) { selectAddress(addressSuggestions[idx]); } }; }); } async function calculateDeliveryFee() { if (!checkoutData.deliveryLocation || !restaurant.location) { calculatedDeliveryFee = 0; deliveryFeeData = null; deliveryFeeError = ''; deliveryFeeLoading = false; updateDeliveryFeeUI(); return; } deliveryFeeLoading = true; deliveryFeeError = ''; updateDeliveryFeeUI(); try { var response = await fetch(BASE_URL + '/api/functions/calculateDeliveryFee', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ restaurant_location: restaurant.location, delivery_location: checkoutData.deliveryLocation, restaurant_id: RESTAURANT_ID }) }); var data = await response.json(); deliveryFeeLoading = false; if (response.ok && data.success) { deliveryFeeData = data; calculatedDeliveryFee = data.delivery_fee; deliveryFeeError = ''; } else { deliveryFeeData = { success: false, error: data.error || 'Error calculating fee' }; calculatedDeliveryFee = 0; deliveryFeeError = data.error || 'Address outside delivery range'; // Immediately alert and keep address section open setTimeout(function() { alert('❌ ' + (deliveryFeeError || 'This address is outside our delivery range. Please choose a different address.')); }, 100); } } catch (error) { deliveryFeeLoading = false; deliveryFeeData = { success: false, error: 'Could not verify delivery zone' }; calculatedDeliveryFee = 0; deliveryFeeError = 'Could not calculate delivery fee'; // Immediately alert and keep address section open setTimeout(function() { alert('❌ Could not calculate delivery fee. Please verify your address.'); }, 100); } finally { deliveryFeeLoading = false; } updateDeliveryFeeUI(); // Don't call renderMenu - just update UI elements to avoid closing the section } function updateDeliveryFeeUI() { if (!currentModalContent) return; var loadingEl = currentModalContent.querySelector('#delivery-fee-loading'); var successEl = currentModalContent.querySelector('#delivery-fee-success'); var errorEl = currentModalContent.querySelector('#delivery-fee-error'); if (loadingEl) { loadingEl.style.display = deliveryFeeLoading ? 'flex' : 'none'; } if (errorEl) { errorEl.style.display = deliveryFeeError ? 'block' : 'none'; if (deliveryFeeError) { errorEl.innerHTML = '
⚠️
Delivery Not Available
' + deliveryFeeError + '
'; } } if (successEl && deliveryFeeData && deliveryFeeData.success) { successEl.style.display = 'block'; var currency = getCurrencySymbol(restaurant.currency); var typeName = deliveryFeeData.method === 'zone_based' ? deliveryFeeData.zone_matched : deliveryFeeData.fee_breakdown && deliveryFeeData.fee_breakdown.tier_matched ? 'Tiered' : 'Linear'; var distanceText = deliveryFeeData.distance && deliveryFeeData.distance.text ? deliveryFeeData.distance.text : 'N/A'; var travelTimeText = deliveryFeeData.duration && deliveryFeeData.duration.text ? deliveryFeeData.duration.text : 'N/A'; successEl.innerHTML = '
' + typeName + ' • Distance: ' + distanceText + ' • Est. Travel Time: ' + travelTimeText + ' • Total Delivery Fee: ' + currency + deliveryFeeData.delivery_fee.toFixed(2) + '
'; } else if (successEl) { successEl.style.display = 'none'; } } function selectAddress(suggestion) { if (!placesService) return; selectedAddressFromList = true; placesService.getDetails({ placeId: suggestion.placeId, fields: ['formatted_address', 'geometry'] }, function(place, status) { if (status === window.google.maps.places.PlacesServiceStatus.OK && place) { checkoutData.address = place.formatted_address || suggestion.description; if (place.geometry && place.geometry.location) { checkoutData.deliveryLocation = { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }; calculateDeliveryFee(); } autocompleteSessionToken = new window.google.maps.places.AutocompleteSessionToken(); } else { checkoutData.address = suggestion.description; } addressSuggestions = []; showAddressSuggestions = false; var addressInput = currentModalContent.querySelector('#checkout-address'); if (addressInput) addressInput.value = checkoutData.address; var suggestionsEl = currentModalContent.querySelector('.address-autocomplete-suggestions'); if (suggestionsEl) suggestionsEl.remove(); }); } function geocodeManualAddress(address) { if (!window.google || !window.google.maps) { console.log('Google Maps not loaded for geocoding'); return; } var geocoder = new window.google.maps.Geocoder(); geocoder.geocode({ address: address }, function(results, status) { if (status === 'OK' && results[0]) { var location = results[0].geometry.location; checkoutData.deliveryLocation = { lat: location.lat(), lng: location.lng() }; checkoutData.address = results[0].formatted_address; var addressInput = currentModalContent ? currentModalContent.querySelector('#checkout-address') : null; if (addressInput) addressInput.value = checkoutData.address; calculateDeliveryFee(); } else { console.log('Geocoding failed:', status); deliveryFeeError = 'Could not verify this address. Please select from suggestions.'; deliveryFeeData = { success: false, error: deliveryFeeError }; updateDeliveryFeeUI(); } }); } function loadGoogleMaps() { if (googleMapsLoaded || window.google) { if (window.google && window.google.maps && window.google.maps.places) { initGoogleServices(); } return; } if (document.querySelector('script[src*="maps.googleapis.com"]')) { var checkInterval = setInterval(function() { if (window.google && window.google.maps && window.google.maps.places) { clearInterval(checkInterval); googleMapsLoaded = true; initGoogleServices(); } }, 100); setTimeout(function() { clearInterval(checkInterval); }, 10000); return; } if (!GOOGLE_MAPS_API_KEY) { console.log('No Google Maps API key'); return; } var script = document.createElement('script'); script.src = 'https://maps.googleapis.com/maps/api/js?key=' + GOOGLE_MAPS_API_KEY + '&libraries=places'; script.async = true; script.defer = true; script.onload = function() { googleMapsLoaded = true; initGoogleServices(); }; document.head.appendChild(script); } if (GOOGLE_MAPS_API_KEY) { loadGoogleMaps(); } function handleLocateMe() { if (!navigator.geolocation) { alert('Geolocation is not supported by your browser'); return; } gettingLocation = true; var locateBtn = currentModalContent.querySelector('#locate-me-btn'); if (locateBtn) { locateBtn.innerHTML = ' Locating...'; locateBtn.disabled = true; } navigator.geolocation.getCurrentPosition( function(position) { var lat = position.coords.latitude; var lng = position.coords.longitude; checkoutData.deliveryLocation = { lat: lat, lng: lng }; if (window.google && window.google.maps) { var geocoder = new window.google.maps.Geocoder(); geocoder.geocode({ location: { lat: lat, lng: lng } }, function(results, status) { gettingLocation = false; var locateBtn = currentModalContent.querySelector('#locate-me-btn'); if (locateBtn) { locateBtn.innerHTML = 'Locate'; locateBtn.disabled = false; } if (status === 'OK' && results[0]) { checkoutData.address = results[0].formatted_address; var addressInput = currentModalContent.querySelector('#checkout-address'); if (addressInput) addressInput.value = checkoutData.address; calculateDeliveryFee(); } else { checkoutData.address = lat.toFixed(6) + ', ' + lng.toFixed(6); var addressInput = currentModalContent.querySelector('#checkout-address'); if (addressInput) addressInput.value = checkoutData.address; } }); } else { gettingLocation = false; var locateBtn = currentModalContent.querySelector('#locate-me-btn'); if (locateBtn) { locateBtn.innerHTML = 'Locate'; locateBtn.disabled = false; } checkoutData.address = lat.toFixed(6) + ', ' + lng.toFixed(6); var addressInput = currentModalContent.querySelector('#checkout-address'); if (addressInput) addressInput.value = checkoutData.address; } }, function(error) { gettingLocation = false; var locateBtn = currentModalContent.querySelector('#locate-me-btn'); if (locateBtn) { locateBtn.innerHTML = 'Locate'; locateBtn.disabled = false; } var errorMsg = 'Unable to get your location'; if (error.code === 1) { errorMsg = 'Location permission denied. Please allow location access in your browser settings.'; } else if (error.code === 2) { errorMsg = 'Location unavailable. Please check your device\'s location settings.'; } else if (error.code === 3) { errorMsg = 'Location request timed out. Please try again.'; } alert(errorMsg); }, { enableHighAccuracy: false, timeout: 15000, maximumAge: 60000 } ); } function geocodeManualAddress(address) { if (!window.google || !window.google.maps) { console.log('Google Maps not loaded for geocoding'); return; } var geocoder = new window.google.maps.Geocoder(); geocoder.geocode({ address: address }, function(results, status) { if (status === 'OK' && results[0]) { var location = results[0].geometry.location; checkoutData.deliveryLocation = { lat: location.lat(), lng: location.lng() }; checkoutData.address = results[0].formatted_address; var addressInput = currentModalContent ? currentModalContent.querySelector('#checkout-address') : null; if (addressInput) addressInput.value = checkoutData.address; calculateDeliveryFee(); } else { console.log('Geocoding failed:', status); deliveryFeeError = 'Could not verify this address. Please select from suggestions.'; deliveryFeeData = { success: false, error: deliveryFeeError }; updateDeliveryFeeUI(); } }); } if (GOOGLE_MAPS_API_KEY) { loadGoogleMaps(); } function renderMenuItemsOnly(items, container) { if (!container) return; var currency = getCurrencySymbol(restaurant.currency); var isDeliveryEnabled = restaurant.delivery_enabled !== false; var isPickupEnabled = restaurant.pickup_enabled !== false; var isDineInEnabled = restaurant.dine_in_enabled === true; var isAnyServiceEnabled = isDeliveryEnabled || isPickupEnabled || isDineInEnabled; var html = ''; items.forEach(function(item) { var isTemporarilyUnavailable = item.not_available_until && new Date(item.not_available_until) > new Date(); var itemCountInCart = cart.filter(function(c) { return c.id === item.id; }).reduce(function(sum, c) { return sum + c.quantity; }, 0); var hasOptions = item.options && item.options.length > 0; html += ''; }); if (items.length === 0) { html = '
No items found
'; } container.innerHTML = html; // Re-attach add button listeners var addBtns = container.querySelectorAll('.add-btn'); addBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var item = menuItems.find(function(i) { return i.id === itemId; }); if (item) { if (item.options && item.options.length > 0) { handleItemClick(item); } else { var notesInput = container.querySelector('.menu-item-notes[data-item-id="' + itemId + '"]'); var notes = notesInput ? notesInput.value : ''; var qty = itemQuantities[itemId] || 1; for (var i = 0; i < qty; i++) { addToCart(item, null, notes); } if (notesInput) notesInput.value = ''; itemQuantities[itemId] = 1; } } }; }); // Attach quantity button listeners var qtyDecreaseBtns = container.querySelectorAll('.qty-decrease-btn'); qtyDecreaseBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var current = itemQuantities[itemId] || 1; if (current > 1) { itemQuantities[itemId] = current - 1; renderMenuItemsOnly(getFilteredItems(), container); } }; }); var qtyIncreaseBtns = container.querySelectorAll('.qty-increase-btn'); qtyIncreaseBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var current = itemQuantities[itemId] || 1; itemQuantities[itemId] = current + 1; renderMenuItemsOnly(getFilteredItems(), container); }; }); } function initializeInlineMenu(container) { if (!container) return; if (FETCH_ERROR) { container.innerHTML = '
Failed to load menu: ' + FETCH_ERROR + '
'; return; } if (!restaurant) { container.innerHTML = '
Restaurant not found
'; return; } if (!menuItems.length) { container.innerHTML = '
No menu items available
'; return; } // Re-merge cart duplicates each time modal opens var mergedCart = []; cart.forEach(function(item) { var itemOptions = item.selected_options; var itemHasOptions = itemOptions && Object.keys(itemOptions).length > 0; var optionsKey = itemHasOptions ? JSON.stringify(itemOptions) : ''; var existingIndex = -1; for (var i = 0; i < mergedCart.length; i++) { var mergedOptions = mergedCart[i].selected_options; var mergedHasOptions = mergedOptions && Object.keys(mergedOptions).length > 0; var mergedOptionsKey = mergedHasOptions ? JSON.stringify(mergedOptions) : ''; if (mergedCart[i].id === item.id && mergedOptionsKey === optionsKey) { existingIndex = i; break; } } if (existingIndex !== -1) { mergedCart[existingIndex].quantity += item.quantity; } else { mergedCart.push(item); } }); cart = mergedCart; saveCart(); currentModalContent = container; categories = getUniqueCategories(); renderMenu(); } function renderMenu() { if (!currentModalContent) return; var currency = getCurrencySymbol(restaurant.currency); var filteredItems = getFilteredItems(); var cartTotal = getCartTotal(); var cartCount = getCartCount(); var isDeliveryEnabled = restaurant.delivery_enabled !== false; var isPickupEnabled = restaurant.pickup_enabled !== false; var isDineInEnabled = restaurant.dine_in_enabled === true; var isAnyServiceEnabled = isDeliveryEnabled || isPickupEnabled || isDineInEnabled; // Build the menu HTML var html = '
'; // Header for inline mode (same as modal) if (DISPLAY_MODE === 'inline') { var headerTextColor = restaurant.theme_header_text_color || '#1E293B'; html += '
'; html += '

' + (restaurant.name || 'Order Online') + '

'; html += '
'; html += ''; html += '
'; } html += '
'; // Service unavailable banner if (!isAnyServiceEnabled) { html += '
⚠️ This restaurant is currently not accepting orders.
'; } // Estimated times banner var timeBannerParts = []; if (isPickupEnabled && restaurant.estimated_pickup_time) { timeBannerParts.push('🛍️ Pickup: ' + restaurant.estimated_pickup_time + ' min'); } if (timeBannerParts.length > 0) { html += '
' + timeBannerParts.join(' • ') + '
'; } // Categories - sticky horizontal scroll like mobile view var buttonColor = restaurant.theme_button_color || '#f97316'; html += '
'; html += ''; html += '
'; // Menu items grid html += '
'; filteredItems.forEach(function(item) { var isTemporarilyUnavailable = item.not_available_until && new Date(item.not_available_until) > new Date(); var itemCountInCart = cart.filter(function(c) { return c.id === item.id; }).reduce(function(sum, c) { return sum + c.quantity; }, 0); var hasOptions = item.options && item.options.length > 0; html += ''; }); html += '
'; // Floating cart button with hover preview if (cartCount > 0 && !cartOpen && !checkoutOpen && !confirmationOpen) { var lastItem = cart[cart.length - 1]; // Get total quantity of last item type in cart var lastItemTotalQty = cart.filter(function(c) { return c.id === lastItem.id; }).reduce(function(sum, c) { return sum + c.quantity; }, 0); var lastItemName = lastItem.name.length > 20 ? lastItem.name.substring(0, 20) + '...' : lastItem.name; html += '
'; // Expanded cart preview (hidden by default, shows on hover) - matches cart page design html += '
'; // Scrollable items area html += '
'; html += '
🛒 Cart (' + cartCount + ')
'; cart.slice().reverse().forEach(function(item, idx) { var isNewest = idx === 0; html += '
'; if (item.image_url) { html += '' + item.name + ''; } html += '
'; html += '
' + item.quantity + 'x ' + item.name + '
'; if (item.selected_options && Object.keys(item.selected_options).length > 0) { var optText = []; Object.keys(item.selected_options).forEach(function(optName) { var optVal = item.selected_options[optName]; if (Array.isArray(optVal) && optVal.length > 0) { optText.push(optVal.join(', ')); } else if (optVal && typeof optVal === 'string') { optText.push(optVal); } }); if (optText.length > 0) { html += '
' + optText.join(' • ') + '
'; } } if (item.special_instructions) { html += '
📝 ' + item.special_instructions + '
'; } html += '
' + currency + item.price.toFixed(2) + ' × ' + item.quantity + '' + currency + (item.price * item.quantity).toFixed(2) + '
'; html += '
'; }); html += '
Total' + currency + cartTotal.toFixed(2) + '
'; html += '
'; // Sticky action buttons at bottom html += '
'; html += ''; html += ''; html += '
'; html += '
'; // Main cart button html += '
'; html += '
✓ ' + lastItemTotalQty + 'x ' + lastItemName + '
'; html += '
🛒 ' + cartCount + ' item' + (cartCount > 1 ? 's' : '') + ' • ' + currency + cartTotal.toFixed(2) + '
'; html += '
'; html += '
'; } html += '
'; // Account/Tracking overlay if (accountOpen) { html = renderAccountPage(currency); } // Orders view for logged in users if (trackingOpen && currentUser) { // Will be handled by renderOrdersView } // Cart overlay if (cartOpen) { html = renderCartPage(currency); } // Checkout overlay if (checkoutOpen) { html = renderCheckoutPage(currency); } // Confirmation overlay if (confirmationOpen) { if (currentOrderData) { html = renderConfirmationPage(currency); } else { // Show loading state while fetching order data html = '
' + '
' + '
' + '

Loading Order...

' + '

Please wait while we fetch your order details.

' + '
' + '
'; } } // Options modal if (optionsModalOpen && selectedItemForOptions) { html += renderOptionsModal(currency); } currentModalContent.innerHTML = html; attachEventListeners(); // Initialize map if confirmation page with tracking data if (confirmationOpen && currentOrderData) { console.log('[Embedder Map] confirmationOpen=true, checking for map data...'); console.log('[Embedder Map] Status:', currentOrderData.status); console.log('[Embedder Map] Has driver_location:', !!currentOrderData.driver_location); console.log('[Embedder Map] Has delivery_location:', !!currentOrderData.delivery_location); console.log('[Embedder Map] Has restaurant_location:', !!currentOrderData.restaurant_location); if (currentOrderData.status === 'out_for_delivery' && currentOrderData.driver_location && currentOrderData.delivery_location && currentOrderData.restaurant_location) { console.log('[Embedder Map] All conditions met, loading map...'); loadLeafletAndInitMap(currentOrderData); } else { console.log('[Embedder Map] Conditions not met, skipping map'); } } // Update delivery fee UI after render if (checkoutOpen) { updateDeliveryFeeUI(); // Auto-focus address field when delivery type is selected and address section is active if (checkoutData.orderType === 'delivery' && checkoutData.openSection === 'address') { setTimeout(function() { var addressField = currentModalContent.querySelector('#checkout-address'); if (addressField) { addressField.focus(); addressField.classList.add('base44-pulse-field'); } }, 300); } } // Mount Stripe if needed if (checkoutOpen && checkoutData.paymentMethod === 'stripe' && STRIPE_PUBLISHABLE_KEY && stripeInstance) { setTimeout(function() { if (!cardElement) mountStripeCard(); }, 100); } } function renderCartPage(currency) { var cartTotal = getCartTotal(); var cartCount = getCartCount(); var html = '
'; // Header with back button html += '
'; html += ''; html += '

🛒 Cart (' + cartCount + ')

'; html += '
'; html += '
'; if (cart.length === 0) { html += '
'; html += '
🛒
'; html += '

Your cart is empty

'; html += '

Add items to get started!

'; html += '
'; } else { // Grid layout for larger screens, single column for mobile html += '
'; cart.forEach(function(item, index) { html += '
'; // Desktop: Large image at top, Mobile: Small image inline if (item.image_url) { html += '' + item.name + ''; } html += '
'; // Item header row with delete button (mobile shows small image here) html += '
'; if (item.image_url) { html += ''; } html += '
'; html += '
'; html += '

' + item.name + '

'; html += ''; html += '
'; // Options in compact form if (item.selected_options && Object.keys(item.selected_options).length > 0) { var optText = []; Object.keys(item.selected_options).forEach(function(optName) { var optVal = item.selected_options[optName]; if (Array.isArray(optVal) && optVal.length > 0) { optText.push(optVal.join(', ')); } else if (optVal && typeof optVal === 'string') { optText.push(optVal); } }); if (optText.length > 0) { html += '
' + optText.join(' • ') + '
'; } } html += '
'; html += '
'; // Special instructions - styled with pen icon like embedderInline html += '
'; if (item.special_instructions) { html += '
' + item.special_instructions + '
'; html += ''; } else { html += ''; } html += '
'; // Price row below note: unit price - qty - total html += '
'; html += '' + currency + item.price.toFixed(2) + ''; html += '
'; html += ''; html += '' + item.quantity + ''; html += ''; html += '
'; html += '' + currency + (item.price * item.quantity).toFixed(2) + ''; html += '
'; html += '
'; // close padding div html += '
'; // close card }); html += '
'; // Add responsive styles for cart images html += ''; } // Floating checkout button - compact if (cart.length > 0) { html += '
'; html += '
'; html += '
'; html += '
Subtotal
'; html += '
' + currency + cartTotal.toFixed(2) + '
'; html += '
'; html += ''; html += '
'; } html += '
'; return html; } function renderCheckoutPage(currency) { var cartTotal = getCartTotal(); var tipAmount = getTipAmount(); var deliveryFee = getDeliveryFee(); var discount = calculateDiscount(); var feeBreakdown = getFeeBreakdown(); var otherFeesTotal = feeBreakdown.reduce(function(sum, fee) { return sum + fee.amount; }, 0); var orderTotal = getOrderTotal(); var isDeliveryEnabled = restaurant.delivery_enabled !== false; var isPickupEnabled = restaurant.pickup_enabled !== false; var isDineInEnabled = restaurant.dine_in_enabled === true; var paymentMethods = getAvailablePaymentMethods(); // Determine completion status var isOrderTypeComplete = !!checkoutData.orderType; var isTimingComplete = false; if (checkoutData.orderType === 'dine_in') { var dateTimeParts = checkoutData.scheduledTime ? checkoutData.scheduledTime.split('T') : []; var hasDate = dateTimeParts[0] && dateTimeParts[0].trim().length > 0; var hasTime = dateTimeParts[1] && dateTimeParts[1].trim().length > 0 && dateTimeParts[1].indexOf(':') > -1; isTimingComplete = !!(checkoutData.partySize && hasDate && hasTime); } else { if (checkoutData.timingType === 'asap') { isTimingComplete = true; } else if (checkoutData.timingType === 'scheduled') { isTimingComplete = !!(checkoutData.scheduledTime && checkoutData.scheduledTime.indexOf(':') > -1); } } var isAddressComplete = true; if (checkoutData.orderType === 'delivery') { isAddressComplete = !!(checkoutData.address && checkoutData.address.length > 3); } var isContactComplete = !!(checkoutData.name && checkoutData.phone && checkoutData.email); // Determine active section (accordion logic) var nextIncomplete = 'type'; if (isOrderTypeComplete) { if (checkoutData.orderType === 'delivery') { // Delivery Flow: Type -> Address -> Time -> Contact nextIncomplete = 'address'; if (isAddressComplete) nextIncomplete = 'time'; if (isAddressComplete && isTimingComplete) nextIncomplete = 'contact'; } else if (checkoutData.orderType === 'dine_in') { // Dine-in Flow: Type -> Booking (combined) -> Contact nextIncomplete = 'booking'; if (isTimingComplete && checkoutData.partySize) nextIncomplete = 'contact'; } else { // Pickup Flow: Type -> Time -> Contact nextIncomplete = 'time'; if (isTimingComplete) nextIncomplete = 'contact'; } } if (isOrderTypeComplete && isTimingComplete && isAddressComplete && isContactComplete) nextIncomplete = 'payment'; // CRITICAL: Define activeSection here for use throughout renderCheckoutPage // ALWAYS keep payment section open var activeSection = checkoutData.openSection || nextIncomplete; var isPaymentSectionActive = true; // Payment section is ALWAYS open // Helper to render accordion section function renderSection(id, title, icon, isComplete, contentHtml, badgeText) { // Payment section is ALWAYS open var isActive = (id === 'payment') ? true : (activeSection === id); var isDisabled = false; // Determine if disabled (must complete previous steps) if (id === 'time') { if (!isOrderTypeComplete) isDisabled = true; if (checkoutData.orderType === 'delivery' && !isAddressComplete) isDisabled = true; } if (id === 'address' && !isOrderTypeComplete) isDisabled = true; if (id === 'contact') { if (!isOrderTypeComplete || !isTimingComplete) isDisabled = true; if (checkoutData.orderType === 'delivery' && !isAddressComplete) isDisabled = true; } if (['payment','notes','promo','tip'].includes(id)) { if (!isOrderTypeComplete || !isTimingComplete || !isContactComplete) isDisabled = true; if (checkoutData.orderType === 'delivery' && !isAddressComplete) isDisabled = true; } // Special case: Address only shows for delivery if (id === 'address' && checkoutData.orderType !== 'delivery') return ''; var headerColor = isActive ? '#1e293b' : (isComplete ? '#059669' : '#64748b'); var iconColor = isActive ? (restaurant.theme_button_color || '#f97316') : (isComplete ? '#059669' : '#94a3b8'); var isRequired = ['contact', 'dine_in_contact', 'payment'].includes(id); var borderColor = isActive ? (restaurant.theme_button_color || '#f97316') : (!isComplete && !isDisabled && isRequired ? '#ef4444' : '#e2e8f0'); var pulseClass = (!isComplete && !isDisabled && isRequired && !isActive) ? 'base44-pulse-field' : ''; var sectionHtml = '
'; // Header sectionHtml += '
'; sectionHtml += '
'; sectionHtml += '
' + (isComplete && !isActive ? '✅' : icon) + '
'; sectionHtml += '
' + title + '
'; sectionHtml += '
'; if (badgeText && !isActive) { sectionHtml += '
'; sectionHtml += '
' + badgeText + '
'; if (isComplete) { sectionHtml += '
✏️
'; } else { sectionHtml += '
'; } sectionHtml += '
'; } else { sectionHtml += '
'; } sectionHtml += '
'; // Content if (isActive) { sectionHtml += '
' + contentHtml + '
'; } sectionHtml += '
'; return sectionHtml; } var html = '
'; // Back buttons html += '
'; html += ''; html += ''; html += '
'; html += '

Checkout

'; html += ''; html += '
'; html += '
'; // --- SECTION 1: Order Type --- var typeHtml = '
'; var typePulseClass = !checkoutData.orderType ? ' base44-pulse-field' : ''; if (isPickupEnabled) typeHtml += ''; if (isDeliveryEnabled) typeHtml += ''; if (isDineInEnabled) typeHtml += ''; typeHtml += '
'; html += renderSection('type', 'Order Type', '🛍️', isOrderTypeComplete, typeHtml, checkoutData.orderType ? (checkoutData.orderType === 'dine_in' ? 'Dine In' : checkoutData.orderType.charAt(0).toUpperCase() + checkoutData.orderType.slice(1)) : ''); // TIMING CONTENT var timeHtml = ''; var timeLabel = checkoutData.orderType === 'dine_in' ? 'Order Timing & Party Size' : 'Order Timing'; var timeBadge = 'Required'; if (checkoutData.orderType === 'dine_in') { var pickerBg = restaurant.theme_background_color || '#f8fafc'; var dateNotSelected = !checkoutData.scheduledTime || !checkoutData.scheduledTime.split('T')[0]; var timeNotSelected = !checkoutData.scheduledTime || !checkoutData.scheduledTime.split('T')[1]; timeHtml += ''; timeHtml += '
'; timeHtml += '
'; timeHtml += '
'; timeHtml += '
'; if (checkoutData.scheduledTime && checkoutData.scheduledTime.indexOf(':') > -1) { var sched = checkoutData.scheduledTime.split('T'); var dateParts = sched[0].split('-'); var timeH = parseInt(sched[1].split(':')[0]); var timeMin = sched[1].split(':')[1]; var h12 = (timeH % 12 === 0) ? 12 : (timeH % 12); var ampm = timeH < 12 ? 'AM' : 'PM'; timeBadge = dateParts[2] + '/' + dateParts[1] + '/' + dateParts[0] + ' ' + String(h12).padStart(2, '0') + ':' + timeMin + ' ' + ampm; } else { timeBadge = 'Select Date & Time'; } } else if (checkoutData.orderType !== 'dine_in') { var isAsap = checkoutData.timingType === 'asap'; var isScheduled = checkoutData.timingType === 'scheduled'; var timingPulseClass = !checkoutData.timingType ? ' base44-pulse-field' : ''; timeHtml += ''; timeHtml += '
'; timeHtml += ''; timeHtml += ''; timeHtml += '
'; if (isScheduled) { var pickerBg = restaurant.theme_background_color || '#f8fafc'; var dateNotSelected = !checkoutData.scheduledTime || !checkoutData.scheduledTime.split('T')[0]; var timeNotSelected = !checkoutData.scheduledTime || !checkoutData.scheduledTime.split('T')[1]; timeHtml += ''; timeHtml += '
'; timeHtml += '
'; timeHtml += '
'; timeHtml += '
'; } if (isAsap) { timeBadge = 'ASAP'; } else if (isScheduled) { if (checkoutData.scheduledTime && checkoutData.scheduledTime.indexOf(':') > -1) { var sched = checkoutData.scheduledTime.split('T'); var dateParts = sched[0].split('-'); var timeH = parseInt(sched[1].split(':')[0]); var timeMin = sched[1].split(':')[1]; var h12 = (timeH % 12 === 0) ? 12 : (timeH % 12); var ampm = timeH < 12 ? 'AM' : 'PM'; timeBadge = dateParts[2] + '/' + dateParts[1] + '/' + dateParts[0] + ' ' + String(h12).padStart(2, '0') + ':' + timeMin + ' ' + ampm; } else { timeBadge = 'Select Date & Time'; } } else { timeBadge = 'Choose ASAP or Schedule'; } } // ADDRESS CONTENT var addrHtml = ''; if (checkoutData.orderType === 'delivery') { addrHtml += '
'; addrHtml += '
'; addrHtml += '
'; addrHtml += '
📍
'; var addrPulseClass = !checkoutData.address ? ' class="base44-pulse-field"' : ''; addrHtml += ''; addrHtml += ''; addrHtml += '
'; addrHtml += ''; addrHtml += ''; addrHtml += ''; addrHtml += ''; addrHtml += '
'; } // RENDER SECTIONS IN CORRECT ORDER if (checkoutData.orderType === 'delivery') { html += renderSection('address', 'Delivery Address', '📍', isAddressComplete, addrHtml, checkoutData.address ? (checkoutData.address.length > 35 ? checkoutData.address.substring(0,35)+'...' : checkoutData.address) : ''); html += renderSection('time', timeLabel, '⏰', isTimingComplete, timeHtml, timeBadge); } else if (checkoutData.orderType === 'dine_in') { // Split into 3 separate sections for dine-in // Section 1: Timing var timingSummary = ''; if (checkoutData.scheduledTime) { var sched = checkoutData.scheduledTime.split('T'); var dateParts = sched[0].split('-'); var timeH = parseInt(sched[1].split(':')[0]); var timeMin = sched[1].split(':')[1]; var h12 = (timeH % 12 === 0) ? 12 : (timeH % 12); var ampm = timeH < 12 ? 'AM' : 'PM'; timingSummary = dateParts[2] + '/' + dateParts[1] + '/' + dateParts[0] + ' ' + String(h12).padStart(2, '0') + ':' + timeMin + ' ' + ampm; } html += renderSection('dine_in_time', 'Order Timing', '⏰', isTimingComplete, timeHtml, timingSummary || 'Select Date & Time'); // Section 2: Party Size var formatPartySize = function(n) { if (!n) return 'Select Party Size'; return n + ' ' + (n === 1 ? 'person' : 'people'); }; var partySizeHtml = '
'; var partySizeClass = !checkoutData.partySize ? 'base44-pulse-field' : ''; partySizeHtml += '
'; for (var ps = 1; ps <= 10; ps++) { var isSelected = checkoutData.partySize === ps; var btnColor = isSelected ? (restaurant.theme_button_color || '#f97316') : '#f8fafc'; var btnTextColor = isSelected ? (restaurant.theme_button_text_color || '#fff') : '#1e293b'; var btnBorder = isSelected ? (restaurant.theme_button_color || '#f97316') : '#e2e8f0'; partySizeHtml += ''; } partySizeHtml += '
'; html += renderSection('dine_in_party', 'Party Size', '👥', !!checkoutData.partySize, partySizeHtml, checkoutData.partySize ? formatPartySize(checkoutData.partySize) : ''); // Section 3: Contact Info var contactHtml = '
'; var namePulse = !checkoutData.name ? ' class="base44-pulse-field"' : ''; var phonePulse = !checkoutData.phone ? ' class="base44-pulse-field"' : ''; var emailPulse = !checkoutData.email ? ' class="base44-pulse-field"' : ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += '
'; var contactSummary = checkoutData.name; if (checkoutData.phone) contactSummary += ' • ' + checkoutData.phone; html += renderSection('dine_in_contact', 'Contact Information', '👤', isContactComplete, contactHtml, contactSummary || ''); } else { html += renderSection('time', timeLabel, '⏰', isTimingComplete, timeHtml, timeBadge); } // --- SECTION 4: Contact Info (for delivery/pickup) --- if (checkoutData.orderType !== 'dine_in') { var contactHtml = '
'; var namePulse = !checkoutData.name ? ' class="base44-pulse-field"' : ''; var phonePulse = !checkoutData.phone ? ' class="base44-pulse-field"' : ''; var emailPulse = !checkoutData.email ? ' class="base44-pulse-field"' : ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += ''; contactHtml += '
'; var contactSummary = checkoutData.name; if (checkoutData.phone) contactSummary += ' • ' + checkoutData.phone; html += renderSection('contact', 'Contact Information', '👤', isContactComplete, contactHtml, contactSummary || ''); } // --- SECTION 5: Special Instructions (Optional) --- var notesHtml = ''; var noteSummary = checkoutData.notes ? (checkoutData.notes.length > 25 ? checkoutData.notes.substring(0, 25) + '...' : checkoutData.notes) : '(Optional)'; html += renderSection('notes', 'Special Instructions', '📝', !!checkoutData.notes, notesHtml, noteSummary); // --- SECTION 6: Promo Code (Optional) --- var promoHtml = ''; if (checkoutData.appliedPromo) { promoHtml += '
'; promoHtml += '' + checkoutData.appliedPromo.name + ''; promoHtml += ''; promoHtml += '
'; } else { promoHtml += '
'; } html += renderSection('promo', 'Promo Code', '🎁', !!checkoutData.appliedPromo, promoHtml, checkoutData.appliedPromo ? checkoutData.appliedPromo.promo_code : '(Optional)'); // --- SECTION 7: Tip (Optional) --- var tipsSettings = restaurant.tips_settings || {}; var tipsGlobalEnabled = tipsSettings.enabled; var orderTypeKey = checkoutData.orderType === 'delivery' ? 'delivery_enabled' : checkoutData.orderType === 'pickup' ? 'pickup_enabled' : 'dine_in_enabled'; var tipsEnabledForOrderType = tipsGlobalEnabled && tipsSettings[orderTypeKey]; var tipHtml = ''; if (tipsEnabledForOrderType) { tipHtml += '
'; // Tip Amount Display tipHtml += '
'; tipHtml += 'Tip Amount'; tipHtml += '
'; tipHtml += '
' + currency + tipAmount.toFixed(2) + '
'; tipHtml += '
' + (checkoutData.tipType === 'percentage' ? checkoutData.tipPercentage + '% of subtotal' : 'Custom amount') + '
'; tipHtml += '
'; // Tip Type Toggle (if both enabled) var showPercentage = tipsSettings.percentage_enabled; var showFlat = tipsSettings.flat_amount_enabled; // Default fallback if undefined if (showPercentage === undefined) showPercentage = true; if (showFlat === undefined) showFlat = false; if (showPercentage && showFlat) { tipHtml += '
'; tipHtml += ''; tipHtml += ''; tipHtml += '
'; } else if (!showPercentage && showFlat) { // Force flat amount if percentage is disabled checkoutData.tipType = 'flat_amount'; } else if (showPercentage && !showFlat) { checkoutData.tipType = 'percentage'; } if (checkoutData.tipType === 'percentage' && showPercentage) { var percentages = tipsSettings.default_percentages || [10, 15, 20, 25]; tipHtml += '
'; // Slider tipHtml += '
'; tipHtml += '
0%15%30%
'; tipHtml += '
'; tipHtml += '
'; percentages.forEach(function(pct){ tipHtml += ''; }); tipHtml += '
'; } else if (showFlat) { var flatAmounts = tipsSettings.default_flat_amounts || [2, 5, 10]; tipHtml += '
'; flatAmounts.forEach(function(amt){ tipHtml += ''; }); tipHtml += '
'; tipHtml += '
'; } tipHtml += '
'; } html += renderSection('tip', 'Add a Tip', '💚', tipAmount > 0, tipHtml, tipAmount > 0 ? currency + tipAmount.toFixed(2) : (tipsEnabledForOrderType ? '(Optional)' : 'Not available')); // --- SECTION 8: Payment Method --- // Check if all required fields are complete to enable payment selection var canSelectPayment = isOrderTypeComplete && isTimingComplete && isAddressComplete && isContactComplete; var payHtml = '
'; if (paymentMethods.length > 0) { var payPulse = !checkoutData.paymentMethod ? ' class="base44-pulse-field"' : ''; var isDisabled = !canSelectPayment; payHtml += ''; if (isDisabled) { payHtml += '
⚠️ Please complete all required sections above to select payment
'; } if (checkoutData.paymentMethod === 'stripe') { payHtml += '
'; payHtml += '
'; } } else { payHtml += '
No payment methods available
'; } payHtml += '
'; var paymentMethodName = ''; if (checkoutData.paymentMethod) { var pm = paymentMethods.find(function(p) { return p.id === checkoutData.paymentMethod; }); paymentMethodName = pm ? pm.name : (checkoutData.paymentMethod === 'stripe' ? 'Card' : checkoutData.paymentMethod.replace(/_/g, ' ')); } html += renderSection('payment', 'Payment Method', '💳', !!checkoutData.paymentMethod, payHtml, paymentMethodName); // Close Left Column html += '
'; // Right Column (Summary) html += '
'; html += '
'; html += '

📋 Order Summary

'; cart.forEach(function(item) { html += '
'; html += '
' + item.quantity + 'x ' + item.name + '' + currency + (item.price * item.quantity).toFixed(2) + '
'; // Display options in checkout if (item.selected_options && Object.keys(item.selected_options).length > 0) { var optText = []; Object.keys(item.selected_options).forEach(function(optName) { var optVal = item.selected_options[optName]; if (Array.isArray(optVal) && optVal.length > 0) { optText.push(optVal.join(', ')); } else if (optVal && typeof optVal === 'string') { optText.push(optVal); } }); if (optText.length > 0) { html += '
' + optText.join(' • ') + '
'; } } if (item.special_instructions) { html += '
📝 ' + item.special_instructions + '
'; } html += '
'; }); var vatCalc = calculateVAT(); html += '
'; html += '
Subtotal' + currency + cartTotal.toFixed(2) + '
'; if (tipAmount > 0) { html += '
Tip' + currency + tipAmount.toFixed(2) + '
'; } if (deliveryFee > 0) { html += '
🚗 Delivery Fee' + currency + deliveryFee.toFixed(2) + '
'; } if (feeBreakdown.length > 0) { feeBreakdown.forEach(function(fee) { if(fee.amount > 0) { html += '
' + fee.name + (fee.type === 'percentage' ? ' (' + fee.value + '%)' : '') + '' + currency + fee.amount.toFixed(2) + '
'; } }); } if (discount > 0) { html += '
Discount-' + currency + discount.toFixed(2) + '
'; } if (vatCalc.vat_amount > 0) { html += '
VAT (' + vatCalc.vat_rate + '%)' + currency + vatCalc.vat_amount.toFixed(2) + '
'; } html += '
Total' + currency + orderTotal.toFixed(2) + '
'; html += '
'; html += '
'; // Spacer for floating button html += '
'; // Floating sticky payment button at bottom html += '
'; html += '
'; if (checkoutData.paymentMethod === 'stripe' && STRIPE_PUBLISHABLE_KEY) { html += ''; } else { html += ''; } html += '
'; return html; } function renderAccountPage(currency) { // This should not be called anymore - redirect to login popup renderLoginPopup(); return ''; } function renderConfirmationPage(currency) { var order = currentOrderData; console.log('[Embedder] Rendering confirmation page - order status:', order ? order.status : 'NO ORDER'); var statusConfig = { pending: { icon: '⏳', title: 'Order Pending', desc: "We're reviewing your order. You'll be notified once it's confirmed.", color: '#d97706', bgColor: '#fef3c7' }, confirmed: { icon: '✅', title: 'Confirmed', desc: 'Great! We have confirmed your order and will start preparing it soon.', color: '#059669', bgColor: '#d1fae5' }, preparing: { icon: '👨‍🍳', title: 'Being Prepared', desc: 'Our kitchen is working on your order right now!', color: '#ea580c', bgColor: '#ffedd5' }, ready: { icon: '🛍️', title: 'Ready', desc: order.order_type === 'delivery' ? 'Your order is ready and waiting for pickup by our delivery driver.' : 'Your order is ready for pickup! Please come to the restaurant.', color: '#2563eb', bgColor: '#dbeafe' }, out_for_delivery: { icon: '🚗', title: 'Out for Delivery', desc: 'Your order is on the way! It should arrive soon.', color: '#7c3aed', bgColor: '#ede9fe' }, completed: { icon: '✅', title: order.order_type === 'delivery' ? 'Delivered' : order.order_type === 'pickup' ? 'Picked Up' : 'Completed', desc: order.order_type === 'delivery' ? 'Your order has been delivered. Enjoy your meal!' : order.order_type === 'pickup' ? 'Your order has been picked up. Enjoy your meal!' : 'Your order is complete. Enjoy your meal!', color: '#059669', bgColor: '#d1fae5' }, cancelled: { icon: '❌', title: 'Cancelled', desc: order.rejection_reason ? 'This order was cancelled. Reason: ' + order.rejection_reason.replace(/_/g, ' ') : 'This order has been cancelled.', color: '#dc2626', bgColor: '#fee2e2' } }; var status = statusConfig[order.status] || statusConfig.pending; var stages = ['pending', 'confirmed', 'preparing', 'ready']; if (order.order_type === 'delivery') stages.push('out_for_delivery'); stages.push('completed'); // Helper function to format time remaining or overdue function formatTimeRemaining(ms) { var isOverdue = ms < 0; var absMs = Math.abs(ms); var totalSeconds = Math.floor(absMs / 1000); var hours = Math.floor(totalSeconds / 3600); var minutes = Math.floor((totalSeconds % 3600) / 60); var seconds = totalSeconds % 60; var timeStr = ''; if (hours > 0) timeStr = hours + ' hr ' + minutes + ' min'; else if (minutes > 0) timeStr = minutes + ' min ' + seconds + ' sec'; else timeStr = seconds + ' sec'; return { timeStr: timeStr, isOverdue: isOverdue }; } // Helper function to format date/time function formatDateTime(dateStr) { if (!dateStr) return null; var d = new Date(dateStr); return d.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }); } function formatTime(dateStr) { if (!dateStr) return null; var d = new Date(dateStr); return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); } // Calculate time remaining or overdue var timeRemaining = null; var expectedAtTime = null; var isOverdue = false; if (order.estimated_ready_time && ['confirmed', 'preparing', 'ready', 'out_for_delivery'].includes(order.status)) { var estimatedTime = new Date(order.estimated_ready_time).getTime(); var now = Date.now(); var remaining = estimatedTime - now; var timeData = formatTimeRemaining(remaining); timeRemaining = timeData.timeStr; isOverdue = timeData.isOverdue; expectedAtTime = formatTime(order.estimated_ready_time); } var requestedTime = order.scheduled_time ? formatDateTime(order.scheduled_time) : null; var html = '
'; // Optimized layout container with min-width fix for grid items html += '
'; html += ''; html += '
'; // Left side - Status and Progress html += '
'; html += '
'; html += '
' + status.icon + '
'; html += '

' + status.title + '

'; html += '

' + status.desc + '

'; // Expected Time Display with Countdown/Overdue if (order.estimated_ready_time && ['confirmed', 'preparing', 'ready', 'out_for_delivery'].includes(order.status)) { var bgGradient = isOverdue ? 'linear-gradient(135deg, #fee2e2, #fecaca)' : 'linear-gradient(135deg, #ecfdf5, #d1fae5)'; var borderColor = isOverdue ? '#FF0400' : '#2BB595'; var iconBg = isOverdue ? '#FF0400' : '#2BB595'; var labelColor = isOverdue ? '#991b1b' : '#059669'; var valueColor = isOverdue ? '#7f1d1d' : '#065f46'; html += '
'; html += '
'; html += '
' + (isOverdue ? '⚠️' : '⏱️') + '
'; html += '
'; if (timeRemaining) { html += '
' + (isOverdue ? 'OVERDUE' : 'Time Remaining') + '
'; html += '
' + timeRemaining + '
'; } if (expectedAtTime) { html += '
Expected: ' + expectedAtTime + '
'; } html += '
'; } // Requested/Scheduled Time if (requestedTime) { html += '
'; html += '
'; html += '📅'; html += '
'; html += '
Requested Time
'; html += '
' + requestedTime + '
'; html += '
'; } // Order Info Grid - mobile friendly html += '
'; html += '
Restaurant:' + (restaurant.name || 'Restaurant') + '
'; html += '
Order Type:' + (order.order_type === 'delivery' ? '🚗 DELIVERY' : order.order_type === 'dine_in' ? '🍽️ DINE IN' : '🛍️ PICKUP') + '
'; if (order.table_number) { html += '
Table:' + order.table_number + '
'; } if (order.party_size) { html += '
Guests:' + order.party_size + ' people
'; } html += '
Order ID:#' + order.id.slice(0, 8).toUpperCase() + '
'; html += '
Payment:' + (order.payment_status || 'PENDING').toUpperCase() + '
'; html += '
Method:' + (order.payment_method ? order.payment_method.replace(/_/g, ' ').toUpperCase() : 'N/A') + '
'; html += '
Total:' + currency + (order.total || 0).toFixed(2) + '
'; html += '
'; html += '
'; // Helper function to get stage timestamp function getStageTimestamp(order, stage) { switch (stage) { case 'pending': return order.created_date; case 'confirmed': return order.confirmed_at; case 'preparing': return order.preparing_started_at; case 'ready': return order.ready_at; case 'out_for_delivery': return order.out_for_delivery_at; case 'completed': return order.completed_at; default: return null; } } // Progress Tracker - responsive with modern design html += '
'; html += '

Order Progress

'; html += '
'; html += ''; html += '
'; stages.forEach(function(stage, idx) { var isActive = stages.indexOf(order.status) >= idx; var isCurrent = order.status === stage; var stageConfig = statusConfig[stage] || { icon: '⏳', title: stage, color: '#64748b', bgColor: '#e2e8f0' }; var timestamp = getStageTimestamp(order, stage); html += '
'; // Connecting line (horizontal line between stages) if (idx < stages.length - 1) { var lineColor = isActive && !isCurrent ? '#22c55e' : isActive ? stageConfig.color : '#cbd5e1'; html += '
'; } // Circle with icon html += '
' + stageConfig.icon + '
'; // Status title and timestamp html += '
'; html += '
' + stageConfig.title + '
'; if (timestamp) { var timestampDate = new Date(timestamp); var formattedTime = timestampDate.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); html += '
' + formattedTime + '
'; } html += '
'; html += '
'; }); html += '
'; html += '
'; // Live Tracking Map - only for out_for_delivery orders if (order.status === 'out_for_delivery' && order.driver_location && order.delivery_location && order.restaurant_location) { html += '
'; html += '
🚗 Live Tracking
'; html += '
'; // Driver info card below map html += '
'; html += '
🚗
'; html += '
'; if (order.driver_name) { html += '
' + order.driver_name + '
'; } if (order.driver_phone) { html += '📞' + order.driver_phone + ''; } if (order.driver_vehicle_type && order.driver_vehicle_number) { html += '
🚙' + order.driver_vehicle_type.toUpperCase() + ' • ' + order.driver_vehicle_number + '
'; } html += '
'; html += '
On the way
'; html += '
'; html += '
'; } // Action Buttons - responsive html += '
'; html += ''; html += ''; html += '
'; html += '
'; // Right side - Order Items - responsive html += '
'; html += '
'; html += '

🛍️ Order Items

'; (order.items || []).forEach(function(item) { html += '
'; html += '
'; html += '
' + (item.is_prepared ? '✅ ' : '') + item.quantity + 'x ' + item.item_name + '
'; // Display options in confirmation if (item.selected_options && Array.isArray(item.selected_options) && item.selected_options.length > 0) { var optStr = item.selected_options.map(function(opt) { return opt.selected_choices.map(function(c) { return c.choice_name; }).join(', '); }).join(' • '); html += '
' + optStr + '
'; } if (item.special_instructions) { html += '
📝 ' + item.special_instructions + '
'; } if (item.is_prepared) { html += '✓ Prepared'; } html += '
'; html += '
' + currency + (item.price * item.quantity).toFixed(2) + '
'; html += '
'; }); // Order Totals - mobile friendly html += '
'; html += '
Subtotal' + currency + (order.subtotal || 0).toFixed(2) + '
'; if (order.tip_amount > 0) { html += '
💚 Tip' + currency + order.tip_amount.toFixed(2) + '
'; } if (order.delivery_fee > 0) { html += '
🚗 Delivery Fee' + currency + order.delivery_fee.toFixed(2) + '
'; } if (order.other_fees_breakdown && order.other_fees_breakdown.length > 0) { order.other_fees_breakdown.forEach(function(fee) { if (fee.amount > 0) { html += '
' + fee.name + '' + currency + fee.amount.toFixed(2) + '
'; } }); } else if (order.other_fees > 0) { // Fallback for older orders html += '
Other Fees' + currency + order.other_fees.toFixed(2) + '
'; } if (order.discount_amount > 0) { html += '
Discount-' + currency + order.discount_amount.toFixed(2) + '
'; } if (order.vat_amount > 0) { html += '
VAT (' + order.vat_rate + '%)' + currency + order.vat_amount.toFixed(2) + '
'; } html += '
Total' + currency + (order.total || 0).toFixed(2) + '
'; html += '
'; // Special Notes - mobile friendly if (order.special_notes) { html += '
📝 Special Notes:
' + order.special_notes + '
'; } html += '
'; html += '
'; html += '
'; // Add pulse animation for current status and payment method var confirmPulseColor = restaurant.theme_button_color || '#f97316'; var confirmPulseRgb = hexToRgb(confirmPulseColor); html += ''; html += '
'; return html; } function renderOptionsModal(currency) { var item = selectedItemForOptions; console.log('[Embedder Options Render] ============================='); console.log('[Embedder Options Render] Item:', item.name); console.log('[Embedder Options Render] Item.options structure:', JSON.stringify(item.options, null, 2)); console.log('[Embedder Options Render] selectedOptions:', JSON.stringify(selectedOptions)); console.log('[Embedder Options Render] ============================='); // Calculate total price with options var calculatedPrice = item.price; if (item.options) { item.options.forEach(function(optGroup) { var selections = selectedOptions[optGroup.name] || []; selections.forEach(function(selectedChoice) { var choice = optGroup.choices.find(function(c) { return c.name === selectedChoice; }); if (choice && choice.price_modifier) { calculatedPrice += choice.price_modifier; } }); }); } var totalPrice = calculatedPrice * optionsQuantity; var html = '
'; html += '
'; // Header html += '
'; html += '

' + item.name + '

'; html += ''; html += '
'; // Scrollable content html += '
'; // Image if (item.image_url) { html += '
'; html += '' + item.name + ''; html += '
'; } // Description if (item.description) { html += '

' + item.description + '

'; } // Base Price html += '
'; html += 'Base Price:'; html += '' + currency + item.price.toFixed(2) + ''; html += '
'; // Option Groups if (item.options && item.options.length > 0) { html += '
'; item.options.forEach(function(optGroup) { console.log('[Embedder Options Render] Processing group:', optGroup.name, 'Type:', optGroup.type); html += '
'; // Group header with badge html += '
'; html += '

' + optGroup.name + '

'; if (optGroup.required) { html += '*'; } html += ''; if (optGroup.type === 'single_select') { html += 'Choose one'; } else { var minSel = optGroup.min_selections || 0; var maxSel = optGroup.max_selections || 'any'; html += 'Select ' + minSel + '-' + maxSel; } html += ''; html += '
'; // Choices html += '
'; optGroup.choices.forEach(function(choice) { var isSelected = false; var currentSelection = selectedOptions[optGroup.name]; if (optGroup.type === 'single_select') { if (Array.isArray(currentSelection)) { isSelected = currentSelection.length > 0 && currentSelection[0] === choice.name; } else if (currentSelection) { isSelected = currentSelection === choice.name; } } else { if (Array.isArray(currentSelection)) { isSelected = currentSelection.indexOf(choice.name) !== -1; } } var buttonBg = isSelected ? (restaurant.theme_button_color || '#f97316') : '#f8fafc'; var buttonColor = isSelected ? (restaurant.theme_button_text_color || '#fff') : (restaurant.theme_text_color || '#1E293B'); var buttonBorder = isSelected ? (restaurant.theme_button_color || '#f97316') : '#e2e8f0'; html += ''; }); html += '
'; html += '
'; }); html += '
'; } // Special instructions html += '
'; html += ''; html += ''; html += '
'; // Quantity html += '
'; html += 'Quantity:'; html += '
'; html += ''; html += '' + optionsQuantity + ''; html += ''; html += '
'; html += '
'; // Footer with action buttons html += '
'; html += ''; html += '
'; html += '
'; return html; } function loadLeafletAndInitMap(order) { // Load Leaflet CSS if not already loaded if (!document.querySelector('link[href*="leaflet.css"]')) { var leafletCSS = document.createElement('link'); leafletCSS.rel = 'stylesheet'; leafletCSS.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; leafletCSS.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY='; leafletCSS.crossOrigin = ''; document.head.appendChild(leafletCSS); } // Load Leaflet JS if not already loaded if (!window.L) { var leafletScript = document.createElement('script'); leafletScript.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; leafletScript.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo='; leafletScript.crossOrigin = ''; leafletScript.onload = function() { setTimeout(function() { initTrackingMap(order); }, 500); }; document.head.appendChild(leafletScript); } else { setTimeout(function() { initTrackingMap(order); }, 500); } } function initTrackingMap(order) { if (!window.L) { console.error('Leaflet not loaded'); return; } var mapDiv = document.getElementById('live-tracking-map-' + order.id); if (!mapDiv) { console.log('Map div not found'); return; } if (mapDiv.offsetHeight === 0) { mapDiv.style.height = '300px'; mapDiv.style.width = '100%'; } try { // Clear any existing map while (mapDiv.firstChild) { mapDiv.removeChild(mapDiv.firstChild); } // Fix Leaflet icon paths delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png' }); // Create map var map = L.map(mapDiv, { zoomControl: true, scrollWheelZoom: false, dragging: true, tap: false }).setView([order.driver_location.lat, order.driver_location.lng], 14); // Add Google Maps tiles L.tileLayer('https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', { maxZoom: 20, attribution: '© Google Maps' }).addTo(map); // Custom icons var driverIcon = L.icon({ iconUrl: 'https://static.vecteezy.com/system/resources/thumbnails/036/876/825/small/3d-delivery-man-character-deivering-package-with-a-scooter-free-png.png', iconSize: [50, 50], iconAnchor: [25, 25], popupAnchor: [0, -25] }); var restaurantIcon = L.icon({ iconUrl: 'https://static.vecteezy.com/system/resources/thumbnails/066/380/758/small/restaurant-icon-3d-rendering-illustration-png.png', iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40] }); var customerIcon = L.icon({ iconUrl: 'https://cdn3d.iconscout.com/3d/premium/thumb/home-3d-icon-png-download-8383048.png', iconSize: [40, 40], iconAnchor: [20, 40], popupAnchor: [0, -40] }); // Add markers L.marker([order.driver_location.lat, order.driver_location.lng], { icon: driverIcon }) .addTo(map) .bindPopup('🚗 Driver' + (order.driver_name ? '
' + order.driver_name : '') + (order.driver_phone ? '
📞 ' + order.driver_phone : '')); L.marker([order.restaurant_location.lat, order.restaurant_location.lng], { icon: restaurantIcon }) .addTo(map) .bindPopup('🍴 Restaurant'); L.marker([order.delivery_location.lat, order.delivery_location.lng], { icon: customerIcon }) .addTo(map) .bindPopup('🏠 Delivery Address'); // Draw route line var routeCoords = [ [order.driver_location.lat, order.driver_location.lng], [order.delivery_location.lat, order.delivery_location.lng] ]; L.polyline(routeCoords, { color: '#7c3aed', weight: 4, opacity: 0.7 }).addTo(map); // Fit bounds to show all markers var bounds = L.latLngBounds([ [order.restaurant_location.lat, order.restaurant_location.lng], [order.driver_location.lat, order.driver_location.lng], [order.delivery_location.lat, order.delivery_location.lng] ]); map.fitBounds(bounds, { padding: [40, 40] }); // Force map to recalculate size after render setTimeout(function() { try { map.invalidateSize(); } catch(e) {} }, 300); } catch (e) { console.error('Map init error:', e); mapDiv.innerHTML = '
Map unavailable
'; } } function attachEventListeners() { if (!currentModalContent) return; // Search toggle and handlers var modalSearchToggle = currentModalContent.querySelector('#modal-search-toggle'); var modalSearchClose = currentModalContent.querySelector('#modal-search-close'); var modalSearchInput = currentModalContent.querySelector('#modal-search-input'); if (modalSearchToggle) { modalSearchToggle.onclick = function(e) { e.preventDefault(); e.stopPropagation(); modalSearchToggle.style.display = 'none'; if (modalSearchInput) { modalSearchInput.style.display = 'block'; modalSearchInput.value = searchQuery || ''; modalSearchInput.focus(); } if (modalSearchClose) modalSearchClose.style.display = 'flex'; }; } if (modalSearchClose) { modalSearchClose.onclick = function(e) { e.preventDefault(); e.stopPropagation(); searchQuery = ''; if (modalSearchInput) modalSearchInput.style.display = 'none'; modalSearchClose.style.display = 'none'; if (modalSearchToggle) modalSearchToggle.style.display = 'flex'; renderMenu(); }; } if (modalSearchInput) { // Prevent renderMenu from stealing focus modalSearchInput.onkeyup = function(e) { searchQuery = e.target.value; if (e.key === 'Enter') { e.target.blur(); } }; modalSearchInput.onchange = function(e) { searchQuery = e.target.value; }; // Live search - filter items as user types var searchTimeout = null; modalSearchInput.oninput = function(e) { searchQuery = e.target.value; clearTimeout(searchTimeout); searchTimeout = setTimeout(function() { // Update only the menu items grid, not the whole page var filteredItems = getFilteredItems(); var menuGrid = currentModalContent.querySelector('[data-menu-grid]'); if (menuGrid) { // Re-render just the items renderMenuItemsOnly(filteredItems, menuGrid); } }, 300); }; } // Accordion headers var checkoutHeaders = currentModalContent.querySelectorAll('.checkout-header'); checkoutHeaders.forEach(function(header) { header.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var id = header.dataset.id; // PAYMENT SECTION: Always keep open, don't allow collapse if (id === 'payment') { return; // Do nothing - keep payment section always open } // Toggle: if already open, close it; otherwise open it if (checkoutData.openSection === id) { checkoutData.openSection = null; } else { checkoutData.openSection = id; } renderMenu(); }; }); // Category buttons var catBtns = currentModalContent.querySelectorAll('.cat-btn'); catBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); selectedCategory = btn.dataset.category; // Clear search when clicking a category searchQuery = ''; renderMenu(); }; }); // Add buttons var addBtns = currentModalContent.querySelectorAll('.add-btn'); addBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var item = menuItems.find(function(i) { return i.id === itemId; }); if (item) { // Check if item has options if (item.options && item.options.length > 0) { handleItemClick(item); } else { // Get notes from the input field var notesInput = currentModalContent.querySelector('.menu-item-notes[data-item-id="' + itemId + '"]'); var notes = notesInput ? notesInput.value : ''; var qty = itemQuantities[itemId] || 1; for (var i = 0; i < qty; i++) { addToCart(item, null, notes); } if (notesInput) notesInput.value = ''; itemQuantities[itemId] = 1; if (window.showCartPreviewBriefly) window.showCartPreviewBriefly(); } } }; }); // Quantity buttons var qtyDecreaseBtns = currentModalContent.querySelectorAll('.qty-decrease-btn'); qtyDecreaseBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var current = itemQuantities[itemId] || 1; if (current > 1) { itemQuantities[itemId] = current - 1; renderMenu(); } }; }); var qtyIncreaseBtns = currentModalContent.querySelectorAll('.qty-increase-btn'); qtyIncreaseBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var itemId = btn.dataset.itemId; var current = itemQuantities[itemId] || 1; itemQuantities[itemId] = current + 1; renderMenu(); }; }); // Floating cart button with hover preview var floatingCartBtn = currentModalContent.querySelector('#floating-cart-btn'); var floatingCartContainer = currentModalContent.querySelector('#floating-cart-container'); var floatingCartPreview = currentModalContent.querySelector('#floating-cart-preview'); if (floatingCartBtn) { floatingCartBtn.onclick = function(e) { e.preventDefault(); openCart(); }; } var previewTimeout = null; if (floatingCartContainer && floatingCartPreview) { floatingCartContainer.onmouseenter = function() { if (previewTimeout) clearTimeout(previewTimeout); floatingCartPreview.style.display = 'flex'; }; floatingCartContainer.onmouseleave = function() { floatingCartPreview.style.display = 'none'; }; } // Auto-show preview briefly when item added window.showCartPreviewBriefly = function() { if (floatingCartPreview) { if (previewTimeout) clearTimeout(previewTimeout); floatingCartPreview.style.display = 'flex'; previewTimeout = setTimeout(function() { floatingCartPreview.style.display = 'none'; }, 4000); } }; // Preview action buttons var previewViewCartBtn = currentModalContent.querySelector('#preview-view-cart-btn'); var previewCheckoutBtn = currentModalContent.querySelector('#preview-checkout-btn'); if (previewViewCartBtn) { previewViewCartBtn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); openCart(); }; } if (previewCheckoutBtn) { previewCheckoutBtn.onclick = async function(e) { e.preventDefault(); e.stopPropagation(); await goToCheckout(); }; } // Cart back button var cartBackBtn = currentModalContent.querySelector('#cart-back'); if (cartBackBtn) { cartBackBtn.onclick = function(e) { e.preventDefault(); closeCart(); }; } // Cart quantity buttons var cartQtyBtns = currentModalContent.querySelectorAll('.cart-qty-btn'); cartQtyBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); var index = parseInt(btn.dataset.index); var action = btn.dataset.action; if (action === 'increase') { cart[index].quantity += 1; } else if (action === 'decrease') { if (cart[index].quantity > 1) { cart[index].quantity -= 1; } else { cart.splice(index, 1); } } saveCart(); if (cart.length === 0) { cartOpen = false; } renderMenu(); }; }); // Cart remove buttons var cartRemoveBtns = currentModalContent.querySelectorAll('.cart-remove-btn'); cartRemoveBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); var index = parseInt(btn.dataset.index); cart.splice(index, 1); saveCart(); if (cart.length === 0) { cartOpen = false; } renderMenu(); }; }); // Edit instructions buttons var editInstructionsBtns = currentModalContent.querySelectorAll('.edit-instructions-btn'); editInstructionsBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var index = parseInt(btn.dataset.index); var instructionsDiv = btn.closest('.cart-item-instructions'); if (instructionsDiv) { var existingText = cart[index].special_instructions || ''; instructionsDiv.style.flexDirection = 'column'; instructionsDiv.style.alignItems = 'stretch'; instructionsDiv.innerHTML = ''; var textarea = instructionsDiv.querySelector('.instructions-textarea'); var saveBtn = instructionsDiv.querySelector('.save-instructions-btn'); if (saveBtn) { saveBtn.onclick = function(ev) { ev.preventDefault(); ev.stopPropagation(); var newText = textarea ? textarea.value.trim() : ''; cart[index].special_instructions = newText; saveCart(); renderMenu(); }; } if (textarea) { setTimeout(function() { textarea.focus({ preventScroll: true }); }, 100); } } }; }); // Proceed to checkout button var proceedCheckoutBtn = currentModalContent.querySelector('#proceed-checkout-btn'); if (proceedCheckoutBtn) { proceedCheckoutBtn.onclick = async function(e) { e.preventDefault(); await goToCheckout(); }; } // Checkout back button var checkoutBackBtn = currentModalContent.querySelector('#checkout-back'); if (checkoutBackBtn) { checkoutBackBtn.onclick = function(e) { e.preventDefault(); closeCheckout(); }; } // Checkout back to cart button var checkoutBackCartBtn = currentModalContent.querySelector('#checkout-back-cart'); if (checkoutBackCartBtn) { checkoutBackCartBtn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); checkoutOpen = false; cartOpen = true; renderMenu(); }; } // Mount Stripe if needed if (checkoutOpen && checkoutData.paymentMethod === 'stripe' && STRIPE_PUBLISHABLE_KEY && stripeInstance) { setTimeout(function() { if (!cardElement) mountStripeCard(); mountPaymentRequestButton(); }, 100); } var orderTypeBtns = currentModalContent.querySelectorAll('.order-type-btn'); orderTypeBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); console.log('[Embedder] >>> Order type button clicked:', btn.dataset.type); checkoutData.orderType = btn.dataset.type; checkoutData.paymentMethod = ''; // Reset timing selection when switching order type checkoutData.timingType = null; // Auto-open next section based on order type if (btn.dataset.type === 'delivery') { checkoutData.openSection = 'address'; } else if (btn.dataset.type === 'dine_in') { checkoutData.scheduledTime = ''; checkoutData.dineInInitialized = true; checkoutData.openSection = 'dine_in_time'; } else { // pickup checkoutData.openSection = 'time'; } renderMenu(); }; }); // Payment method select var paymentSelect = currentModalContent.querySelector('#payment-method-select'); if (paymentSelect) { paymentSelect.onchange = function(e) { console.log('[Embedder] Payment method changed to:', e.target.value); // VALIDATION: Ensure order type is selected before allowing payment selection if (!checkoutData.orderType) { alert('Please select an order type (Delivery, Pickup, or Dine In) first'); e.target.value = ''; checkoutData.openSection = 'type'; renderMenu(); return; } if (cardElement) { try { cardElement.unmount(); } catch(err) {} cardElement = null; stripeElements = null; } stripeError = ''; checkoutData.paymentMethod = e.target.value; console.log('[Embedder] checkoutData.paymentMethod set to:', checkoutData.paymentMethod); renderMenu(); }; } // Inline My Orders button var inlineMyOrdersBtn = currentModalContent.querySelector('#inline-my-orders-btn'); if (inlineMyOrdersBtn) { inlineMyOrdersBtn.onclick = function(e) { e.preventDefault(); openAccount(); }; inlineMyOrdersBtn.onmouseover = function() { this.style.background = 'rgba(255,255,255,0.3)'; }; inlineMyOrdersBtn.onmouseout = function() { this.style.background = 'rgba(255,255,255,0.2)'; }; } // Input fields var nameInput = currentModalContent.querySelector('#checkout-name'); var phoneInput = currentModalContent.querySelector('#checkout-phone'); var emailInput = currentModalContent.querySelector('#checkout-email'); var addressInput = currentModalContent.querySelector('#checkout-address'); var notesInput = currentModalContent.querySelector('#checkout-notes'); if (nameInput) { nameInput.oninput = function(e) { checkoutData.name = e.target.value; }; } if (phoneInput) { phoneInput.oninput = function(e) { checkoutData.phone = e.target.value; }; } if (emailInput) { emailInput.oninput = function(e) { checkoutData.email = e.target.value; }; } // Save contact button (delivery/pickup) var saveContactBtn = currentModalContent.querySelector('#save-contact-btn'); if (saveContactBtn) { saveContactBtn.onclick = function(e) { e.preventDefault(); if (checkoutData.name && checkoutData.phone && checkoutData.email) { checkoutData.openSection = 'payment'; renderMenu(); } }; } // Save dine-in contact button var saveDineInContactBtn = currentModalContent.querySelector('#save-dine-in-contact-btn'); if (saveDineInContactBtn) { saveDineInContactBtn.onclick = function(e) { e.preventDefault(); if (checkoutData.name && checkoutData.phone && checkoutData.email) { checkoutData.openSection = 'payment'; renderMenu(); } }; } // Save address button var saveAddressBtn = currentModalContent.querySelector('#save-address-btn'); if (saveAddressBtn) { saveAddressBtn.onclick = function(e) { e.preventDefault(); if (checkoutData.address && checkoutData.address.length > 3 && deliveryFeeData && deliveryFeeData.success) { checkoutData.openSection = 'time'; renderMenu(); } }; }; // Marketing opt-in checkbox var marketingCheckbox = currentModalContent.querySelector('#checkout-marketing'); if (marketingCheckbox) { marketingCheckbox.onchange = function(e) { checkoutData.marketingOptIn = e.target.checked; }; } // Party size input for dine-in var partySizeInput = currentModalContent.querySelector('#party-size-input'); if (partySizeInput) { partySizeInput.oninput = function(e) { checkoutData.partySize = e.target.value ? parseInt(e.target.value) : null; }; } if (addressInput) { var geocodeDebounceTimer = null; addressInput.oninput = function(e) { checkoutData.address = e.target.value; checkoutData.deliveryLocation = null; selectedAddressFromList = false; if (autocompleteService) { searchAddresses(e.target.value); } else if (GOOGLE_MAPS_API_KEY && !googleMapsLoaded) { loadGoogleMaps(); } // Debounced geocoding on each keystroke if (geocodeDebounceTimer) clearTimeout(geocodeDebounceTimer); if (checkoutData.address && checkoutData.address.length >= 5) { geocodeDebounceTimer = setTimeout(function() { if (!selectedAddressFromList) { geocodeManualAddress(checkoutData.address); } }, 800); } }; // Removed auto-advance on blur - user controls via Save & Continue button addressInput.onfocus = function() { if (!autocompleteService && window.google && window.google.maps && window.google.maps.places) { initGoogleServices(); } }; addressInput.onblur = function() { setTimeout(function() { if (showAddressSuggestions) { showAddressSuggestions = false; var suggestionsEl = currentModalContent.querySelector('.address-autocomplete-suggestions'); if (suggestionsEl) suggestionsEl.remove(); } }, 300); }; addressInput.onkeydown = function(e) { if (e.key === 'Enter') { e.preventDefault(); if (geocodeDebounceTimer) clearTimeout(geocodeDebounceTimer); if (checkoutData.address) { geocodeManualAddress(checkoutData.address); } } }; } if (notesInput) notesInput.oninput = function(e) { checkoutData.notes = e.target.value; }; // Locate me button var locateMeBtn = currentModalContent.querySelector('#locate-me-btn'); if (locateMeBtn) { locateMeBtn.onclick = function(e) { e.preventDefault(); handleLocateMe(); }; } // Schedule buttons var scheduleBtns = currentModalContent.querySelectorAll('.schedule-btn'); scheduleBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); if (btn.dataset.schedule === 'asap') { checkoutData.timingType = 'asap'; checkoutData.scheduledTime = ''; // Auto-advance to contact when ASAP is selected checkoutData.openSection = 'contact'; } else { checkoutData.timingType = 'scheduled'; // Don't initialize date - keep section open for user to select both checkoutData.scheduledTime = ''; } renderMenu(); }; }); // Generate unified date/time picker function renderUnifiedPicker() { var calCont = currentModalContent.querySelector('#calendar-container'); var timeCont = currentModalContent.querySelector('#time-container'); if (!calCont || !timeCont) return; var currentDate = checkoutData.scheduledTime ? new Date(checkoutData.scheduledTime.split('T')[0]) : new Date(); var year = currentDate.getFullYear(); var month = currentDate.getMonth(); // Store current view month if (!window.pickerMonth) window.pickerMonth = month; if (!window.pickerYear) window.pickerYear = year; month = window.pickerMonth; year = window.pickerYear; // Render calendar var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var firstDay = new Date(year, month, 1); var lastDay = new Date(year, month + 1, 0); var daysInMonth = lastDay.getDate(); var startDay = firstDay.getDay(); var themeButtonColor = restaurant.theme_button_color || '#f97316'; var themeButtonTextColor = restaurant.theme_button_text_color || '#fff'; var dateSelectedClass = !checkoutData.scheduledTime || !checkoutData.scheduledTime.split('T')[0] ? ' base44-pulse-field' : ''; var calHtml = '
'; calHtml += '
'; calHtml += ''; calHtml += '
' + monthNames[month] + ' ' + year + '
'; calHtml += ''; calHtml += '
'; calHtml += '
'; var dayLabels = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; dayLabels.forEach(function(day) { calHtml += '
' + day + '
'; }); calHtml += '
'; calHtml += '
'; for (var i = 0; i < startDay; i++) { calHtml += '
'; } var today = new Date(); today.setHours(0, 0, 0, 0); for (var i = 1; i <= daysInMonth; i++) { var dateStr = year + '-' + String(month + 1).padStart(2, '0') + '-' + String(i).padStart(2, '0'); var isSelected = checkoutData.scheduledTime && checkoutData.scheduledTime.startsWith(dateStr); var checkDate = new Date(dateStr); var isPast = checkDate < today; calHtml += ''; } calHtml += '
'; calCont.innerHTML = calHtml; // Render times with AM/PM - fixed colors var timeHtml = ''; var hasDateSelected = checkoutData.scheduledTime && checkoutData.scheduledTime.split('T')[0]; for (var h = 0; h < 24; h++) { var hour24 = String(h).padStart(2, '0'); var hour12 = (h % 12 === 0) ? 12 : (h % 12); var ampm = h < 12 ? 'AM' : 'PM'; var timeStr = hour24 + ':00'; var displayTime = hour12 + ':00 ' + ampm; var isSelected = checkoutData.scheduledTime && checkoutData.scheduledTime.endsWith(timeStr); var isDisabled = !hasDateSelected; timeHtml += ''; } timeCont.innerHTML = timeHtml; // Month navigation var prevBtn = currentModalContent.querySelector('.month-prev-btn'); var nextBtn = currentModalContent.querySelector('.month-next-btn'); if (prevBtn) { prevBtn.onclick = function(e) { e.preventDefault(); window.pickerMonth--; if (window.pickerMonth < 0) { window.pickerMonth = 11; window.pickerYear--; } renderUnifiedPicker(); }; } if (nextBtn) { nextBtn.onclick = function(e) { e.preventDefault(); window.pickerMonth++; if (window.pickerMonth > 11) { window.pickerMonth = 0; window.pickerYear++; } renderUnifiedPicker(); }; } // Attach calendar listeners var calBtns = currentModalContent.querySelectorAll('.cal-day-btn'); calBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); var dateVal = btn.dataset.date; var currentTime = checkoutData.scheduledTime ? checkoutData.scheduledTime.split('T')[1] : ''; checkoutData.scheduledTime = dateVal + 'T' + currentTime; // Auto-advance if both date AND time are selected AND not empty var dateTimeParts = checkoutData.scheduledTime.split('T'); var hasDate = dateTimeParts[0] && dateTimeParts[0].trim().length > 0; var hasTime = dateTimeParts[1] && dateTimeParts[1].trim().length > 0 && dateTimeParts[1].indexOf(':') > -1; if (hasDate && hasTime) { if (checkoutData.orderType === 'dine_in') { checkoutData.openSection = 'dine_in_party'; } else { checkoutData.openSection = 'contact'; } } renderUnifiedPicker(); renderMenu(); }; }); // Attach time listeners var timeBtns = currentModalContent.querySelectorAll('.time-slot-btn'); timeBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); var timeVal = btn.dataset.time; var currentDate = checkoutData.scheduledTime ? checkoutData.scheduledTime.split('T')[0] : ''; checkoutData.scheduledTime = currentDate + 'T' + timeVal; // Auto-advance to next section when time selected if (checkoutData.orderType === 'dine_in') { checkoutData.openSection = 'dine_in_party'; } else { checkoutData.openSection = 'contact'; } renderUnifiedPicker(); renderMenu(); }; }); } // Initialize unified picker when needed var unifiedPicker = currentModalContent.querySelector('#unified-picker'); if (unifiedPicker) { renderUnifiedPicker(); } // Party size buttons var partySizeBtns = currentModalContent.querySelectorAll('.party-size-btn'); partySizeBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var size = parseInt(btn.dataset.partySize); checkoutData.partySize = size; var partySizePicker = currentModalContent.querySelector('.party-size-picker'); if (partySizePicker) partySizePicker.classList.remove('base44-pulse-field'); // Auto-advance to contact info when party size is set checkoutData.openSection = 'dine_in_contact'; renderMenu(); }; }); // Tip type buttons var tipTypeBtns = currentModalContent.querySelectorAll('.tip-type-btn'); tipTypeBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); checkoutData.tipType = btn.dataset.tipType; checkoutData.tipPercentage = 0; checkoutData.tipFlatAmount = 0; renderMenu(); }; }); // Tip percentage buttons var tipPercentBtns = currentModalContent.querySelectorAll('.tip-percent-btn'); tipPercentBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); checkoutData.tipPercentage = parseInt(btn.dataset.percent) || 0; checkoutData.openSection = 'payment'; renderMenu(); }; }); // Tip flat amount buttons var tipFlatBtns = currentModalContent.querySelectorAll('.tip-flat-btn'); tipFlatBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); checkoutData.tipFlatAmount = parseFloat(btn.dataset.amount) || 0; checkoutData.openSection = 'payment'; renderMenu(); }; }); // Custom tip amount input var tipCustomInput = currentModalContent.querySelector('#tip-custom-amount'); if (tipCustomInput) { tipCustomInput.onchange = function(e) { checkoutData.tipFlatAmount = parseFloat(e.target.value) || 0; checkoutData.openSection = 'payment'; renderMenu(); }; tipCustomInput.onblur = function(e) { checkoutData.tipFlatAmount = parseFloat(e.target.value) || 0; checkoutData.openSection = 'payment'; renderMenu(); }; } // Inline My Orders button var inlineMyOrdersBtn = currentModalContent.querySelector('#inline-my-orders-btn'); if (inlineMyOrdersBtn) { inlineMyOrdersBtn.onclick = function(e) { e.preventDefault(); openAccount(); }; } // Promo code handlers var promoInput = currentModalContent.querySelector('#promo-code-input'); if (promoInput) { promoInput.oninput = function(e) { checkoutData.promoCode = e.target.value; }; } var applyPromoBtn = currentModalContent.querySelector('#apply-promo-btn'); if (applyPromoBtn) { applyPromoBtn.onclick = async function(e) { e.preventDefault(); if (!checkoutData.promoCode || !checkoutData.promoCode.trim()) { alert('Please enter a promo code'); return; } try { var response = await fetch(BASE_URL + '/api/functions/validatePromo', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ promo_code: checkoutData.promoCode.trim(), restaurant_id: RESTAURANT_ID, subtotal: getCartTotal() }) }); var data = await response.json(); if (data.success && data.promo) { checkoutData.appliedPromo = data.promo; alert('Promo code "' + data.promo.name + '" applied!'); checkoutData.openSection = 'payment'; renderMenu(); } else { alert(data.error || 'Invalid promo code'); } } catch (err) { alert('Failed to validate promo code'); } }; } var removePromoBtn = currentModalContent.querySelector('#remove-promo-btn'); if (removePromoBtn) { removePromoBtn.onclick = function(e) { e.preventDefault(); checkoutData.appliedPromo = null; checkoutData.promoCode = ''; renderMenu(); }; } // Tip percentage slider var tipSlider = currentModalContent.querySelector('#tip-percentage-slider'); if (tipSlider) { tipSlider.oninput = function(e) { checkoutData.tipPercentage = parseInt(e.target.value) || 0; // Update slider background var percent = (checkoutData.tipPercentage / 30) * 100; tipSlider.style.background = 'linear-gradient(to right, #10b981 0%, #10b981 ' + percent + '%, #e5e7eb ' + percent + '%, #e5e7eb 100%)'; }; tipSlider.onchange = function(e) { checkoutData.tipPercentage = parseInt(e.target.value) || 0; checkoutData.openSection = 'payment'; renderMenu(); }; } // Stripe pay button var stripePayBtn = currentModalContent.querySelector('#stripe-pay-btn'); if (stripePayBtn) { stripePayBtn.onclick = function(e) { e.preventDefault(); if (!checkoutData.name || !checkoutData.phone || !checkoutData.email) { alert('Please fill in your name, phone number, and email address'); return; } if (checkoutData.orderType === 'delivery' && !checkoutData.address) { alert('Please enter your delivery address'); return; } if (checkoutData.orderType === 'delivery' && deliveryFeeData && !deliveryFeeData.success) { alert('❌ ' + (deliveryFeeError || 'This address is outside our delivery range. Please choose a different address or select pickup.')); return; } processStripePayment(); }; } // Place order button var placeOrderBtn = currentModalContent.querySelector('#place-order-btn'); if (placeOrderBtn) { placeOrderBtn.onclick = function(e) { e.preventDefault(); // Save all form values first var nameVal = currentModalContent.querySelector('#checkout-name'); var phoneVal = currentModalContent.querySelector('#checkout-phone'); var emailVal = currentModalContent.querySelector('#checkout-email'); var addressVal = currentModalContent.querySelector('#checkout-address'); var notesVal = currentModalContent.querySelector('#checkout-notes'); var partySizeVal = currentModalContent.querySelector('#party-size-input'); var marketingVal = currentModalContent.querySelector('#checkout-marketing'); if (nameVal) checkoutData.name = nameVal.value; if (phoneVal) checkoutData.phone = phoneVal.value; if (emailVal) checkoutData.email = emailVal.value; if (addressVal) checkoutData.address = addressVal.value; if (notesVal) checkoutData.notes = notesVal.value; if (partySizeVal) checkoutData.partySize = partySizeVal.value ? parseInt(partySizeVal.value) : null; if (marketingVal) checkoutData.marketingOptIn = marketingVal.checked; console.log('[Embedder] Place order clicked - orderType:', checkoutData.orderType, 'paymentMethod:', checkoutData.paymentMethod); console.log('[Embedder] Form values - name:', checkoutData.name, 'phone:', checkoutData.phone, 'email:', checkoutData.email); // Validate delivery address and fee if (checkoutData.orderType === 'delivery') { if (!checkoutData.address || checkoutData.address.length < 3) { alert('Please enter a valid delivery address'); checkoutData.openSection = 'address'; renderMenu(); return; } if (!deliveryFeeData || !deliveryFeeData.success) { alert('❌ ' + (deliveryFeeError || 'Please verify your delivery address is within our delivery range')); checkoutData.openSection = 'address'; renderMenu(); return; } } if (!checkoutData.paymentMethod) { alert('Please select a payment method'); checkoutData.openSection = 'payment'; renderMenu(); return; } console.log('[Embedder] All validations passed, submitting order'); submitOrder(); }; } // Account back button var accountBackBtn = currentModalContent.querySelector('#account-back'); if (accountBackBtn) { accountBackBtn.onclick = function(e) { e.preventDefault(); closeAccount(); }; } // Login button var loginBtn = currentModalContent.querySelector('#login-btn'); if (loginBtn) { loginBtn.onclick = function(e) { e.preventDefault(); handleLogin(); }; } // Confirmation back button var confBackBtn = currentModalContent.querySelector('#conf-back-menu'); if (confBackBtn) { confBackBtn.onclick = function(e) { e.preventDefault(); closeConfirmation(); }; } // Order again button var confOrderAgainBtn = currentModalContent.querySelector('#conf-order-again'); if (confOrderAgainBtn) { confOrderAgainBtn.onclick = function(e) { e.preventDefault(); // Add order items back to cart if (currentOrderData && currentOrderData.items) { currentOrderData.items.forEach(function(orderItem) { var menuItem = menuItems.find(function(m) { return m.id === orderItem.item_id || m.name === orderItem.item_name; }); if (menuItem) { var cartItem = Object.assign({}, menuItem, { quantity: orderItem.quantity, cartId: Date.now() + Math.random(), special_instructions: orderItem.special_instructions || '', selected_options: {} }); cart.push(cartItem); } }); saveCart(); } closeConfirmation(); }; } // Options modal var optionsModalClose = currentModalContent.querySelector('#options-modal-close'); var optionsCancelBtn = currentModalContent.querySelector('#options-cancel-btn'); var optionsModalOverlay = currentModalContent.querySelector('#options-modal-overlay'); var optionsAddBtn = currentModalContent.querySelector('#options-add-btn'); if (optionsModalClose) { optionsModalClose.onclick = function(e) { e.preventDefault(); closeOptionsModal(); }; } if (optionsCancelBtn) { optionsCancelBtn.onclick = function(e) { e.preventDefault(); closeOptionsModal(); }; } if (optionsModalOverlay) { optionsModalOverlay.onclick = function(e) { if (e.target === optionsModalOverlay) closeOptionsModal(); }; } if (optionsAddBtn) { optionsAddBtn.onclick = function(e) { e.preventDefault(); addItemWithOptions(); }; } // Option choice buttons var optionChoiceBtns = currentModalContent.querySelectorAll('.option-choice-btn'); optionChoiceBtns.forEach(function(btn) { btn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); var groupName = btn.dataset.group; var choiceName = btn.dataset.choice; var type = btn.dataset.type; console.log('[Embedder Options] Clicked:', groupName, choiceName, 'Type:', type); console.log('[Embedder Options] Before - selectedOptions:', JSON.stringify(selectedOptions)); // Save current special instructions before updating state var instructionsTextarea = currentModalContent.querySelector('#options-special-instructions'); if (instructionsTextarea) { optionsSpecialInstructions = instructionsTextarea.value; } if (type === 'single_select') { selectedOptions[groupName] = [choiceName]; } else if (type === 'multi_select') { if (!selectedOptions[groupName]) selectedOptions[groupName] = []; var idx = selectedOptions[groupName].indexOf(choiceName); if (idx === -1) { selectedOptions[groupName].push(choiceName); } else { selectedOptions[groupName].splice(idx, 1); } } console.log('[Embedder Options] After - selectedOptions:', JSON.stringify(selectedOptions)); // Update UI without full re-render to prevent flickering var buttonColor = restaurant.theme_button_color || '#f97316'; var buttonTextColor = restaurant.theme_button_text_color || '#fff'; // Get all buttons in this group and update styles var groupBtns = currentModalContent.querySelectorAll('.option-choice-btn[data-group="' + groupName + '"]'); groupBtns.forEach(function(groupBtn) { var btnChoice = groupBtn.dataset.choice; var isNowSelected = false; if (type === 'single_select') { isNowSelected = selectedOptions[groupName] && selectedOptions[groupName][0] === btnChoice; } else { isNowSelected = selectedOptions[groupName] && selectedOptions[groupName].indexOf(btnChoice) !== -1; } // Update button styles if (isNowSelected) { groupBtn.style.background = buttonColor; groupBtn.style.color = buttonTextColor; groupBtn.style.borderColor = buttonColor; // Update text spans to inherit button color var textSpan = groupBtn.querySelector('.choice-text'); var priceSpan = groupBtn.querySelector('.choice-price'); if (textSpan) textSpan.style.color = buttonTextColor; if (priceSpan) { priceSpan.style.color = buttonTextColor; priceSpan.style.opacity = '0.8'; } } else { groupBtn.style.background = '#f8fafc'; groupBtn.style.color = '#1e293b'; groupBtn.style.borderColor = '#e2e8f0'; // Reset text colors var textSpan = groupBtn.querySelector('.choice-text'); var priceSpan = groupBtn.querySelector('.choice-price'); if (textSpan) textSpan.style.color = '#1e293b'; if (priceSpan) { priceSpan.style.color = '#64748b'; priceSpan.style.opacity = '0.6'; } } }); // Calculate and update price - get base price from original menu item var originalItem = menuItems.find(function(mi) { return mi.id === selectedItemForOptions.id; }); var newCalculatedPrice = originalItem ? originalItem.price : selectedItemForOptions.price; if (selectedItemForOptions.options) { selectedItemForOptions.options.forEach(function(optGroup) { var selections = selectedOptions[optGroup.name] || []; selections.forEach(function(selectedChoice) { var choice = optGroup.choices.find(function(c) { return c.name === selectedChoice; }); if (choice && choice.price_modifier) { newCalculatedPrice += choice.price_modifier; } }); }); } var newTotalPrice = newCalculatedPrice * optionsQuantity; var addBtn = currentModalContent.querySelector('#options-add-btn'); if (addBtn) { var currencySymbol = getCurrencySymbol(restaurant.currency); addBtn.textContent = 'Add to Cart - ' + currencySymbol + newTotalPrice.toFixed(2); } console.log('[Embedder Options] Price updated to:', newTotalPrice.toFixed(2)); }; }); // Options special instructions input - persist value var optionsInstructionsInput = currentModalContent.querySelector('#options-special-instructions'); if (optionsInstructionsInput) { optionsInstructionsInput.oninput = function(e) { optionsSpecialInstructions = e.target.value; }; } // Options quantity buttons var optionsQtyDecrease = currentModalContent.querySelector('#options-qty-decrease'); var optionsQtyIncrease = currentModalContent.querySelector('#options-qty-increase'); if (optionsQtyDecrease) { optionsQtyDecrease.onclick = function(e) { e.preventDefault(); if (optionsQuantity > 1) { optionsQuantity--; // Update quantity display and price without re-rendering var qtyDisplay = currentModalContent.querySelector('#options-qty-decrease').nextElementSibling; if (qtyDisplay) { qtyDisplay.textContent = optionsQuantity; } // Recalculate and update price var calculatedPrice = selectedItemForOptions.price; if (selectedItemForOptions.options) { selectedItemForOptions.options.forEach(function(optGroup) { var selections = selectedOptions[optGroup.name] || []; selections.forEach(function(selectedChoice) { var choice = optGroup.choices.find(function(c) { return c.name === selectedChoice; }); if (choice && choice.price_modifier) { calculatedPrice += choice.price_modifier; } }); }); } var totalPrice = calculatedPrice * optionsQuantity; var addBtn = currentModalContent.querySelector('#options-add-btn'); if (addBtn) { addBtn.textContent = 'Add to Cart - ' + getCurrencySymbol(restaurant.currency) + totalPrice.toFixed(2); } // Update decrease button disabled state optionsQtyDecrease.disabled = optionsQuantity <= 1; optionsQtyDecrease.style.cursor = optionsQuantity <= 1 ? 'not-allowed' : 'pointer'; optionsQtyDecrease.style.opacity = optionsQuantity <= 1 ? '0.5' : '1'; } }; } if (optionsQtyIncrease) { optionsQtyIncrease.onclick = function(e) { e.preventDefault(); optionsQuantity++; // Update quantity display and price without re-rendering var qtyDisplay = currentModalContent.querySelector('#options-qty-increase').previousElementSibling; if (qtyDisplay) { qtyDisplay.textContent = optionsQuantity; } // Recalculate and update price var calculatedPrice = selectedItemForOptions.price; if (selectedItemForOptions.options) { selectedItemForOptions.options.forEach(function(optGroup) { var selections = selectedOptions[optGroup.name] || []; selections.forEach(function(selectedChoice) { var choice = optGroup.choices.find(function(c) { return c.name === selectedChoice; }); if (choice && choice.price_modifier) { calculatedPrice += choice.price_modifier; } }); }); } var totalPrice = calculatedPrice * optionsQuantity; var addBtn = currentModalContent.querySelector('#options-add-btn'); if (addBtn) { addBtn.textContent = 'Add to Cart - ' + getCurrencySymbol(restaurant.currency) + totalPrice.toFixed(2); } // Update decrease button enabled state var decreaseBtn = currentModalContent.querySelector('#options-qty-decrease'); if (decreaseBtn) { decreaseBtn.disabled = false; decreaseBtn.style.cursor = 'pointer'; decreaseBtn.style.opacity = '1'; } }; } } function createButton(element, restaurantId, restaurantName) { var buttonColor = EMBEDDED_RESTAURANT?.embedder_button_color || '#fb923c'; var buttonText = EMBEDDED_RESTAURANT?.embedder_button_text || element.textContent || 'Order Online'; var buttonTextColor = EMBEDDED_RESTAURANT?.embedder_button_text_color || '#0f172a'; var borderRadius = typeof EMBEDDED_RESTAURANT?.embedder_button_border_radius === 'number' ? EMBEDDED_RESTAURANT.embedder_button_border_radius : 20; var button = document.createElement('button'); button.style.cssText = 'display: inline-flex !important; align-items: center !important; gap: 0 !important; cursor: pointer !important; transition: all 0.2s !important; border: 2px solid ' + buttonColor + ' !important; background: transparent !important; padding: 0 !important; border-radius: ' + borderRadius + 'px !important; overflow: hidden !important; box-sizing: border-box !important; margin: 12px !important;'; var iconBox = document.createElement('div'); iconBox.style.cssText = 'display: flex !important; align-items: center !important; justify-content: center !important; width: 56px !important; height: 56px !important; background: linear-gradient(135deg, ' + buttonColor + ', ' + adjustColorBrightness(buttonColor, -15) + ') !important; flex-shrink: 0 !important; border-radius: 0 !important;'; iconBox.innerHTML = ''; var textBox = document.createElement('div'); textBox.style.cssText = 'padding: 16px 24px !important; background: transparent !important; color: ' + buttonTextColor + ' !important; font-weight: 600 !important; font-size: 16px !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; flex-shrink: 0 !important; border-radius: 0 !important;'; textBox.textContent = buttonText; button.appendChild(iconBox); button.appendChild(textBox); button.onmouseover = function() { button.style.transform = 'translateY(-2px)'; }; button.onmouseout = function() { button.style.transform = 'translateY(0)'; }; button.onclick = async function(e) { e.preventDefault(); await createModal(restaurantId, restaurantName); }; element.parentNode.replaceChild(button, element); } function adjustColorBrightness(hex, percent) { var num = parseInt(hex.replace('#', ''), 16); var r = Math.max(0, Math.min(255, ((num >> 16) & 0xFF) + percent)); var g = Math.max(0, Math.min(255, ((num >> 8) & 0xFF) + percent)); var b = Math.max(0, Math.min(255, (num & 0xFF) + percent)); return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0'); } function init() { console.log('[Embedder INIT] Starting initialization - Mode:', DISPLAY_MODE); console.log('[Embedder INIT] Restaurant ID:', RESTAURANT_ID); console.log('[Embedder INIT] Document ready state:', document.readyState); injectStyles(); // INLINE MODE - Render directly into container if (DISPLAY_MODE === 'inline') { console.log('[Embedder INIT] Inline mode detected'); var container = document.getElementById('base44-inline-menu'); if (!container) { console.error('[Embedder INIT] Container #base44-inline-menu not found'); return; } console.log('[Embedder INIT] Container found, initializing inline menu'); if (FETCH_ERROR) { container.innerHTML = '
⚠️ Failed to load menu: ' + FETCH_ERROR + '
'; return; } if (!EMBEDDED_RESTAURANT) { container.innerHTML = '
⚠️ Restaurant not found
'; return; } if (!EMBEDDED_MENU_ITEMS || EMBEDDED_MENU_ITEMS.length === 0) { container.innerHTML = '
No menu items available
'; return; } console.log('[Embedder INIT] Rendering inline menu with', EMBEDDED_MENU_ITEMS.length, 'items'); currentModalContent = container; categories = getUniqueCategories(); renderMenu(); return; } // BUTTON MODE - Create buttons if (RESTAURANT_ID) { var elements = document.querySelectorAll('[data-base44-order-button]:not(.base44-initialized)'); console.log('[Embedder INIT] Found', elements.length, 'button elements'); elements.forEach(function(element, idx) { console.log('[Embedder INIT] Button', idx, ':', element.tagName, element.textContent); var restaurantName = element.getAttribute('data-restaurant-name') || ''; element.classList.add('base44-initialized'); createButton(element, RESTAURANT_ID, restaurantName); console.log('[Embedder INIT] Button', idx, 'replaced successfully'); }); } else { console.warn('[Embedder INIT] NO RESTAURANT_ID - cannot initialize buttons'); } var elements2 = document.querySelectorAll('[data-base44-restaurant-id]:not(.base44-initialized)'); console.log('[Embedder INIT] Found', elements2.length, 'legacy button elements'); elements2.forEach(function(element) { var restId = element.getAttribute('data-base44-restaurant-id'); var restaurantName = element.getAttribute('data-base44-restaurant-name'); if (restId) { element.classList.add('base44-initialized'); createButton(element, restId, restaurantName); } }); console.log('[Embedder INIT] Initialization complete!'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } if (typeof MutationObserver !== 'undefined') { var observer = new MutationObserver(function(mutations) { var hasNewElements = mutations.some(function(mutation) { return Array.from(mutation.addedNodes).some(function(node) { return node.nodeType === 1 && ( node.hasAttribute('data-base44-restaurant-id') || node.hasAttribute('data-base44-order-button') || (node.querySelector && (node.querySelector('[data-base44-restaurant-id]') || node.querySelector('[data-base44-order-button]'))) ); }); }); if (hasNewElements) init(); }); observer.observe(document.body, { childList: true, subtree: true }); } window.Base44Embedder = { version: '3.4.0', openMenu: createModal, init: init }; console.log('[Embedder] >>> Script loaded - version 3.4.0'); })();