Preserve hosted website UX inside Archipelago, add isolated Business and owner-authorized native wallet setup, and include deployment handoff. Based on main with native signer import already accepted.
24 lines
7.2 KiB
JavaScript
24 lines
7.2 KiB
JavaScript
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 `<article class="module-config"><span class="module-config-icon">${icon}</span><div><h3>${name}</h3><p>${copy}</p><a href="${url}" target="_blank">${url.replace('https://','')} ↗</a></div><label class="switch"><input data-module="${id}" type="checkbox" ${item.enabled?'checked':''} ${item.status==='coming-soon'?'disabled':''}><span></span></label><small>${item.status==='coming-soon'?'Coming soon':item.status==='live'?'Live service':'Interactive preview'}</small></article>`}).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=>`<div class="productedit"><input data-id="${p.id}" data-field="name" value="${p.name.replaceAll('"','"')}"><input data-id="${p.id}" data-field="price" type="number" step="0.01" value="${(p.price_cents/100).toFixed(2)}"></div>`).join('')}
|
||
function orderRows(orders){if(!orders.length){els.orders.innerHTML='<div class="empty">Waiting for the first table order…</div>';return}const labels={accepted:'Confirm',delivered:'Delivered',closed:'Close'};els.orders.innerHTML=orders.map(o=>`<article class="order" data-status="${o.status}"><div class="orderhead"><strong>Table ${o.table_number}</strong><span class="badge">${o.status}</span></div><div class="items">${o.items.map(i=>`${i.qty}× ${i.name}`).join(' · ')}</div>${o.note?`<div class="muted">“${o.note}”</div>`:''}<div class="muted" style="margin:8px 0">${new Date(o.created_at).toLocaleTimeString()}</div><div class="actions">${['accepted','delivered','closed'].map(s=>`<button class="button small ${s==='accepted'?'':'secondary'}" data-order="${o.id}" data-status="${s}">${labels[s]}</button>`).join('')}</div></article>`).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=>`<div class="payment" data-payment-card="${p.id}"><div><span>Table ${p.table_number} paid</span><strong>€${(p.amount_cents/100).toFixed(2)} · ${p.amount_sats.toLocaleString()} sats</strong></div><button class="payment-ack" data-payment="${p.id}">Got it</button></div>`).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);
|