Pay your tab tool (app)
Staff tool for the Order Ahead app. Please don't delete this page.
const {ROOT,S,shop,esc,money2,variantFor,EXTRAS,visibleSections}=A;
const box=document.createElement("div"); box.className="tt"; ROOT.append(box);
const css=`<style>.tt{position:fixed;inset:0;z-index:30;background:var(--bg);overflow:auto;-webkit-overflow-scrolling:touch;color:var(--ink);font-family:var(--body)}.tt .w{max-width:560px;margin:0 auto;padding:16px 16px 190px}.tt h2{margin:6px 0 2px}.tt .x{float:right;border:0;background:var(--pink);color:var(--red);font-weight:700;border-radius:999px;padding:6px 14px;cursor:pointer}.tt .sec{font-family:var(--board);text-transform:uppercase;color:var(--red);font-weight:600;letter-spacing:.04em;margin:22px 0 2px;font-size:17px}.tt .r{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px dashed var(--line)}.tt .r .n{flex:1;font-weight:700;line-height:1.25}.tt .r .n small{display:block;font-weight:500;color:var(--muted)}.tt .r.on .n{color:var(--red)}.tt .q{display:inline-flex;align-items:center;background:var(--pink);border-radius:999px;flex:none}.tt .q button{border:0;background:none;width:38px;height:38px;font-size:21px;font-weight:700;color:var(--red);cursor:pointer}.tt .q span{min-width:22px;text-align:center;font-weight:700}.tt .bar{position:fixed;left:0;right:0;bottom:0;background:var(--surface);border-top:2px solid var(--line);padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));z-index:31}.tt .bar .w{padding:0}.tt .tot{display:flex;justify-content:space-between;font-weight:700;font-size:18px;margin-bottom:8px}.tt .row{margin-top:8px}.tt .link{width:100%;font-size:13px;margin-top:8px}.tt .hint{font-size:13px;color:var(--muted);margin-top:6px}</style>`;
box.innerHTML=css+`<div class="w"><h2>Pay your tab</h2><p class="muted">Staff only. Enter the trailer code.</p><input class="t" id="tt-pin" inputmode="numeric" maxlength="4" autocomplete="off" style="font-size:26px;text-align:center;letter-spacing:14px;max-width:200px;display:block;margin:14px auto"><p class="err" id="tt-err" hidden></p><div class="row"><button class="btn block" id="tt-go">Open</button></div></div>`;
const pin=box.querySelector("#tt-pin"); pin.focus();
const tryPin=()=>{ if(pin.value===String(S.site.stampCode)) build(); else { const e=box.querySelector("#tt-err"); e.hidden=false; e.textContent="That code doesn't match."; pin.value=""; } };
box.querySelector("#tt-go").onclick=tryPin; pin.onkeydown=e=>{ if(e.key==="Enter") tryPin(); };
function build(){
const ex=shop().extras, rows=[], secs=visibleSections().map(s=>s.key);
S.site.menu.filter(m=>secs.includes(m.cat)).forEach(m=>{
const opts=m.prices?m.prices.map((p,i)=>({lab:p.label,price:p.price,i})):m.choices?m.choices.map((c,i)=>({lab:c,price:m.price,i})):[{lab:"",price:m.price,i:0}];
opts.forEach(o=>{ const ids=variantFor(m,o.i); if(ids) rows.push({cat:m.cat,name:m.name+(o.lab?" ("+o.lab+")":""),price:o.price,ids,q:0}); });
});
const add=(name,id,price)=>{ if(id) rows.push({cat:"Add-ons",name,price,ids:[id],q:0}); };
add("Oat milk",ex.oat,EXTRAS.altMilk); add("Almond milk",ex.almond,EXTRAS.altMilk); add("Cold foam",ex.foam,EXTRAS.coldFoam);
EXTRAS.flavors.forEach(f=>add(f+" syrup",ex.flavors[f],EXTRAS.flavor));
EXTRAS.sugarFree.forEach(f=>add("SF "+f+" syrup",ex.flavors["SF "+f],EXTRAS.flavor));
let h=css+`<div class="w"><button class="x" id="tt-close">Close</button><h2>Pay your tab</h2><p class="muted" style="margin:0">Tap in everything on the tab, then send the link. They pay at your normal checkout, tip included.</p>
<label class="f" for="tt-name">Business or tab name</label><input class="t" id="tt-name" placeholder="DEP Office">
<label class="f" for="tt-note">Note on the bill (optional)</label><input class="t" id="tt-note" placeholder="Thursday Oct 8 coffee run">`;
let last="";
rows.forEach((r,i)=>{ if(r.cat!==last){ h+=`<div class="sec">${esc(r.cat)}</div>`; last=r.cat; }
h+=`<div class="r" id="tt-r${i}"><div class="n">${esc(r.name)}<small>${money2(r.price)}</small></div><span class="q"><button data-tq="-1" data-ti="${i}" aria-label="One less">−</button><span id="tt-q${i}">0</span><button data-tq="1" data-ti="${i}" aria-label="One more">+</button></span></div>`; });
h+=`</div><div class="bar"><div class="w"><div class="tot"><span id="tt-n">0 items</span><span id="tt-t">$0.00</span></div>
<input class="t link" id="tt-link" readonly placeholder="The pay link shows here">
<div class="row" style="display:flex;gap:8px;flex-wrap:wrap"><button class="btn small" id="tt-copy" style="flex:1">Copy link</button><button class="btn small" id="tt-text" style="flex:1">Text</button><button class="btn small" id="tt-mail" style="flex:1">Email</button><button class="btn soft small" id="tt-clear">Clear</button></div>
<p class="hint">After you send it, delete the open tab in POS Cafe so it isn't counted twice.</p></div></div>`;
box.innerHTML=h;
const $=s=>box.querySelector(s), name=$("#tt-name"), note=$("#tt-note");
function link(){
const counts=new Map(), lines=[]; let n=0,t=0;
rows.forEach(r=>{ if(!r.q) return; n+=r.q; t+=r.q*r.price; lines.push(r.q+" x "+r.name); r.ids.forEach(id=>counts.set(id,(counts.get(id)||0)+r.q)); });
$("#tt-n").textContent=n+" item"+(n===1?"":"s"); $("#tt-t").textContent=money2(t)+" + tax";
if(!n){ $("#tt-link").value=""; return ""; }
const tab=name.value.trim()||"Tab", msg="\u{1F9FE} TAB: "+tab+(note.value.trim()?" – "+note.value.trim():"")+"\n"+lines.join("\n");
const u=location.origin+"/cart/"+[...counts].map(([id,q])=>id+":"+q).join(",")+"?note="+encodeURIComponent(msg)+"&attributes[Order type]="+encodeURIComponent("\u{1F9FE} Tab")+"&attributes[Tab]="+encodeURIComponent(tab);
$("#tt-link").value=u; return u;
}
name.oninput=note.oninput=link;
const msg=u=>"Hi! Here's your Lovers Coffee Club tab"+(name.value.trim()?" for "+name.value.trim():"")+". You can pay and add a tip here: "+u+" ❤️";
box.addEventListener("click",async e=>{ const b=e.target.closest("button"); if(!b) return; e.stopPropagation();
if(b.dataset.tq){ const r=rows[+b.dataset.ti]; r.q=Math.max(0,r.q+(+b.dataset.tq)); $("#tt-q"+b.dataset.ti).textContent=r.q; $("#tt-r"+b.dataset.ti).classList.toggle("on",r.q>0); link(); return; }
if(b.id==="tt-close"){ box.remove(); history.replaceState(null,"",location.pathname); return; }
if(b.id==="tt-clear"){ rows.forEach((r,i)=>{ r.q=0; $("#tt-q"+i).textContent="0"; $("#tt-r"+i).classList.remove("on"); }); name.value=note.value=""; link(); return; }
const u=link(); if(!u){ alert("Add at least one item to the tab first."); return; }
if(b.id==="tt-copy"){ try{ await navigator.clipboard.writeText(u); b.textContent="Copied!"; setTimeout(()=>b.textContent="Copy link",1500); }catch{ $("#tt-link").select(); } }
if(b.id==="tt-text") location.href="sms:?&body="+encodeURIComponent(msg(u));
if(b.id==="tt-mail") location.href="mailto:?subject="+encodeURIComponent("Your Lovers Coffee Club tab")+"&body="+encodeURIComponent(msg(u));
});
}
Loading the menu…