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) }