Skip to content

useBookingForm

The booking form of an event type is defined in the dashboard: which questions the attendee answers, in which order, which are required. useBookingForm exposes that schema so you can render it with your own inputs, and collects the values in the shape book and reserve expect.

ts
import { useBookingForm } from "@zaptime/core";

const { bookingForm, setBookingForm, setCustomFieldValue, collectFormValues } = useBookingForm(calendarId?);

Set booking form

Called during initialization with customFields from the remote configuration. See Getting started.

ts
setBookingForm(remote.customFields);

Booking form

A computed array of fields in dashboard order.

ts
type CustomField = {
  uuid: string;
  name: string;
  label: string;
  type: "text" | "email" | "phone" | "number" | "textarea" | "switch" | "checkbox" | "select" | "multiselect" | "radio";
  required: boolean;
  placeholder?: string;
  options?: string[]; // select, multiselect, radio
  mergeTag?: "FIRST_NAME" | "LAST_NAME" | "EMAIL" | "PHONE" | string;
  value?: string | number | boolean | string[];
};

Fields with a mergeTag of FIRST_NAME, LAST_NAME, EMAIL or PHONE are the attendee identity fields. Email is mandatory server-side, so render the EMAIL field as required regardless of its flag.

Set custom field value

Writes a value into a field by UUID. Value types by field type:

Field typeValue
text, email, phone, textarea, select, radiostring
numbernumber
switch, checkboxboolean
multiselectstring[]
ts
setCustomFieldValue(field.uuid, value);

Collect form values

Returns an object ready to spread into book or reserve: identity fields mapped to email, firstName, lastName, phone; every other field under customFields; guest emails from useGuests under guests.

ts
const values = collectFormValues();
// { email, firstName, lastName, phone, customFields: [{ uuid, value }], guests }

await book({ calendarId, ...values });

Rendering by type

vue
<div v-for="field in bookingForm" :key="field.uuid">
  <label :for="field.uuid">{{ field.label }}</label>

  <textarea
    v-if="field.type === 'textarea'"
    :id="field.uuid" :required="field.required" :placeholder="field.placeholder"
    @input="setCustomFieldValue(field.uuid, $event.target.value)"
  />

  <select
    v-else-if="field.type === 'select'"
    :id="field.uuid" :required="field.required"
    @change="setCustomFieldValue(field.uuid, $event.target.value)"
  >
    <option value="" disabled selected>{{ field.placeholder }}</option>
    <option v-for="o in field.options" :key="o" :value="o">{{ o }}</option>
  </select>

  <select
    v-else-if="field.type === 'multiselect'"
    :id="field.uuid" :required="field.required" multiple
    @change="setCustomFieldValue(field.uuid, Array.from($event.target.selectedOptions, (o) => o.value))"
  >
    <option v-for="o in field.options" :key="o" :value="o">{{ o }}</option>
  </select>

  <fieldset v-else-if="field.type === 'radio'">
    <label v-for="o in field.options" :key="o">
      <input type="radio" :name="field.uuid" :value="o" :required="field.required" @change="setCustomFieldValue(field.uuid, o)" />
      {{ o }}
    </label>
  </fieldset>

  <input
    v-else-if="field.type === 'checkbox' || field.type === 'switch'"
    :id="field.uuid" type="checkbox" :required="field.required"
    @change="setCustomFieldValue(field.uuid, $event.target.checked)"
  />

  <input
    v-else
    :id="field.uuid"
    :type="field.type === 'phone' ? 'tel' : field.type"
    :required="field.required || field.mergeTag === 'EMAIL'"
    :placeholder="field.placeholder"
    @input="setCustomFieldValue(field.uuid, field.type === 'number' ? Number($event.target.value) : $event.target.value)"
  />
</div>

Swap each element for the input component of your design system; keep the bindings.