Working with Time Slots
With externalBooking: true the calendar stops after the visitor picks a time slot: the built-in form does not open, the "Confirm" button under the slot list disappears, and the component emits time-slot-changed. Your app renders its own call to action and submits the booking with the exported functions.
<template>
<ZaptimeCalendar :config="config" @time-slot-changed="(s) => (slot = s)" />
<form v-if="slot" @submit.prevent="submit">
<p>{{ slot.start }} – {{ slot.end }}</p>
<input v-model="form.firstName" placeholder="First name" />
<input v-model="form.lastName" placeholder="Last name" />
<input v-model="form.email" type="email" required placeholder="Email" />
<input v-model="form.phone" type="tel" placeholder="Phone" />
<p v-if="error" role="alert">{{ error }}</p>
<button type="submit" :disabled="submitting">Book</button>
</form>
<p v-if="booked">Booked. Confirmation sent to {{ booked.data.userEmail }}.</p>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { ZaptimeCalendar, book } from "@zaptime/vue3";
import type { ZaptimeConfig } from "@zaptime/vue3";
import { SlotNoLongerAvailableError, slotNoLongerAvailableText, useCalendar } from "@zaptime/core";
import type { ReservationResponse, TimeSlot } from "@zaptime/core";
const config: ZaptimeConfig = {
token: "<API_TOKEN>",
externalBooking: true,
};
const slot = ref<TimeSlot | undefined>();
const form = reactive({ firstName: "", lastName: "", email: "", phone: "" });
const submitting = ref(false);
const error = ref<string | undefined>();
const booked = ref<ReservationResponse | undefined>();
const { getDays } = useCalendar();
async function submit() {
submitting.value = true;
error.value = undefined;
try {
const res = await book({ ...form });
if (res.success) {
booked.value = res;
slot.value = undefined;
} else {
error.value = "Please check the entered details.";
}
} catch (e) {
if (e instanceof SlotNoLongerAvailableError) {
error.value = slotNoLongerAvailableText(config.locale);
slot.value = undefined;
await getDays(); // refresh availability
} else {
error.value = "Booking failed. Please try again.";
}
} finally {
submitting.value = false;
}
}
</script>All functions read the token, API base URL, selected time slot and timezone from the calendar state. Pass calendarId when the calendar was rendered with a calendar-id.
Book
Books the selected time slot immediately.
book({
email: string,
firstName?: string,
lastName?: string,
phone?: string,
seats?: number, // defaults to 1
location?: Location, // defaults to the event type's first location
customFields?: CustomFieldCollected[],
guests?: string[], // guest email addresses
calendarId?: string,
}): Promise<ReservationResponse>Throws SlotNoLongerAvailableError when the API answers 409 (the slot was taken in the meantime). Throws a generic Error when no time slot is selected or the request fails. When the event type has redirectAfterBookingUrl set, book navigates to it after a successful call.
Reserve
Holds the selected time slot without booking it. The hold is refreshed automatically every 15 minutes while the page stays open. Use it for multi-step flows: reserve the slot, collect payment or more details, then confirm.
reserve({
email: string,
firstName?: string,
lastName?: string,
phone?: string,
seats?: number,
location?: Location,
customFields?: CustomFieldCollected[],
guests?: string[],
calendarId?: string,
}): Promise<ReservationResponse>Same errors as book.
Confirm
Confirms a previously reserved time slot and stops the refresh. Details passed here are merged into the reservation.
confirm({
calendarId?: string,
firstName?: string,
lastName?: string,
phone?: string,
customFields?: CustomFieldCollected[],
guests?: string[],
}): Promise<ReservationResponse>Throws when nothing was reserved for the given calendarId.
Cancel
Cancels a previously reserved time slot and stops the refresh. Resolves to false when nothing was reserved.
cancel(calendarId?: string): Promise<boolean>Reschedule
Moves an existing reservation to the selected time slot. Requires the calendar to be initialized with reservationUuid in the config. Imported from @zaptime/core.
import { reschedule, RescheduleNotAllowedError } from "@zaptime/core";
reschedule(calendarId?: string): Promise<ReservationResponse>Throws RescheduleNotAllowedError when the API answers 403 (notice period violated, reservation already started, or rescheduling disabled). See Rescheduling.
Stop the reservation refresh
Call this on unmount when a reservation may still be in flight, so the interval does not keep running in the background.
import { stopReservationRefresh } from "@zaptime/core";
onUnmounted(() => stopReservationRefresh());Reserve and confirm flow
import { reserve, confirm, cancel } from "@zaptime/vue3";
const reservation = await reserve({ email: "john@doe.test" });
try {
await chargeCustomer(reservation.data.uuid);
await confirm({ firstName: "John", lastName: "Doe" });
} catch {
await cancel();
}Custom fields
Custom fields are identified by the UUIDs of the booking form fields defined in the dashboard. See Custom Fields for where to find them. To render the dashboard-defined form yourself, use the useBookingForm composable, which knows every field's type, label, required flag and options.
Error messages
@zaptime/core exports two helpers that return the localized message for the given locale, with an English fallback:
import { slotNoLongerAvailableText, rescheduleNotAllowedText } from "@zaptime/core";
slotNoLongerAvailableText(config.locale);
rescheduleNotAllowedText(config.locale);The texts can be overridden in locale.confirmationForm.slotNoLongerAvailable and locale.confirmationForm.rescheduleNotAllowed.
