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.
14 lines
5.2 KiB
JavaScript
14 lines
5.2 KiB
JavaScript
import {frame} from './ds/frame.js';
|
||
import {npubFrom} from './ds/env.js';
|
||
const app=document.querySelector('#app'),npub=npubFrom();let merchant,mode='now',delay=0,guests=2,busy=false;
|
||
const esc=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
||
function timeValue(offsetMinutes){const date=new Date(Date.now()+offsetMinutes*60000);date.setMinutes(Math.ceil(date.getMinutes()/30)*30,0,0);return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}T${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}`}
|
||
function error(message){app.innerHTML=`<section class="center"><span class="orbit">⌁</span><p class="eyebrow">Table booking</p><h1>${esc(message)}</h1><small>Use book.justworks.cash/?npub=npub1…</small></section>`}
|
||
function render(data){merchant=data.merchant;document.title=`Book ${merchant.name} · Just Works`;frame(app,{label:'Bookings',surface:'book',html:`<section class="pay-shell"><div class="pay-copy"><p class="eyebrow">Reserve a table</p><h1>When should we expect you?</h1><p>Your table at ${esc(merchant.name)}, without the phone call.</p></div><form id="form" class="pay-card form-card"><div class="merchant"><span>⌁</span><div><strong>${esc(merchant.name)}</strong><small>Taking bookings</small></div></div><div class="chips booking-mode">${[['now','Soon'],['later','Later']].map(([value,label])=>`<button type="button" class="${mode===value?'active':''}" data-mode="${value}">${label}</button>`).join('')}</div><div id="when"></div><label class="field"><span>Guests</span><div class="stepper"><button type="button" id="less" aria-label="Fewer guests">−</button><strong id="guestCount">2</strong><button type="button" id="more" aria-label="More guests">+</button></div></label><label class="field"><span>Your name</span><input id="name" autocomplete="name" maxlength="80" placeholder="Who should we expect?" required></label><label class="field"><span>Note <small>(optional)</small></span><input id="notes" maxlength="240" placeholder="High chair, outside table…"></label><button id="submit" type="submit" class="primary">Request table <span>→</span></button><p class="privacy">No account · no app · sent directly to ${esc(merchant.name)}</p></form></section>`});document.querySelectorAll('[data-mode]').forEach(button=>button.onclick=()=>setMode(button.dataset.mode));document.querySelector('#less').onclick=()=>setGuests(guests-1);document.querySelector('#more').onclick=()=>setGuests(guests+1);document.querySelector('#form').onsubmit=submit;renderWhen()}
|
||
function setMode(next){mode=next;document.querySelectorAll('[data-mode]').forEach(button=>button.classList.toggle('active',button.dataset.mode===mode));renderWhen()}
|
||
function setGuests(next){guests=Math.max(1,Math.min(20,next));document.querySelector('#guestCount').textContent=guests}
|
||
function renderWhen(){const host=document.querySelector('#when');if(mode==='now'){host.innerHTML=`<label class="field"><span>Arriving in</span><div class="chips">${[[0,'Now'],[5,'+5 min'],[10,'+10 min'],[15,'+15 min']].map(([value,label])=>`<button type="button" data-delay="${value}" class="${delay===value?'active':''}">${label}</button>`).join('')}</div></label>`;host.querySelectorAll('[data-delay]').forEach(button=>button.onclick=()=>{delay=Number(button.dataset.delay);renderWhen()})}else host.innerHTML=`<label class="field"><span>Date and time</span><input id="date" type="datetime-local" min="${timeValue(30)}" value="${timeValue(24*60)}" required></label>`}
|
||
async function submit(event){event.preventDefault();if(busy)return;busy=true;const button=document.querySelector('#submit');button.disabled=true;button.firstChild.textContent='Sending… ';try{const scheduled=mode==='now'?new Date(Date.now()+delay*60000):new Date(document.querySelector('#date').value);const response=await fetch('/api/bookings',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({npub,name:document.querySelector('#name').value,notes:document.querySelector('#notes').value,guests,mode,scheduled_at:scheduled.toISOString()})});const result=await response.json();if(!response.ok)throw new Error(result.error||'Could not book');confirm(result)}catch(error){button.disabled=false;button.firstChild.textContent='Try again ';alert(error.message)}finally{busy=false}}
|
||
function confirm(result){const date=new Date(result.scheduled_at);frame(app,{label:'Bookings',surface:'book',html:`<section class="center"><span class="orbit">✓</span><p class="eyebrow">Request sent</p><h1>Table for ${result.guests} requested.</h1><p>${esc(merchant.name)} received your booking for <strong>${date.toLocaleDateString([],{weekday:'long',day:'numeric',month:'long'})} at ${date.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</strong>.</p><small>The merchant still needs to confirm it. Keep this screen open for your reference.</small></section>`})}
|
||
if(!npub)error('Add the merchant npub to book.');else fetch(`/api/order-merchant?npub=${encodeURIComponent(npub)}`,{cache:'no-store'}).then(async response=>{const data=await response.json();if(!response.ok)throw new Error(data.error);render(data)}).catch(reason=>error(reason.message||'Merchant unavailable'));
|