Bookings Hub
The bookings hub is the host-facing management surface at /p/[slug]/bookings: a kanban board and a month calendar over every booking for the podcast, plus the Guest Network invites that have not converted yet. It sits ON TOP of the core booking flow; it introduces no new state machine of its own.
How the layers fit
- State transitions are RPC-atomic. Every mutating route loads context, calls a SECURITY DEFINER RPC for the DB transition, then runs side effects (Google Calendar, emails, automation events, AI research) outside the transaction, each in its own try/catch. Side-effect failures never fail the response. See the route header of
src/api/routes/bookings/index.ts. - The hub reads two endpoints.
GET /api/bookings?podcastId=(bookings with joined link/session/episode/attendees) andGET /api/guest-network/invitations/podcast?podcastId=(the Sent lane). Fetch helpers live insrc/lib/components/bookings/booking-api.tsand follow the client-auth rule (browser Supabase session token, Bearer header,{ ok, ... }results that never throw). - Board position is never stored. Lanes are derived from
booking_status, the episode's publish state, and the clock. See Hub Architecture.
Code map
| Piece | Path |
|---|---|
| Page (tabs, filters, action dispatch) | src/routes/(app)/p/[slug]/bookings/+page.svelte |
| Server load (role gating) | src/routes/(app)/p/[slug]/bookings/+page.server.ts |
| Lane derivation + drag legality (pure) | src/lib/components/bookings/board-utils.ts |
| Board / lanes / cards | BookingsBoard.svelte, BoardLane.svelte, BookingCard.svelte, InviteCard.svelte |
| Calendar view | BookingsCalendarView.svelte |
| Detail sheet | BookingDetailSheet.svelte |
| Confirm/decline/cancel/delete dialogs | BookingActionDialogs.svelte |
| Reschedule dialog | RescheduleDialog.svelte |
| Status presentation (one palette everywhere) | src/lib/components/bookings/status-meta.ts |
Row shapes (BoardBooking, PodcastInvite, BookingAction) | src/lib/components/bookings/types.ts |
| Bookings API (mutations + confirm side effects) | src/api/routes/bookings/index.ts |
| Booking-sessions API (calendar recovery) | src/api/routes/booking-sessions/index.ts |
Role gating
The server load (+page.server.ts) resolves the podcast via requirePodcastAccess and computes hasAdminAccess = role in (owner, admin). Booking mutations (confirm, decline, cancel, reschedule, outcomes, delete) are admin-level; members get a read-only board and calendar. Independently, isMutable derives from the podcast lifecycle status: a paused or pending-deletion podcast renders the whole hub read-only. The API enforces the same boundary server-side (requirePodcastRoleByResolver('admin', ...) on every mutation; member on reads).
Pages in this section
- Hub Architecture: lane derivation, drag semantics, calendar view, detail sheet, outcomes, reschedule, invite conversion.
- Booking Sessions API & Confirm Path: the calendar-recovery endpoints and everything
POST /api/bookings/:id/confirmactually does.
Related
- Booking Flow guide (the core public flow)
- Bookings API
- Booking Links API
- Availability API
- Guest Research (AI answers captured on the form, research enqueued at confirm)