import { mountThemeToggle } from './ds/theme.js'; import { mountLogo } from './ds/logo.js'; import { link } from './ds/frame.js'; import { prod } from './ds/env.js'; mountThemeToggle(document.querySelector('#themeToggle')); for (const host of document.querySelectorAll('[data-logo]')) mountLogo(host, { icon: host.dataset.logo === 'icon' }); const base = location.pathname.split('/').slice(0,-1).join('/') || ''; const api = (path) => `${base}/api${path}`; const els={enabled:document.querySelector('#enabled'),moduleState:document.querySelector('#moduleState'),moduleGrid:document.querySelector('#moduleGrid'),tableCount:document.querySelector('#tableCount'),products:document.querySelector('#products'),payments:document.querySelector('#payments'),orders:document.querySelector('#orders'),save:document.querySelector('#save'),toast:document.querySelector('#toast'),tableUrl:document.querySelector('#tableUrl'),pulse:document.querySelector('#pulse')}; const moduleMeta={order:['Order to Table','Customer ordering','↗'],pay:['Pay Bill','Settle an open order','€'],payandgo:['Just Pay','Pay a paper receipt','€'],booking:['Bookings','Now or later','⌁'],screen:['4K Screen','Pictures and activity','▣'],gallery:['Gallery','Merchant discovery','□'],events:['Events','Nostr events','◌']}; function moduleRows(modules){els.moduleGrid.innerHTML=Object.entries(moduleMeta).map(([id,[name,copy,icon]])=>{const item=modules[id]||{};const url=prod?(id==='booking'?'https://pos.justworks.cash/':`https://${id}.justworks.cash/`):link(id);return `
${icon}

${name}

${copy}

${url.replace('https://','')} ↗
${item.status==='coming-soon'?'Coming soon':item.status==='live'?'Live service':'Interactive preview'}
`}).join('');document.querySelectorAll('[data-module]').forEach(input=>input.onchange=()=>{if(input.dataset.module==='order')els.enabled.checked=input.checked;save(true)})} let state=null,lastOrder='',configVersion=0,toggleSaving=false; function money(c){return `€${(c/100).toFixed(2)}`} function productRows(products){els.products.innerHTML=products.map(p=>`
`).join('')} function orderRows(orders){if(!orders.length){els.orders.innerHTML='
Waiting for the first table order…
';return}const labels={accepted:'Confirm',delivered:'Delivered',closed:'Close'};els.orders.innerHTML=orders.map(o=>`
Table ${o.table_number}${o.status}
${o.items.map(i=>`${i.qty}× ${i.name}`).join(' · ')}
${o.note?`
“${o.note}”
`:''}
${new Date(o.created_at).toLocaleTimeString()}
${['accepted','delivered','closed'].map(s=>``).join('')}
`).join('');document.querySelectorAll('[data-order]').forEach(b=>b.onclick=()=>updateOrder(b.dataset.order,b.dataset.status))} function paymentRows(payments){els.payments.innerHTML=payments.slice(0,3).map(p=>`
Table ${p.table_number} paid€${(p.amount_cents/100).toFixed(2)} · ${p.amount_sats.toLocaleString()} sats
`).join('');document.querySelectorAll('[data-payment]').forEach(b=>b.onclick=()=>acknowledgePayment(b.dataset.payment,b))} function showModuleState(enabled,saving=false){els.moduleState.textContent=saving?'saving…':enabled?'active':'disabled';els.moduleState.style.color=enabled?'var(--green)':'var(--muted)'} async function load(initial=false){const requestedVersion=configVersion;const res=await fetch(api('/dashboard'),{cache:'no-store'});const next=await res.json();state=next;if(requestedVersion===configVersion&&!toggleSaving){els.enabled.checked=state.module_enabled;showModuleState(state.module_enabled)}els.tableCount.value=state.table_count;if(initial){productRows(state.products);moduleRows(state.modules)}paymentRows(state.payments||[]);orderRows(state.orders);const table=state.tables.find(t=>t.table_number===21)||state.tables[0];els.tableUrl.textContent=prod?`https://order.justworks.cash/?table=${encodeURIComponent(table.token)}`:`${location.origin}${link('order')}${location.search?'&':'?'}table=${encodeURIComponent(table.token)}`;if(state.orders[0]?.id&&lastOrder&&lastOrder!==state.orders[0].id){els.pulse.textContent='new order';els.pulse.style.color='var(--green)';setTimeout(()=>{els.pulse.textContent='connected';els.pulse.style.color=''},1800)}lastOrder=state.orders[0]?.id||''} async function save(showToast=true,enabled=els.enabled.checked){const products=state.products.map(p=>{const name=document.querySelector(`[data-id="${p.id}"][data-field="name"]`).value;const price=document.querySelector(`[data-id="${p.id}"][data-field="price"]`).value;return{id:p.id,name,price_cents:Math.round(Number(price)*100)}});const modules=Object.fromEntries([...document.querySelectorAll('[data-module]')].map(input=>[input.dataset.module,{enabled:input.checked,config:state.modules[input.dataset.module]?.config||{}}]));modules.order.enabled=enabled;showModuleState(enabled,true);await fetch(api('/config'),{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({module_enabled:enabled,table_count:Number(els.tableCount.value),products,modules})});els.enabled.checked=enabled;showModuleState(enabled);if(showToast){els.toast.textContent='Configuration saved';els.toast.classList.add('show');setTimeout(()=>els.toast.classList.remove('show'),1300)}await load(true)} async function updateOrder(id,status){await fetch(api(`/orders/${id}/status`),{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({status})});await load()} async function acknowledgePayment(id,button){button.disabled=true;button.textContent='Clearing…';const res=await fetch(api(`/payments/${id}/acknowledge`),{method:'POST'});if(!res.ok){button.disabled=false;button.textContent='Try again';return}document.querySelector(`[data-payment-card="${id}"]`)?.classList.add('leaving');setTimeout(()=>load(),260)} els.save.onclick=()=>save(true);els.enabled.onchange=async()=>{const enabled=els.enabled.checked;configVersion+=1;toggleSaving=true;els.enabled.disabled=true;try{await save(false,enabled);els.toast.textContent=enabled?'Napplet enabled':'Napplet disabled';els.toast.classList.add('show');setTimeout(()=>els.toast.classList.remove('show'),1300)}finally{els.enabled.checked=enabled;showModuleState(enabled);els.enabled.disabled=false;toggleSaving=false}};load(true);setInterval(()=>load().catch(()=>{els.pulse.textContent='reconnecting'}),1200);