Fix NewBook write mechanism, bundle push extension, remove vestigial hotel_id

- setBookingCustomFields: use bookings_update's own custom_fields array
  (confirmed from the real legacy plugin code) instead of
  instance_custom_fields_set, which was only ever proven for a different,
  unrelated plugin
- online_checkin_complete is now a configurable per-hotel custom field
  (online_checkin_complete_custom_field_id setting), not a bogus top-level
  bookings_update field NewBook would silently ignore
- Fix field-name mismatches vs a real live NewBook booking response:
  booking_arrival/departure/length, guest address (street/city/state/
  postcode/country, not a single field), phone/mobile (in contact_details,
  not top-level), and pickPrimaryGuest now checks primary_client (was
  checking fields that don't exist in the real API)
- Bundle the standalone push extension into backend/extension-src/, add
  GET /api/extension/download (zips on request via archiver) and a
  download button on the Settings page
- Remove newbook_hotel_id — vestigial from the legacy system's multi-hotel
  routing model, never actually reached NewBook's API, this stack is
  single-hotel-per-deployment so there's nothing to route between

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-09-01 18:11:35 +00:00
parent 3b2e25df3f
commit 4d3774ef0d
25 changed files with 3557 additions and 15 deletions

View file

@ -1,14 +1,14 @@
import { useEffect, useState } from 'react'
import { MonitorCog } from 'lucide-react'
import { MonitorCog, Download } from 'lucide-react'
import {
fetchSettings, updateSettings, testNewbookConnection,
fetchKioskPasswordStatus, setKioskPassword,
fetchKioskPasswordStatus, setKioskPassword, extensionDownloadUrl,
} from '../api'
import type { AppSettings } from '../types'
export default function Settings() {
const [settings, setSettings] = useState<AppSettings>({
newbook_hotel_id: '', regcard_custom_field_id: '',
regcard_custom_field_id: '',
online_checkin_complete_custom_field_id: '', terms_and_conditions_text: '',
})
const [loading, setLoading] = useState(true)
@ -76,13 +76,6 @@ export default function Settings() {
<div className="section-title">NewBook Integration</div>
<div className="field-row">
<div className="field">
<label>NewBook Hotel / Location ID</label>
<input
type="text" value={settings.newbook_hotel_id}
onChange={e => setSettings(s => ({ ...s, newbook_hotel_id: e.target.value }))}
/>
</div>
<div className="field">
<label>Regcard PDF Link — Custom Field ID</label>
<input
@ -127,6 +120,20 @@ export default function Settings() {
</button>
</div>
<div className="section-title">Push Extension</div>
<p className="field-hint" style={{ marginBottom: 10 }}>
Standalone Chrome extension — adds a "Send to Check-In Tablet" button when a NewBook booking
is open. Not published anywhere yet, so it needs installing manually on each reception PC:
download, unzip, then <code>chrome://extensions</code> → enable Developer mode → Load
unpacked → select the unzipped folder. It'll need a Terminal API key from the Terminals page
to actually authorize pushes — create one there first.
</p>
<div style={{ marginBottom: 24 }}>
<a className="btn" href={extensionDownloadUrl()} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, textDecoration: 'none' }}>
<Download size={16} strokeWidth={1.75} /> Download Extension (.zip)
</a>
</div>
<div className="section-title">Kiosk Tablets</div>
<p className="field-hint" style={{ marginBottom: 10 }}>
A shared password every check-in tablet uses to set itself up at <code>/tablet-checkin/kiosk</code> —