import { isNewBookLoggedIn } from '../newbook-auth.js'; // Same regex as background.js — re-derived independently here from the live // tab URL each time the popup opens (rather than trusting chrome.storage // left over from the background script) so a stale badge state can never // show a booking that isn't actually the one currently open. Confirmed // 2026-09-01 by the user directly (real NewBook page URLs). const BOOKING_URL_RE = /\/bookings_(?:view|edit|checkin)\/(\d+)/i; const contentEl = document.getElementById('content'); const nbStatusEl = document.getElementById('nbStatus'); async function main() { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const bookingMatch = tab?.url?.match(BOOKING_URL_RE); const bookingId = bookingMatch ? bookingMatch[1] : null; isNewBookLoggedIn().then(ok => { nbStatusEl.textContent = ok ? 'NewBook: signed in' : 'NewBook: not detected'; nbStatusEl.className = `nb-status ${ok ? 'ok' : 'no'}`; }); const { baseUrl, apiKey, defaultDeviceId } = await chrome.storage.local.get(['baseUrl', 'apiKey', 'defaultDeviceId']); if (!baseUrl || !apiKey) { contentEl.innerHTML = `
Not set up yet — add the tablet-checkin URL and a terminal API key first.
Open Settings `; document.getElementById('openSettings').addEventListener('click', e => { e.preventDefault(); chrome.runtime.openOptionsPage(); }); return; } // Two independent sections: tablet status + reset is always shown (not // gated on a booking being open) so reception can clear a stuck-busy // tablet from the terminal without needing to be on a booking page at all. contentEl.innerHTML = ``; const tabletsEl = document.getElementById('tabletsSection'); const bookingEl = document.getElementById('bookingSection'); let devices = []; try { const res = await fetch(`${baseUrl}/api/staff/devices`, { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || `HTTP ${res.status}`); devices = await res.json(); } catch (err) { tabletsEl.innerHTML = `Could not reach tablet-checkin: ${escapeHtml(err.message)}
`; return; } renderTablets(tabletsEl, devices, baseUrl, apiKey); if (!bookingId) { bookingEl.innerHTML = `Open a booking in NewBook to send it to a check-in tablet.
`; return; } renderBooking(bookingEl, bookingId, devices, defaultDeviceId, baseUrl, apiKey); } function renderTablets(el, devices, baseUrl, apiKey) { if (!devices.length) { el.innerHTML = `No tablets registered yet
`; return; } el.innerHTML = `Tablets
Booking #${bookingId}
Booking #${bookingId}
No tablets registered yet
`; return; } el.innerHTML = `Booking #${bookingId}
${note ? `${escapeHtml(note)}
` : ''} `; const deviceSelect = document.getElementById('device'); const sendBtn = document.getElementById('send'); const msgEl = document.getElementById('msg'); // Setting .value to a disabled (busy) option is a no-op in every browser // that matters here, so a busy default just falls through to whatever the // browser's own first-enabled-option default would have been anyway. if (defaultDeviceId) deviceSelect.value = defaultDeviceId; sendBtn.addEventListener('click', async () => { sendBtn.disabled = true; setMsg(msgEl, 'Sending…', 'warn'); const result = await sendPush(bookingId, deviceSelect.value, baseUrl, apiKey); if (result.ok) { setMsg(msgEl, 'Sent — the tablet should navigate now.', 'ok'); } else if (result.status === 409) { setMsg(msgEl, result.error || 'That tablet is busy with another guest right now.', 'warn'); } else { setMsg(msgEl, `Failed: ${result.error}`, 'error'); } sendBtn.disabled = false; }); } async function sendPush(bookingId, deviceId, baseUrl, apiKey) { try { const res = await fetch(`${baseUrl}/api/staff/push`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, body: JSON.stringify({ booking_id: bookingId, device_id: deviceId }), }); const body = await res.json().catch(() => ({})); if (!res.ok) return { ok: false, status: res.status, error: body.error || `HTTP ${res.status}` }; return { ok: true }; } catch (err) { return { ok: false, status: 0, error: err.message }; } } function setMsg(el, text, kind) { el.textContent = text; el.className = `msg ${kind}`; } function escapeHtml(s) { const div = document.createElement('div'); div.textContent = s; return div.innerHTML; } main();