2026-05-12 18:25:15 -04:00
"""
Generates a self - contained HTML order form from the latest scraped menu .
The form shows this week ' s meals with quantity selectors, a running total,
and submits orders to the backend ( local Flask or cloud API Gateway ) .
Usage :
python3 generate_form . py # local mode
python3 generate_form . py - - api - url URL - - api - key KEY # cloud mode
"""
import argparse
import json
import sys
from datetime import datetime
from pathlib import Path
PROJECT_ROOT = Path ( __file__ ) . resolve ( ) . parents [ 2 ]
OUTPUT_DIR = PROJECT_ROOT / " output "
CONFIG_PATH = PROJECT_ROOT / " config.json "
def load_config ( ) :
with open ( CONFIG_PATH ) as f :
return json . load ( f )
def latest_menu ( ) - > dict :
files = sorted ( OUTPUT_DIR . glob ( " menu-*.json " ) , reverse = True )
if not files :
print ( " Error: No menu JSON found. Run scrape_menu.py first. " , file = sys . stderr )
sys . exit ( 1 )
with open ( files [ 0 ] ) as f :
return json . load ( f )
2026-05-12 19:31:27 -04:00
def generate_form (
2026-05-13 11:36:14 -04:00
menu : dict ,
config : dict ,
api_url : str = " " ,
api_key : str = " " ,
bulk_discount : float = 0 ,
company_subsidy : float = 0 ,
google_client_id : str = " " ,
2026-05-12 19:31:27 -04:00
) - > str :
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
meals_json = json . dumps ( menu [ " meals " ] ) . replace ( " </ " , " < \\ / " )
roster_json = json . dumps ( config . get ( " roster " , [ ] ) ) . replace ( " </ " , " < \\ / " )
2026-05-12 20:16:46 -04:00
deadline = config . get ( " order_deadline " , " Thursday 11:59 PM " )
2026-05-12 18:25:15 -04:00
week = datetime . now ( ) . strftime ( " % Y-W % U " )
scraped_at = menu . get ( " scraped_at " , " unknown " )
submit_url = f " { api_url } /api/submit-order " if api_url else " /api/submit-order "
2026-05-13 13:25:40 -04:00
status_url = (
f " { api_url } /api/form-status/ { week } " if api_url else f " /api/form-status/ { week } "
)
2026-05-12 20:16:46 -04:00
roster_url = f " { api_url } /api/roster " if api_url else " /api/roster "
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
api_key_json = json . dumps ( api_key ) . replace ( " </ " , " < \\ / " )
2026-05-13 11:36:14 -04:00
has_discount = bulk_discount > 0 or company_subsidy > 0
use_google_auth = bool ( google_client_id )
google_client_id_json = json . dumps ( google_client_id )
if use_google_auth :
auth_section_html = """ <div class= " employee-info " >
< div id = " user-info " >
< div style = " display:flex;align-items:center;gap:12px; " >
< img id = " user-avatar " style = " width:40px;height:40px;border-radius:50 % ;object-fit:cover;display:none; " alt = " " >
< div style = " flex:1; " >
< div id = " user-name " style = " font-weight:600;font-size:0.95rem; " > < / div >
< div id = " user-email " style = " font-size:0.85rem;color:#6b7280; " > < / div >
< / div >
< button onclick = " signOut() " style = " background:none;border:1px solid #d1d5db;border-radius:8px;padding:6px 14px;cursor:pointer;font-size:0.8rem;color:#6b7280; " > Sign out < / button >
< / div >
< / div >
< / div > """
else :
auth_section_html = """ <div class= " employee-info " >
< label for = " emp-name " > Your Name < / label >
< select id = " emp-name " > < option value = " " > Loading . . . < / option > < / select >
< input type = " hidden " id = " emp-email " >
< / div > """
if use_google_auth :
google_auth_js = """
function waitForGoogleAuth ( ) {
if ( typeof google != = ' undefined ' & & google . accounts & & google . accounts . id ) {
initGoogleAuth ( ) ;
} else {
setTimeout ( waitForGoogleAuth , 50 ) ;
}
}
function initGoogleAuth ( ) {
google . accounts . id . initialize ( {
client_id : GOOGLE_CLIENT_ID ,
callback : handleCredentialResponse ,
hosted_domain : ' seahavenind.com ' ,
auto_select : true ,
} ) ;
google . accounts . id . renderButton (
document . getElementById ( ' g-signin-btn ' ) ,
{ theme : ' outline ' , size : ' large ' , text : ' signin_with ' , width : 300 }
) ;
}
function handleCredentialResponse ( response ) {
googleCredential = response . credential ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
const b64 = response . credential . split ( ' . ' ) [ 1 ] . replace ( / - / g , ' + ' ) . replace ( / _ / g , ' / ' ) ;
const payload = JSON . parse ( atob ( b64 ) ) ;
2026-05-13 11:36:14 -04:00
googleUser = { name : payload . name , email : payload . email } ;
document . getElementById ( ' auth-overlay ' ) . style . display = ' none ' ;
document . getElementById ( ' app ' ) . style . display = ' block ' ;
var footer = document . getElementById ( ' sticky-footer ' ) ;
if ( footer ) footer . style . display = ' block ' ;
document . getElementById ( ' user-name ' ) . textContent = payload . name ;
document . getElementById ( ' user-email ' ) . textContent = payload . email ;
if ( payload . picture ) {
const avatar = document . getElementById ( ' user-avatar ' ) ;
avatar . src = payload . picture ;
avatar . style . display = ' block ' ;
}
updateTotal ( ) ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
checkDuplicateOrder ( ) ;
2026-05-13 11:36:14 -04:00
}
function signOut ( ) {
googleCredential = null ;
googleUser = null ;
google . accounts . id . disableAutoSelect ( ) ;
document . getElementById ( ' auth-overlay ' ) . style . display = ' flex ' ;
document . getElementById ( ' app ' ) . style . display = ' none ' ;
var footer = document . getElementById ( ' sticky-footer ' ) ;
if ( footer ) footer . style . display = ' none ' ;
}
"""
else :
google_auth_js = " "
if use_google_auth :
submit_order_js = """
async function submitOrder ( ) {
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
if ( formClosed ) { alert ( ' Orders are closed. ' ) ; return ; }
2026-05-13 11:36:14 -04:00
if ( ! googleCredential | | ! googleUser ) { alert ( ' Please sign in with Google first. ' ) ; return ; }
const items = Object . entries ( quantities ) . map ( ( [ i , qty ] ) = > ( {
name : MEALS [ i ] . name ,
retail_price : MEALS [ i ] . price ,
quantity : qty ,
} ) ) ;
const btn = document . getElementById ( ' submit-btn ' ) ;
btn . disabled = true ;
btn . textContent = ' Submitting... ' ;
try {
const headers = { ' Content-Type ' : ' application/json ' } ;
if ( API_KEY ) headers [ ' x-api-key ' ] = API_KEY ;
const res = await fetch ( SUBMIT_URL , {
method : ' POST ' ,
headers ,
body : JSON . stringify ( { google_id_token : googleCredential , items } ) ,
} ) ;
const data = await res . json ( ) ;
if ( res . ok ) {
for ( const el of document . getElementById ( ' app ' ) . children ) { if ( el . id != = ' success ' ) el . style . display = ' none ' ; }
document . getElementById ( ' success ' ) . style . display = ' block ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
document . getElementById ( ' success-detail ' ) . textContent = ` $ { googleUser . name } — $ $ { ( data . total | | 0 ) . toFixed ( 2 ) } total . You ' re all set!`;
2026-05-13 11:36:14 -04:00
document . querySelector ( ' .sticky-footer ' ) . style . display = ' none ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
try { localStorage . setItem ( ' lastOrderWeek ' , WEEK ) ; } catch ( e ) { }
2026-05-13 11:36:14 -04:00
} else {
alert ( data . error | | ' Something went wrong. ' ) ;
btn . disabled = false ;
btn . textContent = ' Submit Order ' ;
}
} catch ( e ) {
alert ( ' Failed to submit. Check your connection and try again. ' ) ;
btn . disabled = false ;
btn . textContent = ' Submit Order ' ;
}
}
"""
else :
submit_order_js = """
async function submitOrder ( ) {
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
if ( formClosed ) { alert ( ' Orders are closed. ' ) ; return ; }
2026-05-13 11:36:14 -04:00
const name = document . getElementById ( ' emp-name ' ) . value . trim ( ) ;
const email = document . getElementById ( ' emp-email ' ) . value . trim ( ) ;
if ( ! name ) { alert ( ' Please enter your name. ' ) ; return ; }
if ( ! email ) { alert ( ' Please enter your email. ' ) ; return ; }
const items = Object . entries ( quantities ) . map ( ( [ i , qty ] ) = > ( {
name : MEALS [ i ] . name ,
retail_price : MEALS [ i ] . price ,
quantity : qty ,
} ) ) ;
const btn = document . getElementById ( ' submit-btn ' ) ;
btn . disabled = true ;
btn . textContent = ' Submitting... ' ;
try {
const headers = { ' Content-Type ' : ' application/json ' } ;
if ( API_KEY ) headers [ ' x-api-key ' ] = API_KEY ;
const res = await fetch ( SUBMIT_URL , {
method : ' POST ' ,
headers ,
body : JSON . stringify ( { employee_name : name , employee_email : email , items } ) ,
} ) ;
const data = await res . json ( ) ;
if ( res . ok ) {
for ( const el of document . getElementById ( ' app ' ) . children ) { if ( el . id != = ' success ' ) el . style . display = ' none ' ; }
document . getElementById ( ' success ' ) . style . display = ' block ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
document . getElementById ( ' success-detail ' ) . textContent = ` $ { name } — $ $ { ( data . total | | 0 ) . toFixed ( 2 ) } total . You ' re all set!`;
2026-05-13 11:36:14 -04:00
document . querySelector ( ' .sticky-footer ' ) . style . display = ' none ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
try { localStorage . setItem ( ' lastOrderWeek ' , WEEK ) ; } catch ( e ) { }
2026-05-13 11:36:14 -04:00
} else {
alert ( data . error | | ' Something went wrong. ' ) ;
btn . disabled = false ;
btn . textContent = ' Submit Order ' ;
}
} catch ( e ) {
alert ( ' Failed to submit. Check your connection and try again. ' ) ;
btn . disabled = false ;
btn . textContent = ' Submit Order ' ;
}
}
"""
2026-05-12 18:25:15 -04:00
return f """ <!DOCTYPE html>
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > Sea Haven — Meal Order ( { week } ) < / title >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< link rel = " icon " type = " image/png " sizes = " 32x32 " href = " data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABX0lEQVRYhe3TPWsUURjF8d+dnSxhoxA1xEJnQQsJGbBTrIIIvoEDNqkNiJ9BrPwI4jeY2kmVoKWkSVALK6MgwWLsJIWIBBFZLGaLTTLrJrX3X96X5znnPPcSiUQikf+dcHAhr+qLuIrT2MV77Owv9ycWy6u6i3tnOu5maTg/FUzjOz5gHe/KImsXkFf1ZTzH9ZbaNdbwElvby/0fwzsJFnANN3uJO/00zJ5MxmrcwqOyyD7uE5BX9Q08xh5O4BL+ZXkXvzQp9RKcS4OzaUukh9nDg7LIVkcFGI04r2pDZw+xgrlx1U516KdB93DnGhvYxBd8w08M0MXnssgmCx7O9RZuYzEw30sszATpXCeY2R/3AC/wTMu420gnHbgyHeY1sb3FH9wfc28DT8oiezOx63EE4DeeYqllb4BXGsevj+L4IEd4M6ysf6X5HUu4oEniE9bKIts5dtdIJBKJREb4C8ngTD+C9DtvAAAAAElFTkSuQmCC " >
2026-05-12 18:25:15 -04:00
< style >
* { { margin : 0 ; padding : 0 ; box - sizing : border - box ; } }
body { { font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , Roboto , sans - serif ; background : #f5f5f7; color: #1d1d1f; }}
. container { { max - width : 800 px ; margin : 0 auto ; padding : 16 px ; } }
header { { background : #1a1a2e; color: #fff; padding: 24px 16px; text-align: center; margin-bottom: 24px; border-radius: 12px; }}
header h1 { { font - size : 1.5 rem ; margin - bottom : 4 px ; } }
header p { { font - size : 0.85 rem ; opacity : 0.8 ; } }
. deadline { { background : #fef3c7; color: #92400e; padding: 10px 16px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; text-align: center; font-weight: 500; }}
. employee - info { { background : #fff; border-radius: 12px; padding: 16px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }}
. employee - info label { { display : block ; font - weight : 600 ; margin - bottom : 4 px ; font - size : 0.85 rem ; } }
. employee - info input , . employee - info select { { width : 100 % ; padding : 10 px 12 px ; border : 1 px solid #d1d5db; border-radius: 8px; font-size: 1rem; margin-bottom: 12px; }}
. filters { { display : flex ; flex - wrap : wrap ; gap : 8 px ; margin - bottom : 16 px ; } }
. filters button { { padding : 6 px 14 px ; border : 1 px solid #d1d5db; border-radius: 20px; background: #fff; font-size: 0.8rem; cursor: pointer; transition: all 0.2s; }}
. filters button . active { { background : #1a1a2e; color: #fff; border-color: #1a1a2e; }}
. meal - card { { background : #fff; border-radius: 12px; padding: 12px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); display: flex; gap: 12px; align-items: center; }}
. meal - img { { width : 80 px ; height : 80 px ; border - radius : 8 px ; object - fit : cover ; flex - shrink : 0 ; } }
. meal - info { { flex : 1 ; min - width : 0 ; } }
. meal - name { { font - weight : 600 ; font - size : 0.95 rem ; margin - bottom : 2 px ; } }
. meal - meta { { font - size : 0.8 rem ; color : #6b7280; margin-bottom: 4px; }}
. meal - tags { { display : flex ; flex - wrap : wrap ; gap : 4 px ; margin - bottom : 4 px ; } }
. meal - tags span { { font - size : 0.7 rem ; padding : 2 px 8 px ; border - radius : 10 px ; background : #e0f2fe; color: #0369a1; font-weight: 500; }}
. meal - tags span . new { { background : #dcfce7; color: #15803d; }}
. qty - control { { display : flex ; align - items : center ; gap : 0 ; flex - shrink : 0 ; } }
. qty - control button { { width : 32 px ; height : 32 px ; border : 1 px solid #d1d5db; background: #f9fafb; font-size: 1.1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; }}
. qty - control button : first - child { { border - radius : 8 px 0 0 8 px ; } }
. qty - control button : last - child { { border - radius : 0 8 px 8 px 0 ; } }
. qty - control . qty { { width : 36 px ; height : 32 px ; text - align : center ; border : 1 px solid #d1d5db; border-left: 0; border-right: 0; font-size: 0.95rem; font-weight: 600; }}
. meal - card . selected { { border : 2 px solid #1a1a2e; }}
. sticky - footer { { position : fixed ; bottom : 0 ; left : 0 ; right : 0 ; background : #fff; border-top: 1px solid #e5e7eb; padding: 12px 16px; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); z-index: 100; }}
. sticky - footer . inner { { max - width : 800 px ; margin : 0 auto ; display : flex ; justify - content : space - between ; align - items : center ; } }
. total { { font - size : 1.1 rem ; font - weight : 700 ; } }
. submit - btn { { background : #1a1a2e; color: #fff; border: none; padding: 12px 32px; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s; }}
. submit - btn : disabled { { opacity : 0.4 ; cursor : not - allowed ; } }
. submit - btn : hover : not ( : disabled ) { { opacity : 0.85 ; } }
. success - msg { { text - align : center ; padding : 60 px 20 px ; } }
. success - msg h2 { { color : #15803d; margin-bottom: 8px; }}
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
. back - btn { { background : transparent ; color : #1a1a2e; border: 2px solid #1a1a2e; padding: 12px 32px; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-top: 20px; }}
. back - btn : hover { { background : #1a1a2e; color: #fff; }}
. duplicate - warning { { background : #fef3c7; color: #92400e; padding: 10px 16px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; text-align: center; font-weight: 500; border: 1px solid #fcd34d; }}
2026-05-12 18:25:15 -04:00
. meal - desc { { font - size : 0.8 rem ; color : #6b7280; margin-bottom: 4px; }}
. search - bar { { width : 100 % ; padding : 10 px 12 px ; border : 1 px solid #d1d5db; border-radius: 8px; font-size: 1rem; margin-bottom: 16px; }}
body { { padding - bottom : 80 px ; } }
. closed - banner { { background : #fee2e2; color: #991b1b; padding: 16px; border-radius: 8px; margin-bottom: 20px; text-align: center; font-weight: 600; font-size: 1rem; }}
2026-05-13 11:36:14 -04:00
@media ( min - width : 768 px ) { {
. container { { max - width : 1100 px ; } }
#meals-list {{ display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }}
#meals-list .meal-card {{ margin-bottom: 0; }}
} }
@media ( min - width : 1200 px ) { {
. container { { max - width : 1400 px ; } }
#meals-list {{ grid-template-columns: repeat(3, 1fr); }}
} }
. discount - banner { { background : #dcfce7; color: #15803d; padding: 10px 16px; border-radius: 8px; margin-bottom: 20px; font-size: 0.85rem; text-align: center; font-weight: 500; }}
. price - retail { { text - decoration : line - through ; color : #9ca3af; font-size: 0.75rem; margin-right: 4px; }}
. price - employee { { color : #15803d; font-weight: 600; }}
. auth - overlay { { position : fixed ; inset : 0 ; background : #1a1a2e; display: flex; align-items: center; justify-content: center; z-index: 1000; }}
. auth - card { { background : #fff; border-radius: 16px; padding: 48px 40px; text-align: center; max-width: 400px; width: 90%; box-shadow: 0 8px 32px rgba(0,0,0,0.3); }}
. auth - card h1 { { font - size : 1.5 rem ; margin - bottom : 4 px ; color : #1d1d1f; }}
. auth - card p { { font - size : 0.9 rem ; color : #6b7280; margin-bottom: 32px; }}
. auth - card . logo { { font - size : 2 rem ; margin - bottom : 16 px ; } }
. closed - overlay { { position : fixed ; inset : 0 ; background : #1a1a2e; display: none; align-items: center; justify-content: center; z-index: 2000; }}
. closed - card { { background : #fff; border-radius: 16px; padding: 48px 40px; text-align: center; max-width: 420px; width: 90%; box-shadow: 0 8px 32px rgba(0,0,0,0.3); }}
. closed - card h1 { { font - size : 1.5 rem ; margin - bottom : 8 px ; color : #1d1d1f; }}
. closed - card p { { font - size : 0.9 rem ; color : #6b7280; margin-bottom: 24px; }}
. closed - card . logo { { font - size : 2.5 rem ; margin - bottom : 16 px ; } }
. countdown { { font - size : 2 rem ; font - weight : 700 ; color : #1a1a2e; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }}
. countdown - label { { font - size : 0.75 rem ; color : #9ca3af; margin-top: 4px; }}
2026-05-12 18:25:15 -04:00
< / style >
2026-05-13 13:25:40 -04:00
{
' <script src= " https://accounts.google.com/gsi/client " async defer></script> '
if use_google_auth
else " "
}
2026-05-12 18:25:15 -04:00
< / head >
< body >
2026-05-13 11:36:14 -04:00
< div class = " closed-overlay " id = " closed-overlay " >
< div class = " closed-card " >
< div class = " logo " > & #127869;</div>
< h1 > Orders Are Closed < / h1 >
< p > Orders open again Monday at 8 : 00 AM ET < / p >
< div class = " countdown " id = " countdown " > < / div >
< div class = " countdown-label " > until orders open < / div >
< / div >
< / div >
2026-05-13 13:25:40 -04:00
{
' <div class= " auth-overlay " id= " auth-overlay " ><div class= " auth-card " ><div class= " logo " >🍽</div><h1>Sea Haven Meal Order</h1><p>Sign in with your company Google account to place your order.</p><div id= " g-signin-btn " style= " display:flex;justify-content:center; " ></div></div></div> '
if use_google_auth
else " "
}
< div class = " container " id = " app " { ' style= " display:none; " ' if use_google_auth else " " } >
2026-05-12 18:25:15 -04:00
< header >
< h1 > Sea Haven Meal Order < / h1 >
< p > Week of { week } & middot ; Menu scraped { scraped_at [ : 10 ] } < / p >
< / header >
< div class = " deadline " > Order deadline : { deadline } < / div >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< div class = " duplicate-warning " id = " duplicate-warning " style = " display:none; " > You ' ve already submitted an order this week. Submitting again will replace your previous order.</div>
2026-05-13 13:25:40 -04:00
{
' <div class= " discount-banner " >Prices reflect employee cost after '
+ ( f " { bulk_discount : g } % bulk discount " if bulk_discount > 0 else " " )
+ ( " + " if bulk_discount > 0 and company_subsidy > 0 else " " )
+ ( f " { company_subsidy : g } % company subsidy " if company_subsidy > 0 else " " )
+ " </div> "
if has_discount
else " "
}
2026-05-12 18:25:15 -04:00
2026-05-13 11:36:14 -04:00
{ auth_section_html }
2026-05-12 18:25:15 -04:00
< input type = " text " class = " search-bar " id = " search " placeholder = " Search meals... " >
< div class = " filters " id = " filters " > < / div >
< div id = " meals-list " > < / div >
2026-05-13 13:25:40 -04:00
< div class = " sticky-footer " {
' style= " display:none; " id= " sticky-footer " ' if use_google_auth else " "
} >
2026-05-12 18:25:15 -04:00
< div class = " inner " >
< div >
< span class = " total " id = " total-display " > $ 0.00 < / span >
< span style = " font-size:0.8rem;color:#6b7280;margin-left:8px; " id = " count-display " > 0 meals < / span >
< / div >
< button class = " submit-btn " id = " submit-btn " disabled > Submit Order < / button >
< / div >
< / div >
< div id = " success " class = " success-msg " style = " display:none; " >
< h2 > Order submitted ! < / h2 >
< p id = " success-detail " > < / p >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< button class = " back-btn " onclick = " location.reload() " > Back to Menu < / button >
2026-05-12 18:25:15 -04:00
< / div >
< / div >
< script >
const MEALS = { meals_json } ;
const ROSTER = { roster_json } ;
const SUBMIT_URL = ' {submit_url} ' ;
const STATUS_URL = ' {status_url} ' ;
2026-05-12 20:16:46 -04:00
const ROSTER_URL = ' {roster_url} ' ;
2026-05-12 18:25:15 -04:00
const API_KEY = { api_key_json } ;
const WEEK = ' {week} ' ;
2026-05-13 11:36:14 -04:00
const BULK_DISCOUNT = { bulk_discount } ;
const COMPANY_SUBSIDY = { company_subsidy } ;
2026-05-12 18:25:15 -04:00
const quantities = { { } } ;
let formClosed = false ;
2026-05-13 13:25:40 -04:00
{
" const GOOGLE_CLIENT_ID = "
+ google_client_id_json
+ " ; \n let googleCredential = null; \n let googleUser = null; "
if use_google_auth
else " "
}
2026-05-12 18:25:15 -04:00
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
function escapeHtml ( str ) { {
if ( ! str ) return ' ' ;
return String ( str )
. replace ( / & / g , ' & ' )
. replace ( / < / g , ' < ' )
. replace ( / > / g , ' > ' )
. replace ( / " /g, ' " ' )
. replace ( / ' /g, ' & #39;');
} }
2026-05-13 11:36:14 -04:00
function employeePrice ( retail ) { {
if ( ! retail ) return 0 ;
2026-05-13 13:39:18 -04:00
const bulkPrice = Math . round ( retail * ( 1 - BULK_DISCOUNT / 100 ) * 100 + 1e-8 ) / 100 ;
const empPrice = Math . round ( bulkPrice * ( 1 - COMPANY_SUBSIDY / 100 ) * 100 + 1e-8 ) / 100 ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
return empPrice ;
2026-05-13 11:36:14 -04:00
} }
{ google_auth_js }
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
function checkDuplicateOrder ( ) { {
try { {
if ( localStorage . getItem ( ' lastOrderWeek ' ) == = WEEK ) { {
document . getElementById ( ' duplicate-warning ' ) . style . display = ' block ' ;
} }
} } catch ( e ) { { } }
} }
2026-05-12 18:25:15 -04:00
function init ( ) { {
checkFormStatus ( ) ;
2026-05-13 13:25:40 -04:00
{
" waitForGoogleAuth(); "
if use_google_auth
else " loadRoster(); checkDuplicateOrder(); "
}
2026-05-12 18:25:15 -04:00
/ / Build filter buttons
const tags = new Set ( ) ;
MEALS . forEach ( m = > ( m . dietary_tags | | [ ] ) . forEach ( t = > tags . add ( t ) ) ) ;
const cats = [ ' All Meals ' , . . . Array . from ( tags ) . sort ( ) ] ;
const filtersEl = document . getElementById ( ' filters ' ) ;
cats . forEach ( cat = > { {
const btn = document . createElement ( ' button ' ) ;
btn . textContent = cat ;
if ( cat == = ' All Meals ' ) btn . classList . add ( ' active ' ) ;
btn . onclick = ( ) = > { {
filtersEl . querySelectorAll ( ' button ' ) . forEach ( b = > b . classList . remove ( ' active ' ) ) ;
btn . classList . add ( ' active ' ) ;
renderMeals ( ) ;
} } ;
filtersEl . appendChild ( btn ) ;
} } ) ;
document . getElementById ( ' search ' ) . addEventListener ( ' input ' , renderMeals ) ;
document . getElementById ( ' submit-btn ' ) . addEventListener ( ' click ' , submitOrder ) ;
renderMeals ( ) ;
} }
function renderMeals ( ) { {
const activeFilter = document . querySelector ( ' .filters button.active ' ) ? . textContent | | ' All Meals ' ;
const search = document . getElementById ( ' search ' ) . value . toLowerCase ( ) ;
const list = document . getElementById ( ' meals-list ' ) ;
list . innerHTML = ' ' ;
MEALS . forEach ( ( meal , i ) = > { {
if ( activeFilter != = ' All Meals ' & & ! ( meal . dietary_tags | | [ ] ) . includes ( activeFilter ) ) return ;
if ( search & & ! meal . name . toLowerCase ( ) . includes ( search ) ) return ;
const qty = quantities [ i ] | | 0 ;
const card = document . createElement ( ' div ' ) ;
card . className = ' meal-card ' + ( qty > 0 ? ' selected ' : ' ' ) ;
let tagsHtml = ' ' ;
if ( meal . is_new ) tagsHtml + = ' <span class= " new " >NEW</span> ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
( meal . dietary_tags | | [ ] ) . forEach ( t = > { { tagsHtml + = ` < span > $ { { escapeHtml ( t ) } } < / span > ` ; } } ) ;
2026-05-12 18:25:15 -04:00
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
const descHtml = meal . description ? ` < div class = " meal-desc " > $ { { escapeHtml ( meal . description ) } } < / div > ` : ' ' ;
2026-05-13 11:36:14 -04:00
const hasDiscount = ( BULK_DISCOUNT > 0 | | COMPANY_SUBSIDY > 0 ) & & meal . price ;
const empPrice = employeePrice ( meal . price ) ;
const priceHtml = hasDiscount
? ` < span class = " price-retail " > $ $ { { meal . price . toFixed ( 2 ) } } < / span > < span class = " price-employee " > $ $ { { empPrice . toFixed ( 2 ) } } < / span > `
: ( meal . price ? ` $ $ { { meal . price . toFixed ( 2 ) } } ` : ' — ' ) ;
2026-05-12 18:25:15 -04:00
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
const safeName = escapeHtml ( meal . name ) ;
const safeImageUrl = escapeHtml ( meal . image_url ) ;
2026-05-12 18:25:15 -04:00
card . innerHTML = `
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
$ { { meal . image_url ? ` < img class = " meal-img " src = " $ {{ safeImageUrl}} " alt = " $ {{ safeName}} " loading = " lazy " > ` : ' ' } }
2026-05-12 18:25:15 -04:00
< div class = " meal-info " >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< div class = " meal-name " > $ { { safeName } } < / div >
2026-05-12 18:25:15 -04:00
$ { { descHtml } }
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< div class = " meal-meta " > $ { { priceHtml } } & middot ; $ { { escapeHtml ( meal . calories | | ' ? ' ) } } cal & middot ; $ { { escapeHtml ( meal . protein | | ' ? ' ) } } protein < / div >
2026-05-12 18:25:15 -04:00
< div class = " meal-tags " > $ { { tagsHtml } } < / div >
< / div >
< div class = " qty-control " >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< button onclick = " changeQty($ {{ i}}, -1) " $ { { formClosed ? ' disabled ' : ' ' } } > & minus ; < / button >
2026-05-12 18:25:15 -04:00
< input class = " qty " type = " text " value = " $ {{ qty}} " readonly >
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
< button onclick = " changeQty($ {{ i}}, 1) " $ { { formClosed ? ' disabled ' : ' ' } } > + < / button >
2026-05-12 18:25:15 -04:00
< / div >
` ;
list . appendChild ( card ) ;
} } ) ;
updateTotal ( ) ;
} }
function changeQty ( index , delta ) { {
const current = quantities [ index ] | | 0 ;
const next = Math . max ( 0 , current + delta ) ;
if ( next == = 0 ) delete quantities [ index ] ; else quantities [ index ] = next ;
renderMeals ( ) ;
} }
function updateTotal ( ) { {
let total = 0 , count = 0 ;
Object . entries ( quantities ) . forEach ( ( [ i , qty ] ) = > { {
2026-05-13 11:36:14 -04:00
total + = employeePrice ( MEALS [ i ] . price | | 0 ) * qty ;
2026-05-12 18:25:15 -04:00
count + = qty ;
} } ) ;
document . getElementById ( ' total-display ' ) . textContent = ` $ $ { { total . toFixed ( 2 ) } } ` ;
document . getElementById ( ' count-display ' ) . textContent = ` $ { { count } } meal $ { { count != = 1 ? ' s ' : ' ' } } ` ;
2026-05-13 13:25:40 -04:00
document . getElementById ( ' submit-btn ' ) . disabled = count == = 0 {
" || !googleCredential " if use_google_auth else " "
} ;
2026-05-12 18:25:15 -04:00
} }
2026-05-13 11:36:14 -04:00
{ submit_order_js }
2026-05-12 18:25:15 -04:00
2026-05-13 13:25:40 -04:00
{
" "
if use_google_auth
else ''' async function loadRoster() {{
2026-05-12 20:16:46 -04:00
try { {
const res = await fetch ( ROSTER_URL ) ;
const data = await res . json ( ) ;
const select = document . getElementById ( ' emp-name ' ) ;
select . innerHTML = ' <option value= " " >Select your name</option> ' ;
( data . employees | | ROSTER ) . forEach ( emp = > { {
const opt = document . createElement ( ' option ' ) ;
opt . value = emp . name ;
opt . dataset . email = emp . email ;
opt . textContent = emp . name ;
select . appendChild ( opt ) ;
} } ) ;
select . addEventListener ( ' change ' , ( ) = > { {
const selected = select . options [ select . selectedIndex ] ;
document . getElementById ( ' emp-email ' ) . value = selected ? . dataset ? . email | | ' ' ;
} } ) ;
} } catch ( e ) { {
const select = document . getElementById ( ' emp-name ' ) ;
select . innerHTML = ' <option value= " " >Select your name</option> ' ;
ROSTER . forEach ( emp = > { {
const opt = document . createElement ( ' option ' ) ;
opt . value = emp . name ;
opt . dataset . email = emp . email ;
opt . textContent = emp . name ;
select . appendChild ( opt ) ;
} } ) ;
} }
} }
2026-05-13 13:25:40 -04:00
'''
}
2026-05-12 18:25:15 -04:00
async function checkFormStatus ( ) { {
if ( ! STATUS_URL ) return ;
try { {
const res = await fetch ( STATUS_URL ) ;
const data = await res . json ( ) ;
if ( data . status == = ' closed ' ) { {
formClosed = true ;
2026-05-13 11:36:14 -04:00
document . getElementById ( ' closed-overlay ' ) . style . display = ' flex ' ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
document . querySelectorAll ( ' .qty-control button ' ) . forEach ( b = > b . disabled = true ) ;
const submitBtn = document . getElementById ( ' submit-btn ' ) ;
if ( submitBtn ) submitBtn . disabled = true ;
startCountdown ( data ) ;
2026-05-12 18:25:15 -04:00
} }
} } catch ( e ) { { } }
} }
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
let reopenAtMs = null ;
function startCountdown ( data ) { {
if ( data & & data . reopen_at ) { {
reopenAtMs = data . reopen_at * 1000 ;
} }
2026-05-13 11:36:14 -04:00
updateCountdown ( ) ;
setInterval ( updateCountdown , 1000 ) ;
} }
function updateCountdown ( ) { {
const now = new Date ( ) ;
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
let target ;
if ( reopenAtMs ) { {
target = new Date ( reopenAtMs ) ;
} } else { {
const etStr = now . toLocaleString ( ' en-US ' , { { timeZone : ' America/New_York ' , hour12 : false } } ) ;
const etDate = new Date ( etStr ) ;
const day = etDate . getDay ( ) ;
const hour = etDate . getHours ( ) ;
let daysUntil = ( 1 - day + 7 ) % 7 ;
if ( daysUntil == = 0 & & hour > = 8 ) daysUntil = 7 ;
target = new Date ( etDate ) ;
target . setDate ( target . getDate ( ) + daysUntil ) ;
target . setHours ( 8 , 0 , 0 , 0 ) ;
} }
2026-05-13 11:36:14 -04:00
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
let diff = Math . max ( 0 , Math . floor ( ( target - now ) / 1000 ) ) ;
2026-05-13 11:36:14 -04:00
const d = Math . floor ( diff / 86400 ) ; diff % = 86400 ;
const h = Math . floor ( diff / 3600 ) ; diff % = 3600 ;
const m = Math . floor ( diff / 60 ) ;
const s = diff % 60 ;
const pad = n = > String ( n ) . padStart ( 2 , ' 0 ' ) ;
document . getElementById ( ' countdown ' ) . textContent =
` $ { { d } } d $ { { pad ( h ) } } h $ { { pad ( m ) } } m $ { { pad ( s ) } } s ` ;
} }
2026-05-12 18:25:15 -04:00
init ( ) ;
< / script >
< / body >
< / html > """
def main ( ) :
parser = argparse . ArgumentParser ( description = " Generate meal order form HTML " )
2026-05-12 19:31:27 -04:00
parser . add_argument (
" --api-url " , default = " " , help = " API Gateway base URL (cloud mode) "
)
parser . add_argument (
" --api-key " , default = " " , help = " API key for order submission (cloud mode) "
)
2026-05-13 11:36:14 -04:00
parser . add_argument (
" --bulk-discount " ,
type = float ,
default = None ,
help = " Bulk discount percentage (e.g., 10 for 10 %% off) " ,
)
parser . add_argument (
" --company-subsidy " ,
type = float ,
default = None ,
help = " Company subsidy percentage (e.g., 50 for 50 %% off after bulk discount) " ,
)
parser . add_argument (
" --google-client-id " ,
default = None ,
help = " Google OAuth Client ID for Sign-In authentication " ,
)
2026-05-12 18:25:15 -04:00
args = parser . parse_args ( )
config = load_config ( )
menu = latest_menu ( )
2026-05-13 11:36:14 -04:00
bulk_discount = (
args . bulk_discount
if args . bulk_discount is not None
else config . get ( " bulk_discount_percent " , 0 )
)
company_subsidy = (
args . company_subsidy
if args . company_subsidy is not None
else config . get ( " company_subsidy_percent " , 0 )
)
google_client_id = args . google_client_id or config . get ( " google_client_id " , " " )
if not google_client_id :
try :
Fix XSS risks and add closed-form UX to order page
Add escapeHtml() for all scraped content in innerHTML, fix script
injection via </script> in JSON, fix JWT base64url decoding, match
backend two-step rounding in JS employeePrice(), disable qty buttons
and submit when form is closed, add server-driven countdown from
reopen_at, add duplicate order warning via localStorage, add back
button after submission, embed favicon, use :g format for fractional
discounts, and exclude dead loadRoster code when Google auth enabled.
2026-05-13 12:59:06 -04:00
import boto3
2026-05-13 13:25:40 -04:00
2026-05-13 11:36:14 -04:00
ssm = boto3 . client ( " ssm " )
resp = ssm . get_parameter ( Name = " /meal-order-manager/google-client-id " )
google_client_id = resp [ " Parameter " ] [ " Value " ]
except Exception :
pass
html = generate_form (
menu ,
config ,
api_url = args . api_url ,
api_key = args . api_key ,
bulk_discount = bulk_discount ,
company_subsidy = company_subsidy ,
google_client_id = google_client_id ,
)
2026-05-12 18:25:15 -04:00
week = datetime . now ( ) . strftime ( " % Y-W % U " )
output_file = OUTPUT_DIR / f " order-form- { week } .html "
with open ( output_file , " w " ) as f :
f . write ( html )
mode = " cloud " if args . api_url else " local "
print ( f " Generated order form ( { mode } mode): { output_file } " )
print ( f " { menu [ ' meal_count ' ] } meals from menu scraped { menu [ ' scraped_at ' ] [ : 10 ] } " )
if mode == " local " :
2026-05-12 19:31:27 -04:00
print ( " Start the server with: python3 src/server/app.py " )
2026-05-12 18:25:15 -04:00
else :
print ( f " API endpoint: { args . api_url } " )
if __name__ == " __main__ " :
main ( )