{
  "name": "booking-page",
  "title": "BookingPage",
  "description": "Multi-step booking flow: service, staff, resource, date/slot, confirmation.",
  "type": "component",
  "registryDependencies": [
    "date-slot-picker",
    "staff-picker",
    "resource-picker",
    "price",
    "cn"
  ],
  "files": [
    {
      "path": "booking-page.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport type { Product } from \"@cimplify/sdk\";\nimport type { CurrencyCode } from \"@cimplify/sdk\";\nimport type { Service, Staff, AvailableSlot, CustomerBooking } from \"@cimplify/sdk\";\nimport type { Resource } from \"./resource-picker\";\nimport { useCart } from \"@cimplify/sdk/react\";\nimport { useCimplifyClient } from \"@cimplify/sdk/react\";\nimport { DateSlotPicker } from \"@cimplify/sdk/react\";\nimport { StaffPicker } from \"@cimplify/sdk/react\";\nimport { ResourcePicker } from \"./resource-picker\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\nimport { formatDuration } from \"./utils/format-duration\";\nimport { parsePrice } from \"@cimplify/sdk\";\n\nexport interface BookingPageClassNames {\n  root?: string;\n  header?: string;\n  title?: string;\n  serviceInfo?: string;\n  step?: string;\n  stepTitle?: string;\n  summary?: string;\n  summaryRow?: string;\n  confirmButton?: string;\n  backButton?: string;\n  error?: string;\n  rescheduleInfo?: string;\n  depositInfo?: string;\n  cancellationPolicy?: string;\n  resourceStep?: string;\n}\n\nexport interface BookingPageProps {\n  /** The service being booked. */\n  service: Service;\n  /** The underlying product (for deposit/cancellation fields). Falls back to service fields. */\n  product?: Product;\n  /** Optional staff list for staff selection step. */\n  staff?: Staff[];\n  /** Optional resources for resource selection step (rooms, equipment, etc.). */\n  resources?: Resource[];\n  /** Number of participants. */\n  participantCount?: number;\n  /** Page title. */\n  title?: string;\n  /** Called after successfully adding to cart. */\n  onBooked?: (slot: AvailableSlot, staffId: string | null) => void;\n  /** Called when user wants to go back. */\n  onBack?: () => void;\n  /** Existing booking for reschedule mode. */\n  existingBooking?: CustomerBooking;\n  /** Called after a successful reschedule. */\n  onRescheduled?: (booking: CustomerBooking, newSlot: AvailableSlot) => void;\n  /** Show price on slots. Default: true. */\n  showPrice?: boolean;\n  className?: string;\n  classNames?: BookingPageClassNames;\n}\n\nconst STEP = {\n  SELECT_SLOT: \"select-slot\",\n  SELECT_RESOURCE: \"select-resource\",\n  SELECT_STAFF: \"select-staff\",\n  CONFIRM: \"confirm\",\n} as const;\n\ntype BookingStep = (typeof STEP)[keyof typeof STEP];\n\nexport function BookingPage({\n  service,\n  product,\n  staff,\n  resources,\n  participantCount,\n  title,\n  onBooked,\n  onBack,\n  existingBooking,\n  onRescheduled,\n  showPrice = true,\n  className,\n  classNames,\n}: BookingPageProps): React.ReactElement {\n  const { addItem } = useCart();\n  const { client } = useCimplifyClient();\n\n  const currency = service.currency as CurrencyCode | undefined;\n\n  const isReschedule = existingBooking !== undefined;\n\n  const [step, setStep] = useState<BookingStep>(STEP.SELECT_SLOT);\n  const [selectedSlot, setSelectedSlot] = useState<AvailableSlot | null>(null);\n  const [selectedDate, setSelectedDate] = useState<string | null>(null);\n  const [selectedStaffId, setSelectedStaffId] = useState<string | null>(null);\n  const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);\n  const [bookingUnits, setBookingUnits] = useState(1);\n  const [isSubmitting, setIsSubmitting] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  const hasResourceStep = resources && resources.length > 0;\n  const hasStaffStep = staff && staff.length > 0;\n\n  // Deposit info — prefer product fields, then fall back to service metadata\n  const depositType = product?.deposit_type;\n  const depositAmount = product?.deposit_amount;\n  const hasDeposit = depositType !== undefined && depositType !== \"none\" && depositAmount !== undefined && parsePrice(depositAmount) !== 0;\n\n  // Cancellation policy\n  const cancellationMinutes = product?.cancellation_window_minutes;\n  const noShowFee = product?.no_show_fee;\n\n  const handleSlotSelect = useCallback(\n    (slot: AvailableSlot, date: string) => {\n      setSelectedSlot(slot);\n      setSelectedDate(date);\n      setError(null);\n      if (hasResourceStep) {\n        setStep(STEP.SELECT_RESOURCE);\n      } else if (hasStaffStep) {\n        setStep(STEP.SELECT_STAFF);\n      } else {\n        setStep(STEP.CONFIRM);\n      }\n    },\n    [hasResourceStep, hasStaffStep],\n  );\n\n  const handleResourceSelect = useCallback(\n    (resourceId: string | null) => {\n      setSelectedResourceId(resourceId);\n      if (hasStaffStep) {\n        setStep(STEP.SELECT_STAFF);\n      } else {\n        setStep(STEP.CONFIRM);\n      }\n    },\n    [hasStaffStep],\n  );\n\n  const handleStaffSelect = useCallback((staffId: string | null) => {\n    setSelectedStaffId(staffId);\n    setStep(STEP.CONFIRM);\n  }, []);\n\n  const handleBack = useCallback(() => {\n    setError(null);\n    if (step === STEP.CONFIRM && hasStaffStep) {\n      setStep(STEP.SELECT_STAFF);\n    } else if (step === STEP.CONFIRM && hasResourceStep) {\n      setStep(STEP.SELECT_RESOURCE);\n    } else if (step === STEP.SELECT_STAFF && hasResourceStep) {\n      setStep(STEP.SELECT_RESOURCE);\n    } else if (step === STEP.CONFIRM || step === STEP.SELECT_STAFF || step === STEP.SELECT_RESOURCE) {\n      setStep(STEP.SELECT_SLOT);\n    } else {\n      onBack?.();\n    }\n  }, [step, hasStaffStep, hasResourceStep, onBack]);\n\n  const handleConfirm = useCallback(async () => {\n    if (!selectedSlot || !selectedDate) return;\n\n    setIsSubmitting(true);\n    setError(null);\n\n    try {\n      if (isReschedule && existingBooking) {\n        // Reschedule mode — call the scheduling API instead of adding to cart\n        const serviceItem = existingBooking.service_items[0];\n        const result = await client.scheduling.rescheduleBooking({\n          order_id: existingBooking.order_id,\n          line_item_id: serviceItem?.service_id ?? existingBooking.order_id,\n          new_start_time: selectedSlot.start_time,\n          new_end_time: selectedSlot.end_time,\n          new_staff_id: selectedStaffId || undefined,\n          reschedule_type: \"customer\",\n        });\n\n        if (!result.ok) {\n          throw result.error;\n        }\n\n        onRescheduled?.(existingBooking, selectedSlot);\n      } else {\n        // Normal booking mode — add to cart\n        const serviceProduct = {\n          id: service.product_id || service.id,\n          business_id: service.business_id || \"\",\n          category_id: service.category_id || undefined,\n          name: service.name,\n          slug: service.id,\n          description: service.description || undefined,\n          image_url: service.image_url || undefined,\n          default_price: (service.price || \"0\") as Product[\"default_price\"],\n          type: \"service\" as const,\n          inventory_type: \"none\" as const,\n          variant_strategy: \"fetch_all\" as const,\n          is_active: service.is_available,\n          created_at: new Date().toISOString(),\n          updated_at: new Date().toISOString(),\n        };\n\n        await addItem(serviceProduct, 1, {\n          scheduledStart: selectedSlot.start_time,\n          scheduledEnd: selectedSlot.end_time,\n          staffId: selectedStaffId || undefined,\n          resourceId: selectedResourceId || undefined,\n          units: bookingUnits > 1 ? bookingUnits : undefined,\n        });\n\n        onBooked?.(selectedSlot, selectedStaffId);\n      }\n    } catch (err) {\n      const fallbackMessage = isReschedule ? \"Failed to reschedule booking\" : \"Failed to add booking to cart\";\n      setError(err instanceof Error ? err.message : fallbackMessage);\n    } finally {\n      setIsSubmitting(false);\n    }\n  }, [selectedSlot, selectedDate, selectedStaffId, service, addItem, onBooked, isReschedule, existingBooking, client, onRescheduled]);\n\n  return (\n    <div data-cimplify-booking-page className={cn(className, classNames?.root)}>\n      <div data-cimplify-booking-page-header className={classNames?.header}>\n        {onBack && step === STEP.SELECT_SLOT && (\n          <button\n            type=\"button\"\n            onClick={onBack}\n            data-cimplify-booking-page-back\n            className={classNames?.backButton}\n          >\n            Back\n          </button>\n        )}\n        {step !== STEP.SELECT_SLOT && (\n          <button\n            type=\"button\"\n            onClick={handleBack}\n            data-cimplify-booking-page-back\n            className={classNames?.backButton}\n          >\n            Back\n          </button>\n        )}\n        <h1 data-cimplify-booking-page-title className={classNames?.title}>\n          {title || (isReschedule ? \"Reschedule Booking\" : `Book ${service.name}`)}\n        </h1>\n      </div>\n\n      {isReschedule && existingBooking && (\n        <div data-cimplify-booking-reschedule-info className={classNames?.rescheduleInfo}>\n          <span>Rescheduling booking from {new Date(existingBooking.service_items[0]?.scheduled_start ?? existingBooking.created_at).toLocaleDateString()}</span>\n        </div>\n      )}\n\n      <div data-cimplify-booking-service-info className={classNames?.serviceInfo}>\n        <span data-cimplify-booking-service-name>{service.name}</span>\n        <span data-cimplify-booking-service-duration>{formatDuration(service.duration_minutes, service.duration_unit)}</span>\n        {service.price && (\n          <span data-cimplify-booking-service-price>\n            <Price amount={service.price} currency={currency} />\n          </span>\n        )}\n        {hasDeposit && (\n          <span data-cimplify-booking-deposit-info className={classNames?.depositInfo}>\n            Deposit: {depositType === \"percentage\" ? `${parsePrice(depositAmount!)}%` : <Price amount={depositAmount!} currency={currency} />}\n          </span>\n        )}\n        {cancellationMinutes !== undefined && cancellationMinutes > 0 && (\n          <span data-cimplify-booking-cancellation-policy className={classNames?.cancellationPolicy}>\n            Free cancellation up to {cancellationMinutes >= 60 ? `${Math.floor(cancellationMinutes / 60)} hour${Math.floor(cancellationMinutes / 60) !== 1 ? \"s\" : \"\"}` : `${cancellationMinutes} minute${cancellationMinutes !== 1 ? \"s\" : \"\"}`} before\n          </span>\n        )}\n        {noShowFee !== undefined && parsePrice(noShowFee) !== 0 && (\n          <span data-cimplify-booking-no-show-fee className={classNames?.cancellationPolicy}>\n            No-show fee: <Price amount={noShowFee} currency={currency} />\n          </span>\n        )}\n      </div>\n\n      {error && (\n        <div data-cimplify-booking-error className={classNames?.error}>\n          {error}\n        </div>\n      )}\n\n      {step === STEP.SELECT_SLOT && (\n        <div data-cimplify-booking-step=\"select-slot\" className={classNames?.step}>\n          <h2 data-cimplify-booking-step-title className={classNames?.stepTitle}>\n            Select a date &amp; time\n          </h2>\n          <DateSlotPicker\n            serviceId={service.id}\n            participantCount={participantCount}\n            selectedSlot={selectedSlot}\n            onSlotSelect={handleSlotSelect}\n            showPrice={showPrice}\n            currency={currency}\n          />\n        </div>\n      )}\n\n      {step === STEP.SELECT_RESOURCE && resources && (\n        <div data-cimplify-booking-step=\"select-resource\" className={cn(classNames?.step, classNames?.resourceStep)}>\n          <h2 data-cimplify-booking-step-title className={classNames?.stepTitle}>\n            Choose a room\n          </h2>\n          <ResourcePicker\n            resources={resources}\n            selectedResourceId={selectedResourceId}\n            onResourceSelect={handleResourceSelect}\n          />\n        </div>\n      )}\n\n      {step === STEP.SELECT_STAFF && staff && (\n        <div data-cimplify-booking-step=\"select-staff\" className={classNames?.step}>\n          <h2 data-cimplify-booking-step-title className={classNames?.stepTitle}>\n            Choose a provider\n          </h2>\n          <StaffPicker\n            staff={staff}\n            selectedStaffId={selectedStaffId}\n            onStaffSelect={handleStaffSelect}\n          />\n        </div>\n      )}\n\n      {step === STEP.CONFIRM && selectedSlot && (\n        <div data-cimplify-booking-step=\"confirm\" className={classNames?.step}>\n          <h2 data-cimplify-booking-step-title className={classNames?.stepTitle}>\n            Confirm your booking\n          </h2>\n          <div data-cimplify-booking-units className={classNames?.summaryRow}>\n            <span>Units</span>\n            <div data-cimplify-booking-units-stepper>\n              <button\n                type=\"button\"\n                data-cimplify-booking-units-decrement\n                onClick={() => setBookingUnits((current) => Math.max(1, current - 1))}\n                disabled={bookingUnits <= 1}\n              >\n                −\n              </button>\n              <span data-cimplify-booking-units-value>{bookingUnits}</span>\n              <button\n                type=\"button\"\n                data-cimplify-booking-units-increment\n                onClick={() => setBookingUnits((current) => current + 1)}\n              >\n                +\n              </button>\n            </div>\n          </div>\n          <div data-cimplify-booking-summary className={classNames?.summary}>\n            <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n              <span>Service</span>\n              <span>{service.name}</span>\n            </div>\n            <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n              <span>Date</span>\n              <span>{selectedDate}</span>\n            </div>\n            <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n              <span>Time</span>\n              <span>\n                {new Date(selectedSlot.start_time).toLocaleTimeString(undefined, {\n                  hour: \"numeric\",\n                  minute: \"2-digit\",\n                })}\n              </span>\n            </div>\n            <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n              <span>Duration</span>\n              <span>{formatDuration(service.duration_minutes, service.duration_unit)}</span>\n            </div>\n            {selectedResourceId && resources && (\n              <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n                <span>Room</span>\n                <span>{resources.find((r) => r.id === selectedResourceId)?.name ?? \"Selected\"}</span>\n              </div>\n            )}\n            {selectedStaffId && staff && (\n              <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n                <span>Provider</span>\n                <span>{staff.find((s) => s.id === selectedStaffId)?.name ?? \"Selected\"}</span>\n              </div>\n            )}\n            {(selectedSlot.price || service.price) && (\n              <div data-cimplify-booking-summary-row className={classNames?.summaryRow}>\n                <span>Price</span>\n                <span>\n                  <Price amount={selectedSlot.price || service.price!} currency={currency} />\n                </span>\n              </div>\n            )}\n            {hasDeposit && (\n              <div data-cimplify-booking-summary-row className={cn(classNames?.summaryRow, classNames?.depositInfo)}>\n                <span>Deposit</span>\n                <span>\n                  {depositType === \"percentage\" ? `${parsePrice(depositAmount!)}%` : <Price amount={depositAmount!} currency={currency} />}\n                </span>\n              </div>\n            )}\n          </div>\n          <button\n            type=\"button\"\n            onClick={handleConfirm}\n            disabled={isSubmitting}\n            data-cimplify-booking-confirm\n            className={classNames?.confirmButton}\n          >\n            {isSubmitting\n              ? (isReschedule ? \"Rescheduling…\" : \"Adding to cart…\")\n              : (isReschedule ? \"Reschedule\" : \"Confirm Booking\")}\n          </button>\n        </div>\n      )}\n    </div>\n  );\n}\n"
    }
  ]
}
