Files
archy/docker/justworks/business/public/assets/dashboard.js
T
yaya 94899e69d3 feat: integrate Just Works app and native business setup
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.
2026-10-08 19:39:38 +01:00

24 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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('"','&quot;')}"><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);