tablet-checkin/frontend/src/types.ts
jtricerolph 2b83f47556 Kiosk redesign: logo, tablet sizing, 3-step flow, better email/car-reg logic
- Uploadable reception logo (Settings, PNG/JPEG, public GET so the kiosk
  can show it without a staff login) — shown top-center on the kiosk
  login and idle screens, Welcome copy centered underneath.
- Tablet-sized styling pass on all kiosk views for a ~10" tablet (bigger
  touch targets, base text, spacing) rather than the general app's
  desktop-first sizing.
- New booking-confirm step (name/arrive/nights/depart/rate name(s)/rate
  total) replaces the old partial review step.
- Contact details, car registration and marketing merged into one step
  (was 3 separate steps) — 5 steps down to 3 overall.
- Email handling now matches the legacy system's confirmed behaviour:
  excluded-domain (OTA/agent relay) addresses are filtered out of what's
  shown as 'on file' entirely, not just flagged, and a new
  excluded_email_domains Settings field (CSV) replaces the old hardcoded
  domain list so any OTA/agent domain can be added without a code change.
- Single email field design: only pre-filled when there's exactly one
  valid existing email, left blank (not guessed) when there are several —
  marketing opt-in only ever touches whatever's in that one field, never
  blanket-subscribes multiple pre-existing emails (project_checkin memory,
  2026-09-01 'stay in touch' note).
- Car registration now allows multiple additions — add/remove list UI,
  buildContactDetails appends each new one not already on file rather
  than the old single-slot overwrite.
- Signature now required to actually complete (tracked live via
  SignatureCanvas onEnd, not just caught on submit) alongside accepted
  terms — Complete Check-In stays disabled until both are true.
- T&C block is height-capped with its own internal scroll (keeps the step
  on one non-scrolling screen) and now renders basic HTML (<b>, <br>,
  <p>, <ul> etc) instead of plain text — content is admin-authored
  (settings capability required), same trust boundary as any other
  setting. PDF generation strips tags back to plain text since pdfkit
  can't render HTML.
- PDF and booking-confirm screen both surface tariff name(s) (deduped
  across tariffs_quoted in case the rate changed mid-stay) and nights,
  not just the total.
- Also fixed: injected extension buttons could hang forever on 'Loading
  tablets…' with no feedback if the extension was reloaded without also
  refreshing the NewBook tab (stale content script) — now surfaces a
  clear 'Extension was reloaded — refresh this page' message instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 21:21:30 +00:00

145 lines
3.5 KiB
TypeScript

export type DeviceStatus = 'idle' | 'busy'
export interface Device {
device_id: string
name: string
status: DeviceStatus
last_seen: string | null
created_at: string
}
export interface Terminal {
terminal_id: string
name: string
created_at: string
last_used_at: string | null
revoked_at: string | null
}
export interface CreatedTerminal extends Terminal {
api_key: string // present only in the create response — never again
}
export type CheckinStatus = 'pending' | 'complete' | 'failed'
export interface MarketingSyncSummary {
provider: string
status: 'pending' | 'success' | 'failed'
error: string | null
}
export interface Checkin {
id: number
booking_id: string
guest_id: string | null
hotel_id: string
device_id: string | null
device_name: string | null
guest_name: string | null
guest_email: string | null
pdf_path: string | null
opted_in: boolean
status: CheckinStatus
error: string | null
submitted_at: string
marketing_sync: MarketingSyncSummary[] | null
}
export type MarketingProviderId = 'sendy' | 'mailchimp'
export interface SendyConfig {
api_key?: string
install_url?: string
list_id?: string
}
export interface MailchimpConfig {
api_key?: string
server_prefix?: string
audience_id?: string
}
export interface MarketingProvider {
provider: MarketingProviderId
active: boolean
config: SendyConfig | MailchimpConfig
updated_at: string
implemented: boolean
}
export interface MarketingSyncRow {
id: number
checkin_id: number
provider: string
status: 'pending' | 'success' | 'failed'
attempts: number
last_attempt_at: string | null
error: string | null
created_at: string
booking_id: string
guest_name: string | null
guest_email: string | null
}
export interface AppSettings {
regcard_custom_field_id: string
online_checkin_complete_custom_field_id: string
terms_and_conditions_text: string
excluded_email_domains: string
}
// ── Guest-facing token flow (routes/checkin.js) ─────────────────────────────
export interface CheckinBooking {
booking_id: string
arrival: string | null
departure: string | null
site_name: string | null
tariff_total: number | string | null
tariff_names: string[]
nights: number | null
}
// No is_relay flag any more — excluded-domain emails are filtered out
// server-side entirely (matches the legacy system's confirmed behaviour,
// see project_checkin_legacy_plugin), not just flagged. had_excluded_email
// on CheckinGuest below covers "was something hidden" without exposing it.
export interface ExistingEmail {
content: string
}
export interface CheckinGuest {
guest_id: string | null
name: string
address: string
phone: string
existing_emails: ExistingEmail[]
had_excluded_email: boolean
car_regos: string[]
}
export interface CheckinTokenData {
booking: CheckinBooking
guest: CheckinGuest
terms_text: string
}
export interface SubmitCheckinPayload {
email: string
car_regos: string[]
opted_in: boolean
accepted_terms: boolean
signature_png_base64: string
}
// ── Staff auth ──────────────────────────────────────────────────────────────
export interface User {
user_id: number
name: string
email: string
is_admin: boolean
caps: string[] // bare slugs — verify?app=tablet-checkin strips the prefix
}
export function can(user: User, cap: string): boolean {
return user.is_admin || user.caps.includes(cap)
}