export function registerKioskFlow(Alpine) { Alpine.data('kioskFlow', (config = {}) => ({ step: 'select', queueId: null, customerName: '', customerPhone: '', ticket: null, error: null, loading: false, issueUrl: config.issueUrl, csrf: config.csrf, selectQueue(id) { this.queueId = id; this.step = 'details'; }, async issue() { if (! this.queueId) return; this.loading = true; this.error = null; try { const res = await fetch(this.issueUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': this.csrf, }, body: JSON.stringify({ queue_id: this.queueId, customer_name: this.customerName || null, customer_phone: this.customerPhone || null, }), }); const data = await res.json(); if (! res.ok) throw new Error(data.message || 'Could not issue ticket'); this.ticket = data.data; this.step = 'ticket'; } catch (e) { this.error = e.message || 'Something went wrong'; } finally { this.loading = false; } }, reset() { this.step = 'select'; this.queueId = null; this.customerName = ''; this.customerPhone = ''; this.ticket = null; this.error = null; }, })); Alpine.data('displayBoard', (dataUrl) => ({ payload: null, async poll() { try { const res = await fetch(dataUrl, { headers: { Accept: 'application/json' } }); this.payload = await res.json(); if (this.payload?.announcements?.length && window.speechSynthesis) { const msg = this.payload.announcements[0]?.message; if (msg) { const utter = new SpeechSynthesisUtterance(msg); window.speechSynthesis.speak(utter); } } } catch (e) { console.error('Display poll failed', e); } }, init() { this.poll(); setInterval(() => this.poll(), 5000); }, })); Alpine.data('mobileTracker', (pollUrl) => ({ data: null, ahead: 0, async poll() { try { const res = await fetch(pollUrl, { headers: { Accept: 'application/json' } }); const json = await res.json(); this.data = json.data; this.ahead = json.customers_ahead ?? 0; } catch (e) { console.error('Tracker poll failed', e); } }, init() { this.poll(); setInterval(() => this.poll(), 10000); }, })); }