BookingConfirmation
Displays a summary of the booking details (date, time, service, customer info) and a confirm button.
Install
Section titled “Install”npx thebookingkit add booking-confirmationimport { BookingConfirmation } from "./components/booking-confirmation";
<BookingConfirmation eventTitle="30-Minute Consultation" providerName="Dr. Smith" date={selectedDate} slot={selectedSlot} customerName="Alice Johnson" customerEmail="alice@example.com" onConfirm={handleConfirm} onBack={handleBack}/>interface BookingConfirmationProps { /** Event type title */ eventTitle: string; /** Event type duration in minutes */ duration: number; /** Provider's display name */ providerName: string; /** Location description */ location?: string; /** The selected time slot */ slot: Slot; /** Customer's timezone */ timezone: string; /** Customer's form data (name, email, responses) */ formData: BookingFormData; /** Callback to create the booking */ onConfirm: () => Promise<{ bookingId: string } | void>; /** Callback to go back and change selection */ onBack?: () => void; /** Additional CSS class name */ className?: string; /** Inline styles */ style?: React.CSSProperties;}States
Section titled “States”The component handles four states:
- Idle: Display confirmation screen with Back and Confirm buttons
- Submitting: Show loading state with disabled buttons
- Success: Display confirmation message with booking ID
- Conflict: Show error that slot is no longer available
- Error: Display error message with retry button
Source
Section titled “Source”import React, { useState } from "react";import type { Slot } from "@thebookingkit/core";import type { BookingFormData } from "./booking-questions.js";import { cn } from "../utils/cn.js";
/** Props for the BookingConfirmation component */export interface BookingConfirmationProps { /** Event type title */ eventTitle: string; /** Event type duration in minutes */ duration: number; /** Provider's display name */ providerName: string; /** Location description */ location?: string; /** The selected time slot */ slot: Slot; /** Customer's timezone */ timezone: string; /** Customer's form data (name, email, responses) */ formData: BookingFormData; /** Callback to create the booking */ onConfirm: () => Promise<{ bookingId: string } | void>; /** Callback to go back and change selection */ onBack?: () => void; /** Additional CSS class name */ className?: string; /** Inline styles */ style?: React.CSSProperties;}
/** Booking result state */type BookingState = | { status: "idle" } | { status: "submitting" } | { status: "success"; bookingId: string } | { status: "conflict" } | { status: "error"; message: string };
/** * Booking confirmation summary view. * * Displays all booking details for review before the customer confirms. * Handles success, conflict, and error states. * * @example * ```tsx * <BookingConfirmation * eventTitle="30-Minute Consultation" * duration={30} * providerName="Alice Johnson" * slot={selectedSlot} * timezone="America/New_York" * formData={customerData} * onConfirm={handleConfirm} * /> * ``` */export function BookingConfirmation({ eventTitle, duration, providerName, location, slot, timezone, formData, onConfirm, onBack, className, style,}: BookingConfirmationProps) { const [state, setState] = useState<BookingState>({ status: "idle" });
const handleConfirm = async () => { setState({ status: "submitting" }); try { const result = await onConfirm(); setState({ status: "success", bookingId: result?.bookingId ?? "confirmed", }); } catch (error) { if ( error instanceof Error && (error.message.includes("conflict") || error.message.includes("no longer available") || error.message.includes("BOOKING_CONFLICT")) ) { setState({ status: "conflict" }); } else { setState({ status: "error", message: error instanceof Error ? error.message : "An unexpected error occurred", }); } } };
if (state.status === "success") { return ( <div className={cn("tbk-booking-confirmation tbk-success", className)} style={style}> <div className="tbk-success-icon" aria-hidden="true"> ✓ </div> <h2>Booking Confirmed!</h2> <p>Your booking has been confirmed.</p> <dl className="tbk-confirmation-details"> <dt>Booking ID</dt> <dd>{state.bookingId}</dd> <dt>Event</dt> <dd>{eventTitle}</dd> <dt>Date & Time</dt> <dd>{formatSlotDisplay(slot, timezone)}</dd> <dt>Duration</dt> <dd>{duration} minutes</dd> </dl> </div> ); }
return ( <div className={cn("tbk-booking-confirmation", className)} style={style}> <h2>Confirm Your Booking</h2>
<dl className="tbk-confirmation-details"> <dt>Service</dt> <dd>{eventTitle}</dd> <dt>Provider</dt> <dd>{providerName}</dd> <dt>Date & Time</dt> <dd>{formatSlotDisplay(slot, timezone)}</dd> <dt>Duration</dt> <dd>{duration} minutes</dd> {location && ( <> <dt>Location</dt> <dd>{location}</dd> </> )} <dt>Name</dt> <dd>{formData.name}</dd> <dt>Email</dt> <dd>{formData.email}</dd> {formData.phone && ( <> <dt>Phone</dt> <dd>{formData.phone}</dd> </> )} </dl>
{Object.keys(formData.responses).length > 0 && ( <div className="tbk-responses-review"> <h3>Your Responses</h3> <dl> {Object.entries(formData.responses).map(([key, value]) => ( <React.Fragment key={key}> <dt>{key}</dt> <dd>{value}</dd> </React.Fragment> ))} </dl> </div> )}
{state.status === "conflict" && ( <div className="tbk-alert tbk-alert-warning" role="alert"> <p>This time slot is no longer available. Please select a different time.</p> {onBack && ( <button type="button" className="tbk-button-secondary" onClick={onBack} > Choose Another Time </button> )} </div> )}
{state.status === "error" && ( <div className="tbk-alert tbk-alert-error" role="alert"> <p>{state.message}</p> <button type="button" className="tbk-button-secondary" onClick={handleConfirm} > Try Again </button> </div> )}
<div className="tbk-confirmation-actions"> {onBack && state.status !== "conflict" && ( <button type="button" className="tbk-button-secondary" onClick={onBack} disabled={state.status === "submitting"} > Back </button> )} {state.status !== "conflict" && ( <button type="button" className="tbk-button-primary" onClick={handleConfirm} disabled={state.status === "submitting"} > {state.status === "submitting" ? "Confirming..." : "Confirm Booking"} </button> )} </div> </div> );}
function formatSlotDisplay(slot: Slot, timezone: string): string { const date = new Date(slot.startTime); const dateStr = date.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: timezone, });
// Extract time from localStart const timePart = slot.localStart.split("T")[1]; const [h, m] = (timePart ?? "00:00").split(":").map(Number); const period = h >= 12 ? "PM" : "AM"; const h12 = h % 12 || 12; const timeStr = `${h12}:${String(m).padStart(2, "0")} ${period}`;
return `${dateStr} at ${timeStr} (${timezone})`;}