package main import ( "fmt" "strconv" ) // CalcomBookingWidget renders the main booking widget container. // // The widget is divided into four regions, each with a stable ID so HTMX can // swap them independently: // // - #calcom-steps-{id} step indicator (OOB-updated on every transition) // - #calcom-date-grid-{id} the date picker // - #calcom-slots-{id} time slots, loaded after a date click // - #calcom-form-{id} booking form, loaded after a slot click // - #calcom-confirm-{id} success panel, populated after submit // // Each partial returned by the server emits hx-swap-oob directives to keep // the step indicator in sync with the user's progress, and to clear stale // regions when the user changes their mind (Change date / Change time) or // finishes (post-confirmation). templ CalcomBookingWidget(config BookingConfig) {
// Booking times render server-side in the MEETING's timezone (the // event's Cal.com availability schedule) — the visitor's browser // timezone is deliberately never detected or sent. Client-side // timezone plumbing is what caused the 2026-06-10 incident: a hidden // input's "UTC" default never got updated, every hx-include sent // timezone=UTC, and visitors saw UTC slot times. // // This script only: // - captures the browser locale for phone dial-code inference // - wires the date-grid selection highlight // - drives the phone country combobox // // Init is deferred to DOMContentLoaded: the locale input sits after // this script in the widget and doesn't exist while it parses. // Admin-authored fallback copy for a non-fixable booking failure // (Tier-2). Stamped once from block content here and hx-included into // the POST /book request (like locale) so HandleCreateBooking can read // it back. templ auto-escapes the value. Defaults to // DefaultUnavailableMessage when the admin leaves it blank (resolved in // block.go), so it is never empty. if config.Title != "" {

{ config.Title }

} if config.Description != "" {

{ config.Description }

} // Step indicator — wrapped in a stable container so partial responses // can replace its contents via hx-swap-oob.
@calcomStepIndicator(1)
if config.ShowTimezone {
All times shown in { config.TimeZone }
} // Saved-booking panel — normally hidden. The widget script reveals it on // DOMContentLoaded when this visitor's localStorage holds a booking for // this username/event, hiding the date grid below so they manage the // existing booking (cancel / redo) instead of double-booking. @calcomSavedBooking(config.BlockID) // Date picker section — wrapped so confirmation can OOB-clear it.
@calcomDateGrid(config)
// Time slots section — populated after a date click.
// Booking form section — populated after a slot click.
// Confirmation section — populated after successful booking.
// Central loading indicator shown during HTMX requests.
} // calcomStepIndicator renders the three-step progress indicator. `active` is // the user's current step (1 = Select Date, 2 = Select Time, 3 = Confirm, // 4 = Done — all three checkmarked). When active > n the step is rendered as // completed; when active == n it is current; when active < n it is upcoming. templ calcomStepIndicator(active int) {
@calcomStep(1, "Select Date", active)
@calcomStep(2, "Select Time", active)
@calcomStep(3, "Confirm", active)
} // calcomStep renders one step pill inside the progress indicator. templ calcomStep(n int, label string, active int) {
= n), templ.KV("bg-muted text-muted-foreground", active < n), }> if active > n { } else { { strconv.Itoa(n) } } { label }
} // calcomStepIndicatorOOB wraps calcomStepIndicator in the OOB-swap envelope // so partial responses can update the indicator out-of-band. templ calcomStepIndicatorOOB(blockID string, active int) {
@calcomStepIndicator(active)
} // calcomClearRegion emits an empty container with the given ID and an OOB // swap directive, used to wipe stale regions when the user transitions // between flow steps (Change date / Change time / post-confirmation). templ calcomClearRegion(id, extraClasses string) {
} // calcomDateGrid renders the calendar date picker. The prev/next buttons fire // hx-get against /api/plugins/calcomblock/date-grid, which returns a fresh // instance of this template scoped to a new WeekStart — innerHTML-swapped // into the #calcom-date-grid-{id} container in the widget root. templ calcomDateGrid(config BookingConfig) {
if config.CanGoBack { } else { } { config.RangeLabel }
for _, day := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} {
{ day }
}
// Offset the first date to its weekday column. Without these pad // cells a mid-week window pours in at the "Sun" column and every // date renders under the wrong header — the second bidbuddy // incident (2026-06-10): tapped the cell under "Tue", got Friday. for i, pad := 0, leadingBlanks(config.Dates); i < pad; i++ { } for _, date := range config.Dates { if date.IsPast { } else { } }
} // CalcomTimeSlots renders available time slots for a selected date. Returned // as the body of GET /slots; also emits OOB updates for the step indicator // (active=2) and an OOB clear of the form region so a previously-rendered // form for an earlier slot doesn't linger. templ CalcomTimeSlots(slots []TimeSlot, config SlotConfig) {

Available times for { config.SelectedDate }

if len(slots) == 0 {

No available times for this date. Please select another date.

} else {
for _, slot := range slots { }
}
// OOB: advance step indicator to "Select Time" + clear any stale form // from a previous slot selection. @calcomStepIndicatorOOB(config.BlockID, 2) @calcomClearRegion(fmt.Sprintf("calcom-form-%s", config.BlockID), "calcom-booking-form") } // CalcomBookingForm renders the contact form for booking. When the event type // has custom bookingFields configured, each is rendered via bookingField. When // the list is empty (typical for default event types or a transient fetch // error), a built-in name + email pair is rendered so the flow still works. // // Returned as the body of GET /form. Also emits an OOB update advancing the // step indicator to "Confirm". templ CalcomBookingForm(formConfig FormConfig) {

Your Details

Booking for { formConfig.SelectedDate } at { formConfig.SelectedTime }

// hx-include pulls the widget-level hidden inputs into the POST: locale // (phone dial-code inference) and unavailableMessage (admin-authored // Tier-2 copy). Both live in the widget root, outside this form partial.
// Honeypot: a real user can't see this off-screen input; bots and // over-eager autofill heuristics still hit it. The name is // deliberately non-semantic so Chrome's autofill doesn't fire on // "company" / "address" / "website". if len(formConfig.BookingFields) > 0 { for _, f := range formConfig.BookingFields { @bookingField(f, formConfig.BlockID, formConfig.DefaultCountryCode) } } else { @bookingField(BookingField{Name: "name", Label: "Name", Type: "name", Required: true, Placeholder: "Your name"}, formConfig.BlockID, "") @bookingField(BookingField{Name: "email", Label: "Email", Type: "email", Required: true, Placeholder: "your@email.com"}, formConfig.BlockID, "") } // Cap proof-of-work widget — rendered only when the block enables // captcha. On solve it injects a hidden cap-token field into this form, // which HandleCreateBooking verifies (server-authoritative: same flag). if formConfig.CaptchaEnabled {
@CaptchaWidget()
}
// OOB: advance step indicator to "Confirm". @calcomStepIndicatorOOB(formConfig.BlockID, 3) } // bookingField renders a single Cal.com bookingField in the correct widget for // its type. Unknown types fall back to a text input. defaultCountry is the ISO // region pre-selected in the phone-field country combobox ("" = none). templ bookingField(f BookingField, blockID string, defaultCountry string) {
switch f.Type { case "textarea": case "boolean", "checkbox":
{ f.Placeholder }
case "select": case "multiselect":
for _, opt := range f.Options { }
case "radio":
for _, opt := range f.Options { }
case "phone":
default: }
} // CalcomConfirmation renders the success panel after a booking is created. // Returned as the body of POST /book; also emits OOB clears that wipe the // date grid + slots + form so the visitor is presented with only the // confirmation (preventing accidental double-submits) and an OOB step-indicator // update marking all three steps complete. templ CalcomConfirmation(result BookingResult, blockID string) {

Booking Confirmed!

Your appointment has been scheduled for { result.FormattedDateTime }.

A confirmation email has been sent to { result.Email }.

if result.MeetingLink != "" { Join meeting } // Machine-readable booking identity for the "manage your booking" feature. // Hidden from view; the script below reads it and persists the booking to // this visitor's localStorage so they can cancel / redo it on a later // visit. templ auto-escapes every attribute value. // Persist {uid, when, email, savedAt} under a STABLE key derived from the // widget root's data-username + data-event-type (NOT the per-render // blockId). HTMX evaluates this script when it swaps the confirmation in. // Skips when uid is empty — the honeypot path renders a fake confirmation // with no UID, which must never poison the saved-booking panel.
// OOB: mark all three steps complete and clear the upstream regions so // the visitor cannot re-submit the form (prevents the double-booking // footgun the adversarial review caught). @calcomStepIndicatorOOB(blockID, 4) @calcomClearRegion(fmt.Sprintf("calcom-date-grid-%s", blockID), "calcom-date-picker") @calcomClearRegion(fmt.Sprintf("calcom-slots-%s", blockID), "calcom-time-slots mt-6") @calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form") } // calcomSavedBooking renders the normally-hidden "manage your booking" panel. // The widget script un-hides it (and hides the date grid) on DOMContentLoaded // when this visitor's localStorage holds a booking for this username/event, // filling in the date/time + email from storage. It offers two actions: // // - Cancel: an HTMX form POSTing /cancel with a hidden `uid` the script // populates from localStorage; on a successful swap the script clears the // storage key (htmx:afterRequest listener in the widget script). // - Reschedule (redo): cancels the existing booking then reveals the grid to // rebook — wired in the widget script to trigger the same Cancel POST, so // the visitor can't end up double-booked. // // Cancellation is authorized by the Cal.com booking UID alone (the same // capability model as Cal.com's emailed cancel link); the server rate-limits // the POST by IP as the abuse guard. templ calcomSavedBooking(blockID string) { } // CalcomCancelled is the body returned by POST /cancel on success. It swaps a // "your booking was cancelled" message into #calcom-confirm-{id} and rewinds // the step indicator to step 1. The date grid is restored client-side: when // cancel fires from the saved panel the grid is only CSS-hidden (never removed), // so the widget script's htmx:afterRequest listener re-shows it (showGrid) — // avoiding a server-side grid re-render that would need the booking config the // cancel POST doesn't carry. That same listener also clears the saved booking // from localStorage and hides the saved panel. templ CalcomCancelled(blockID string) {

Booking cancelled

Your booking has been cancelled. You can book another time below whenever you're ready.

// OOB: rewind the step indicator to step 1 (the grid is re-shown client-side). @calcomStepIndicatorOOB(blockID, 1) } // CalcomReset is the body returned by GET /reset — empty slots region with // an OOB clear of the form (so the user pressing "Change date" loses any // stale form rendered from an earlier slot click) and an OOB rewind of the // step indicator to step 1. templ CalcomReset(blockID string) { // Main response body — empty, replaces #calcom-slots-{id}. // OOB: clear the form region and rewind the step indicator. @calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form") @calcomStepIndicatorOOB(blockID, 1) } // CalcomError renders an error message. templ CalcomError(message string, blockID string, canRetry bool) { }