import {frame as shellFrame} from './ds/frame.js'; const ids=['business','pos','order','pay','payandgo','screen','gallery','events']; const query=new URLSearchParams(location.search); const npub=query.get('npub')?.trim()||''; const host=location.hostname.split('.')[0]; const surface=ids.includes(host)?host:(query.get('surface')||'pos'); const prod=location.hostname.endsWith('justworks.cash'); const link=id=>prod?`https://${id}.justworks.cash/`:`/portal?surface=${id}`; const imgs=['https://justworks.cash/assets/examples/tiles/localpub-venue-1.webp','https://justworks.cash/assets/examples/tiles/localpub-venue-3.webp','https://justworks.cash/assets/examples/tiles/localpub-venue-5.webp']; const app=document.querySelector('#app');let state; const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const frame=(html,label=surface,nav=true)=>shellFrame(app,{html,label,surface,nav}); function off(id){const m=state.modules[id];return m&&!m.enabled?`

Module disabled

${id} is currently off.

The merchant can activate it in Business.

Open Business
`:''} function sample(){return `
08
Table 82× Coral · 1× Nikita
⌁
Anna · 4 guestsArriving in 10 minutes
`} function orderCard(o){return `
${o.table_number}
Table ${o.table_number}${o.items.map(x=>`${x.qty}× ${esc(x.name)}`).join(' · ')}
`} function pos(){const orders=state.orders||[];frame(`

LocalPub Madeira · Staff tablet

Service, without the shouting.

● Live
Open orders${orders.length}Order to table
Bookings42 arriving soon
Paid today286ksats settled

Live queue

Orders & bookings

${orders.length?orders.map(orderCard).join(''):sample()}
`,'POS');document.querySelectorAll('[data-order]').forEach(b=>b.onclick=async()=>{await fetch(`/api/orders/${b.dataset.order}/status`,{method:'POST',headers:{'content-type':'application/json'},body:'{"status":"accepted"}'});boot()})} function euro(cents){return `€${(cents/100).toFixed(2)}`} function orderTotal(order){return (order?.items||[]).reduce((sum,item)=>sum+Number(item.price_cents||0)*Number(item.qty||0),0)} function invoiceView(payment){return `

Invoice ready

${payment.amount_sats.toLocaleString()} sats

${payment.qr_svg}
Open Lightning wallet ↗
Waiting for payment${payment.verifiable?'This page checks Lightning automatically.':'Keep this page open while the merchant confirms.'}
`} function paidView(payment){const proof=payment.receipt_id?`Verified Lightning proof${esc(payment.receipt_id.slice(0,12))}…${esc(payment.receipt_id.slice(-8))}↗`:'

Payment confirmed by the provider

';return ``} async function createQuickInvoice(amountCents,comment){const response=await fetch('/api/quick-pay',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({npub,amount_cents:amountCents,comment})});const value=await response.json();if(!response.ok)throw new Error(value.error||'Invoice unavailable');return value} async function paymentStatus(id){const response=await fetch(`/api/quick-pay-status?npub=${encodeURIComponent(npub)}&id=${encodeURIComponent(id)}`,{cache:'no-store'});const value=await response.json();if(!response.ok)throw new Error(value.error||'Payment status unavailable');return value} function pay(go=false){const id=go?'payandgo':'pay';if(off(id)){frame(off(id),go?'Just Pay':'Pay Bill');return}const merchant=state.merchant||{},profile=state.profile||{},orders=state.orders||[];let pollTimer;const initialTable=query.get('table')||'';frame(`

${go?'Pay & go':'Pay your table'}

${go?'Receipt in. Wallet out.':'Find it. Scan it. Paid.'}

${go?'Enter the exact receipt total and pay. No account.':'Enter your table number. We find the open order and create one Lightning invoice.'}

${esc((merchant.name||'JW').split(/\s+/).slice(0,2).map(x=>x[0]).join('').toUpperCase())}
${esc(merchant.name||'Merchant')}${esc(profile.lud16||state.lightning_address||'Lightning')}
${go?'€':'#'}${go?'Add the amount printed on the receipt.':'Enter your table to find its open order.'}
${go?'':``}

QR code + one-tap wallet link

`,go?'Just Pay':'Pay Bill');const input=document.querySelector('#payAmount'),button=document.querySelector('#payButton'),status=document.querySelector('#payStatus'),receipt=document.querySelector('#receipt'),result=document.querySelector('#invoiceResult');let selectedOrder=null;const findOrder=()=>{if(go)return;const table=Number.parseInt(input.value,10);selectedOrder=orders.find(order=>Number(order.table_number)===table&&order.status!=='closed')||null;const cents=orderTotal(selectedOrder);receipt.classList.toggle('hidden',!selectedOrder);receipt.innerHTML=selectedOrder?`${selectedOrder.items.map(item=>`${item.qty}× ${esc(item.name)}`).join(' · ')}${euro(cents)}`:'';document.querySelector('#amountHint').textContent=selectedOrder?(cents?`Open order · ${euro(cents)}`:'This order has no priced items.'):'No open order found for this table.'};if(!go){input.addEventListener('input',findOrder);if(initialTable)findOrder()}button.onclick=async()=>{let cents;if(go){const raw=Number.parseFloat(input.value.replace(',','.'));if(!Number.isFinite(raw)||raw<=0){status.textContent='Enter the receipt total.';status.classList.add('error');input.focus();return}cents=Math.round(raw*100)}else{findOrder();cents=orderTotal(selectedOrder);if(!selectedOrder||!cents){status.textContent=selectedOrder?'This order has no payable amount.':'Enter a table with an open order.';status.classList.add('error');input.focus();return}}button.disabled=true;button.innerHTML='Creating invoice…';status.textContent='Connecting to the merchant’s Lightning address…';status.classList.remove('error');try{const payment=await createQuickInvoice(cents,go?'Pay & Go':`Table ${selectedOrder.table_number}`);result.innerHTML=invoiceView(payment);document.querySelector('.copy-invoice').onclick=async event=>{await navigator.clipboard.writeText(payment.bolt11);event.currentTarget.textContent='Copied ✓'};button.classList.add('hidden');status.textContent=`${euro(payment.amount_cents)} · ready to pay`;const check=async()=>{try{const current=await paymentStatus(payment.id);if(current.status!=='paid')return;clearInterval(pollTimer);status.classList.add('hidden');result.innerHTML=paidView(current);document.querySelector('.paid-view').scrollIntoView({behavior:'smooth',block:'center'})}catch(error){status.textContent='Still checking Lightning…'}};document.querySelector('.check-payment').onclick=check;if(payment.verifiable)pollTimer=setInterval(check,2500);document.querySelector('.invoice-view').scrollIntoView({behavior:'smooth',block:'nearest'})}catch(error){status.textContent=error.message;status.classList.add('error');button.disabled=false;button.innerHTML='Try again →'}}} function screen(){if(off('screen')){frame(off('screen'),'Screen');return}frame(`
LocalPub Madeira

Tonight in Madeira

Fresh poncha.
Cold Coral.
Good people.

21,000 sats tippedTable 8 · order confirmedNext · Sunset Sessions
`,'Screen',false)} const card=(name,type,img,rating)=>`
Open now

${type}

${name}

★ ${rating} · Bitcoin accepted
`; function gallery(){if(off('gallery')){frame(off('gallery'),'Gallery');return}frame(``,'Gallery');document.querySelectorAll('.filters button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.filters button').forEach(x=>x.classList.remove('active'));b.classList.add('active')})} function events(){frame(`
⌁

Nostr-native discovery

Events are coming soon.

Signed events from Just Works merchants—shown like the gallery, owned by the people publishing them.

Merchant eventsNostr signedLocal discovery
Explore merchants instead →
`,'Events')} async function boot(){if(surface==='order')return location.replace('/nsite'+location.search);if(surface==='business')return location.replace(link('business'));if(['pay','payandgo'].includes(surface)){if(!npub)throw new Error('Merchant missing');const [business,posState]=await Promise.all([fetch(`/api/business?npub=${encodeURIComponent(npub)}`,{cache:'no-store'}).then(async r=>{const v=await r.json();if(!r.ok)throw new Error(v.error);return v}),fetch(`/api/pay-orders?npub=${encodeURIComponent(npub)}`,{cache:'no-store'}).then(async r=>{const v=await r.json();if(!r.ok)throw new Error(v.error);return v})]);state={...business,...posState};return pay(surface==='payandgo')}state=await fetch('/api/dashboard',{cache:'no-store'}).then(r=>r.json());({pos,screen,gallery,events}[surface]||pos)()}boot().catch(error=>frame(`

Payment temporarily unavailable.

${esc(error.message)}

`));