Calendar Booking

Kitchen sink demo showing the calendar component in different meeting states.

No existing meeting. Buyer selects a date and time.

Full page version

Acme Solutions

Acme Solutions

Introduction Call

March 2026
SunMonTueWedThuFriSat
UTC

Friday, March 20 UTC

Component documentation

CalendarBookingView is a stateful booking component that handles the full meeting lifecycle: selecting a time, viewing a pending request, and displaying a confirmed meeting. It can be rendered inline or inside CalendarModal.

Props

PropTypeDefaultDescription
vendorCalendarVendorRequired Vendor name, logo, meeting title, duration, and optional buffer hours.
buyerCalendarBuyerRequired Buyer name, email, and optional company. Used in confirmation view and tracking.
closablebooleanfalse Shows a close button in the header. Use when rendered inside a modal.
availabilityConfigCalendarAvailabilityConfigundefined Blocked dates, blocked day-of-week, and per-date blocked time ranges.
initialMeetingExistingMeetingundefined Pre-populate from backend state. When provided with status requested or confirmed, the component opens directly to the meeting info view. A cancelled status falls through to the calendar picker.

Events

EventPayloadDescription
book{ date: Date; time: string } Fires when the buyer confirms a time slot. Also fires on reschedule (edit then re-book).
cancel{ date: Date; time: string } Fires when the buyer cancels a request or confirmed meeting. Component returns to the calendar picker.
closenone Fires when the close button is clicked. Only relevant when closable is true.

Meeting states

The component manages three internal steps. When no initialMeeting is provided, it starts in the select step. When the backend already has meeting data, pass it via initialMeeting to skip straight to the appropriate view.

select

Calendar picker. The buyer chooses a date and time. This is the default state and the state after a cancel or reschedule action.

requested

Meeting request received. Shows the selected date/time with a "Pending" badge. The buyer can edit the time or cancel.

confirmed

Meeting confirmed by the vendor. Shows a "Confirmed" badge, calendar invite messaging, and the option to reschedule or cancel.

State flow

select→ buyer books →requested→ vendor confirms (backend) →confirmed
requestedorconfirmed→ buyer edits time or cancels →select

The transition from requested to confirmed happens in the backend. On the next page load, pass the updated initialMeeting with status: 'confirmed'. To force a re-render when the prop changes, use a :key bound to the status.

Usage examples

New booking (no backend state)

<CalendarBookingView
  :vendor="vendor"
  :buyer="buyer"
  @book="onBook"
  @cancel="onCancel"
/>

Pre-populated from backend (requested)

<CalendarBookingView
  :vendor="vendor"
  :buyer="buyer"
  :initial-meeting="{
    status: 'requested',
    date: meetingDate,
    time: { label: '10:00 AM', hour: 10, minute: 0, available: true }
  }"
  @book="onBook"
  @cancel="onCancel"
/>

Pre-populated from backend (confirmed)

<CalendarBookingView
  :vendor="vendor"
  :buyer="buyer"
  :initial-meeting="{
    status: 'confirmed',
    date: meetingDate,
    time: { label: '2:30 PM', hour: 14, minute: 30, available: true }
  }"
  @book="onReschedule"
  @cancel="onCancel"
/>

Inside a modal

<CalendarModal :open="modalOpen" @close="modalOpen = false">
  <CalendarBookingView
    :vendor="vendor"
    :buyer="buyer"
    :initial-meeting="existingMeeting"
    closable
    @book="onBook"
    @cancel="onCancel"
    @close="modalOpen = false"
  />
</CalendarModal>

Re-render on backend status change

<CalendarBookingView
  :key="meeting.status"
  :vendor="vendor"
  :buyer="buyer"
  :initial-meeting="meeting"
  @book="onBook"
  @cancel="onCancel"
/>

Binding :key to the meeting status ensures the component fully re-mounts when the backend transitions from requested to confirmed (or any other change).

Types reference

All types are exported from ~/components/calendar/types.

interface CalendarVendor {
  name: string
  logo: string
  meetingTitle: string
  meetingDuration: number
  bufferHours?: number        // hours before a slot is bookable (default: 4)
}

interface CalendarBuyer {
  name: string
  email: string
  company?: string
}

type MeetingStatus = 'requested' | 'confirmed' | 'cancelled'

interface ExistingMeeting {
  status: MeetingStatus
  date: Date                  // midnight-normalized date
  time: TimeSlot              // the booked slot
}

interface TimeSlot {
  label: string               // e.g. "10:00 AM"
  hour: number                // 0-23
  minute: number              // 0-59
  available: boolean
}

interface CalendarAvailabilityConfig {
  unavailableDates?: string[]                      // "YYYY-MM-DD" format
  unavailableSlots?: Record<string, BlockedTimeRange[]>
  unavailableDaysOfWeek?: number[]                 // 0=Sun, 6=Sat
}