import type { ReactNode, Ref } from 'react'; import { type BuiltInBookingFieldName, type MeetingAvailability } from '../../schemas/meeting-booking-schema'; /** * One field in a host-supplied row. `name` is a built-in (`email`, `firstName`, * `lastName`) or a HubSpot-declared question's `name`. */ export interface BookingFieldSlot { /** Built-ins autocomplete; a HubSpot question's `name` is whatever the link declares. */ name: BuiltInBookingFieldName | (string & NonNullable); /** Columns out of four at `md` and up. Defaults to an even split of the row. */ span?: BookingFieldSpan; } /** Columns out of `GRID_COLUMNS` at `md` and up — the keys of `SPAN_CLASS`. */ export type BookingFieldSpan = keyof typeof SPAN_CLASS; export type BookingFieldRow = BookingFieldSlot[]; /** * A HOST-supplied consent row — the block the waitlist form draws for its SMS * consent, here for "I agree to the Privacy Policy and to be contacted". It is * the host's copy and the host's link, so it is a prop, not HubSpot metadata; * HubSpot's own `legalConsent` block (verbatim, declared on the link) renders * alongside when the link carries one. * * A client-side gate, exactly like the waitlist's: Continue is refused until it * is ticked, and the tick rides in the payload as `hostConsent` — which the * host's book route strips as an undeclared key. It is not a HubSpot consent * record; declare `legalConsent` on the link when one is required. */ export interface BookingFormConsent { label: ReactNode; description?: ReactNode; /** Shown under the row when Continue is pressed unticked. */ errorMessage?: string; } /** Static so Tailwind's scanner sees every class — a template built from a * runtime span would compile to nothing. */ declare const SPAN_CLASS: { readonly 1: "md:col-span-1"; readonly 2: "md:col-span-2"; readonly 3: "md:col-span-3"; readonly 4: "md:col-span-4"; }; /** The submit copy every standalone `BookingForm` gets; the slot-first preset reads the same constant. */ export declare const DEFAULT_SUBMIT_LABEL = "Confirm Booking"; /** The even split of four columns over `count` slots; a remainder goes to the * leading slots (three slots → 2/1/1), so a row never leaves a trailing gap. */ export declare const evenSpan: (count: number, index: number) => BookingFieldSpan; export interface BookingFormProps { availability: MeetingAvailability; meetingId: string; /** Absent in `deferSlot` mode — the slot is chosen AFTER these answers. */ startTimeMs?: number; durationMs?: number; /** IANA zone the confirmation/invite should render in (parent-resolved). * Null until hydration, which is why `deferSlot` relaxes it. */ timezone: string | null; /** * Collect-only mode (`flow="details-first"`): validate against the deferred * schema, and hand the values up instead of POSTing. The parent re-attaches * the authoritative slot/duration/timezone when it submits. */ deferSlot?: boolean; /** Repopulates the form on a remount — the back edge, or an error return. */ initialValues?: Record; /** Defaults to "Confirm Booking"; details-first says "Continue". */ submitLabel?: string; /** Small print beside the submit (details-first sets expectations). */ footerNote?: string; /** * Re-arrange the fields into rows instead of the built-in order (email, the * name pair, then each declared question full width). Reuse, not a fork: the * SAME controls, validation, consent block and honeypot — only their grouping * changes, so a host can match a mock without owning the machine. * * A slot naming nothing is skipped; a declared question no row claims is * appended full width. Both are deliberate — see `slotNode`/`unplacedFields`. */ fieldRows?: BookingFieldRow[]; /** Host-supplied consent row, rendered after the fields — see `BookingFormConsent`. */ consent?: BookingFormConsent; isSubmitting: boolean; onSubmit: (payload: Record) => Promise; /** From useHumanitySignals — parent owns the instance so it can resetSignals(). */ honeypotInputProps: { ref: Ref; name: string; }; getSignals: () => Record; } /** * BookingForm — attendee details + the link's declared custom questions + * verbatim legal-consent copy. ContactForm's scaffolding (react-hook-form + * zodResolver + lib field primitives); a sibling rather than a `` * configuration because of the dynamic HubSpot `formFields`, the per-checkbox * consent model, and the first/last-name field model — none expressible via * `hideFields`/`extraTopField`. * * Bot protection is LOAD-BEARING: without the humanity signals in the body, * the host's `verifyHuman` degrades to first-party-only BotID (fails open for * external embedders). Honeypot + elapsed-ms are merged into the POST at * submit; the parent calls `resetSignals()` after a SLOT_TAKEN refetch so a * legitimate retry isn't flagged too-fast. */ export declare function BookingForm({ availability, meetingId, startTimeMs, durationMs, timezone, deferSlot, initialValues, submitLabel, footerNote, fieldRows, consent, isSubmitting, onSubmit, honeypotInputProps, getSignals, }: BookingFormProps): import("react").JSX.Element; /** * Cold-start placeholder for `flow="details-first"`, where the FORM is the * first thing in the action panel. * * The slot-first skeleton (`SlotPickerSkeleton`) would put a grey calendar * where the form belongs — above the fold on a page whose entire content is * this card. Same footprint discipline as its sibling: fixed heights, and no * shift when the real form swaps in for single-line fields. Two footprints it * cannot know: a textarea row is taller than its placeholder (the rows carry no * type), and a HubSpot `legalConsent` block only exists once availability lands. */ export declare function BookingFormSkeleton({ fieldRows, consent, footerNote, }: { fieldRows?: BookingFieldRow[]; /** Whether the host adds its consent row (drawn only then). A HubSpot * `legalConsent` block is unknown until availability lands — the one * footprint this skeleton cannot budget for. */ consent?: boolean; footerNote?: string; }): import("react").JSX.Element; export {}; //# sourceMappingURL=booking-form.d.ts.map