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

+21 -1
View File
@@ -323,7 +323,27 @@ func NewServer(cfg config.Config, pools *db.Pools) (*Server, error) {
}
server.router.GET("/v1/public/tenants/:tenantSlug/availability", func(c *gin.Context) {
response, err := bookingService.Availability(c.Request.Context(), c.Param("tenantSlug"))
response, err := bookingService.Availability(c.Request.Context(), c.Param("tenantSlug"), bookings.AvailabilityFilter{
ServiceID: c.Query("serviceId"),
StaffID: c.Query("staffId"),
Date: c.Query("date"),
})
if err != nil {
status := http.StatusInternalServerError
switch {
case errors.Is(err, bookings.ErrTenantNotFound):
status = http.StatusNotFound
case errors.Is(err, bookings.ErrInvalidBooking):
status = http.StatusBadRequest
}
c.JSON(status, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, response)
})
server.router.GET("/v1/public/tenants/:tenantSlug/catalog", func(c *gin.Context) {
response, err := bookingService.Catalog(c.Request.Context(), c.Param("tenantSlug"))
if err != nil {
status := http.StatusInternalServerError
if errors.Is(err, bookings.ErrTenantNotFound) {