feat(booking): stepped public booking flow with service/staff selection

The public booking page showed a capped flat preview of slots with no way
to pick a service, day, or staff member. Replaces it with a stepped flow:
pick service, optional staff preference, pick a day, pick a time.

Backend: availability accepts serviceId/staffId/date filters (default
response unchanged for the embed widget), a new public catalog endpoint
exposes bookable services and active staff, and slots carry staffName.
Slot generation and booking creation now honor tenant-wide and per-staff
blocked days; appointment requests must fit inside a matching
availability rule and reference tenant-owned, active staff and locations.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 10:18:03 +02:00
1 parent 09f780e7a6
commit d9aa29dbcb
9 files changed
+940 -118

No files matched your search

+77
View File
@@ -263,6 +263,27 @@ paths:
required: true
schema:
type: string
- in: query
name: serviceId
required: false
schema:
type: string
format: uuid
description: Restrict appointment slots to a single service
- in: query
name: staffId
required: false
schema:
type: string
format: uuid
description: Restrict appointment slots to a single staff member
- in: query
name: date
required: false
schema:
type: string
format: date
description: Return all slots for a single tenant-local day (YYYY-MM-DD) instead of the capped preview
responses:
"200":
description: Availability for a tenant booking page
@@ -270,6 +291,23 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/PublicAvailabilityResponse"
/v1/public/tenants/{tenantSlug}/catalog:
get:
tags: [Public Booking]
operationId: getPublicCatalog
parameters:
- in: path
name: tenantSlug
required: true
schema:
type: string
responses:
"200":
description: Bookable services and active staff for a tenant booking page
content:
application/json:
schema:
$ref: "#/components/schemas/PublicCatalogResponse"
/v1/public/bookings:
post:
tags: [Public Booking]
@@ -1270,6 +1308,9 @@ components:
type: string
format: uuid
nullable: true
staffName:
type: string
nullable: true
locationId:
type: string
format: uuid
@@ -1303,6 +1344,42 @@ components:
type: array
items:
$ref: "#/components/schemas/TimeSlot"
PublicCatalogResponse:
type: object
required: [tenantSlug, services, staff]
properties:
tenantSlug:
type: string
services:
type: array
items:
$ref: "#/components/schemas/PublicService"
staff:
type: array
items:
$ref: "#/components/schemas/PublicStaff"
PublicService:
type: object
required: [id, name, durationMinutes, priceCents]
properties:
id:
type: string
format: uuid
name:
type: string
durationMinutes:
type: integer
priceCents:
type: integer
PublicStaff:
type: object
required: [id, displayName]
properties:
id:
type: string
format: uuid
displayName:
type: string
CreateBookingRequest:
type: object
required: [tenantSlug, bookingMode, customerName, customerEmail, startsAt, endsAt]