feat(bookings): customer self-service rebooking with real slot picker

CustomerBookingView now carries tenantSlug/serviceId/staffId/
classSessionId so the manage page can query live availability; class
bookings resolve their session title as the display label. Reschedule
rejects waitlisted/completed bookings and session-bound class bookings
(400), collapsing a dead branch in the appointment path.

The manage page reschedule dialog replaces free-form date/time inputs
with a date → availability-slot picker filtered to appointment mode,
so customers can only move to a genuinely free slot with the correct
duration; 409 surfaces "slot just taken". Actions are status-gated:
reschedule for confirmed/pending appointments, cancel for live
bookings, explanatory note for class bookings.

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 14:58:49 +02:00
1 parent 1cd5b1ba91
commit 796c06ae66
7 files changed
+311 -87

No files matched your search

+44 -6
View File
@@ -1120,6 +1120,32 @@ export interface components {
notes?: string;
phone?: string;
};
CustomerBookingView: {
businessName?: string;
/**
* Format: uuid
* @description Present for session-bound bookings, which cannot be rescheduled to arbitrary times
*/
classSessionId?: string;
customerEmail?: string;
customerName?: string;
/** Format: date-time */
endsAt?: string;
location?: string;
notes?: string;
reference?: string;
service?: string;
/** Format: uuid */
serviceId?: string;
/** Format: uuid */
staffId?: string;
/** Format: date-time */
startsAt?: string;
status?: string;
tenantSlug?: string;
/** @description 1-based queue spot for waitlisted class bookings; absent otherwise */
waitlistPosition?: number;
};
DashboardBookingRequest: {
/** Format: email */
customerEmail: string;
@@ -3186,7 +3212,9 @@ export interface operations {
headers: {
[name: string]: unknown;
};
content?: never;
content: {
"application/json": components["schemas"]["CustomerBookingView"];
};
};
/** @description Invalid manage token */
403: {
@@ -3239,7 +3267,10 @@ export interface operations {
};
rescheduleBooking: {
parameters: {
query?: never;
query: {
/** @description Manage token issued at booking time */
token: string;
};
header?: never;
path: {
reference: string;
@@ -3250,10 +3281,10 @@ export interface operations {
content: {
"application/json": {
/** Format: date-time */
endsAt?: string;
newEndsAt: string;
/** Format: date-time */
startsAt: string;
token: string;
newStartsAt: string;
reason?: string;
};
};
};
@@ -3265,6 +3296,13 @@ export interface operations {
};
content?: never;
};
/** @description Invalid new window, non-reschedulable status, or class booking */
400: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Invalid manage token */
403: {
headers: {
@@ -3272,7 +3310,7 @@ export interface operations {
};
content?: never;
};
/** @description New slot conflicts */
/** @description New slot conflicts or booking already cancelled */
409: {
headers: {
[name: string]: unknown;