Skip to content

Commit 331f4ee

Browse files
refactor: simplify booking to request-based flow
- Remove BookingResponse, downloadCalendar from useBookAppointment - book() now returns boolean instead of BookingResponse - Handle API response format change (events wrapper) - Fix date parsing to use local time instead of UTC - Update confirmation UI to reflect request semantics Made-with: Cursor
1 parent a3f256f commit 331f4ee

5 files changed

Lines changed: 38 additions & 92 deletions

File tree

src/components/examples/CustomBookingExample.tsx

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -968,11 +968,10 @@ function BookingForm({ slot, showLocationSelector, selectedLocation, onLocationC
968968

969969
type ConfirmationProps = {
970970
slot: EnrichedTimeSlot
971-
onDownloadCalendar: () => void
972971
onReset: () => void
973972
}
974973

975-
function Confirmation({ slot, onDownloadCalendar, onReset }: ConfirmationProps) {
974+
function Confirmation({ slot, onReset }: ConfirmationProps) {
976975
const formatDateLong = (dateStr: string) => {
977976
return new Date(dateStr).toLocaleDateString('de-DE', {
978977
weekday: 'long',
@@ -1009,7 +1008,7 @@ function Confirmation({ slot, onDownloadCalendar, onReset }: ConfirmationProps)
10091008
margin: '0 0 12px 0'
10101009
}}
10111010
>
1012-
Termin gebucht!
1011+
Terminanfrage eingegangen!
10131012
</h2>
10141013

10151014
<p
@@ -1021,32 +1020,13 @@ function Confirmation({ slot, onDownloadCalendar, onReset }: ConfirmationProps)
10211020
lineHeight: 1.6
10221021
}}
10231022
>
1024-
Ihr Termin <strong style={{ color: 'var(--booking-text)' }}>"{slot.title}"</strong> am{' '}
1023+
Ihre Anfrage für <strong style={{ color: 'var(--booking-text)' }}>"{slot.title}"</strong> am{' '}
10251024
<strong style={{ color: 'var(--booking-text)' }}>{formatDateLong(slot.start)}</strong> um{' '}
1026-
<strong style={{ color: 'var(--booking-text)' }}>{slot.timeString} Uhr</strong> wurde erfolgreich gebucht.
1025+
<strong style={{ color: 'var(--booking-text)' }}>{slot.timeString} Uhr</strong> ist eingegangen. Bitte bestätigen Sie den Termin über den Link in der E-Mail, die
1026+
wir Ihnen gerade gesendet haben.
10271027
</p>
10281028

10291029
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
1030-
<button
1031-
type="button"
1032-
onClick={onDownloadCalendar}
1033-
style={{
1034-
padding: '12px 20px',
1035-
border: 'none',
1036-
borderRadius: 'var(--booking-radius-sm)',
1037-
background: 'var(--booking-accent)',
1038-
color: '#000',
1039-
fontSize: 14,
1040-
fontWeight: 600,
1041-
cursor: 'pointer',
1042-
fontFamily: 'inherit',
1043-
display: 'flex',
1044-
alignItems: 'center',
1045-
gap: 8
1046-
}}
1047-
>
1048-
📅 Zum Kalender hinzufügen
1049-
</button>
10501030
<button
10511031
type="button"
10521032
onClick={onReset}
@@ -1086,7 +1066,6 @@ function BookingInterface() {
10861066
selectSlot,
10871067
selectedLocation,
10881068
setLocation,
1089-
booking,
10901069
submitBooking,
10911070
reset,
10921071
isLoading,
@@ -1272,9 +1251,7 @@ function BookingInterface() {
12721251
/>
12731252
)}
12741253

1275-
{step === 'confirmation' && enrichedSelectedSlot && (
1276-
<Confirmation slot={enrichedSelectedSlot} onDownloadCalendar={booking.downloadCalendar} onReset={reset} />
1277-
)}
1254+
{step === 'confirmation' && enrichedSelectedSlot && <Confirmation slot={enrichedSelectedSlot} onReset={reset} />}
12781255
</div>
12791256
</div>
12801257
</div>

src/hooks/useAvailableSlots.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ export function useAvailableSlots(options: UseAvailableSlotsOptions = {}): UseAv
123123
throw new Error(`Failed to fetch slots: ${response.statusText}`)
124124
}
125125

126-
const data = (await response.json()) as TimeSlot[]
126+
const responseData = await response.json()
127+
// API returns { events: [], categories: [], bookingNote: null }
128+
const data = (Array.isArray(responseData) ? responseData : responseData.events) as TimeSlot[]
127129
setState({ data, isLoading: false, error: null })
128130
} catch (err) {
129131
const error = err instanceof Error ? err : new Error('Unknown error occurred')
@@ -165,7 +167,11 @@ export function useAvailableSlots(options: UseAvailableSlotsOptions = {}): UseAv
165167

166168
const availableDates = useMemo<Date[]>(() => {
167169
return Object.keys(slotsByDate)
168-
.map(dateStr => new Date(dateStr))
170+
.map(dateStr => {
171+
// Parse as local time, not UTC
172+
const [year, month, day] = dateStr.split('-').map(Number)
173+
return new Date(year, month - 1, day)
174+
})
169175
.sort((a, b) => a.getTime() - b.getTime())
170176
}, [slotsByDate])
171177

src/hooks/useBookAppointment.ts

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useCallback, useState } from 'react'
2-
import type { AppointmentLocation, BookingResponse, ClientInfo, TimeSlot } from '../api-types'
2+
import type { AppointmentLocation, ClientInfo, TimeSlot } from '../api-types'
33
import { useTebutoContext } from './TebutoProvider'
44

5-
type BookingState = {
6-
booking: BookingResponse | null
5+
type BookingRequestState = {
76
isLoading: boolean
87
error: Error | null
98
isSuccess: boolean
@@ -15,61 +14,49 @@ type BookAppointmentParams = {
1514
locationSelection?: AppointmentLocation
1615
}
1716

18-
type UseBookAppointmentReturn = BookingState & {
19-
/** Book the appointment */
20-
book: (params: BookAppointmentParams) => Promise<BookingResponse | null>
21-
/** Reset the booking state */
17+
type UseBookAppointmentReturn = BookingRequestState & {
18+
/** Submit booking request - returns true on success */
19+
book: (params: BookAppointmentParams) => Promise<boolean>
20+
/** Reset the state */
2221
reset: () => void
23-
/** Download the calendar file (.ics) */
24-
downloadCalendar: () => void
2522
}
2623

2724
/**
28-
* useBookAppointment - Complete the booking process
25+
* useBookAppointment - Submit a public booking request
2926
*
30-
* Submits the booking with client information and handles
31-
* the response including calendar download.
27+
* Submits a booking request with client information. For public bookings,
28+
* the client will receive a confirmation email to verify the appointment.
3229
*
3330
* @example
3431
* ```tsx
35-
* const { book, booking, isLoading, isSuccess, downloadCalendar, reset } = useBookAppointment()
32+
* const { book, isLoading, isSuccess, error, reset } = useBookAppointment()
3633
*
3734
* const handleSubmit = async (clientInfo: ClientInfo) => {
38-
* const result = await book({
35+
* const success = await book({
3936
* slot: claimedSlot,
4037
* client: clientInfo,
4138
* locationSelection: selectedLocation
4239
* })
4340
*
44-
* if (result) {
41+
* if (success) {
4542
* setStep('confirmation')
4643
* }
4744
* }
48-
*
49-
* if (isSuccess) {
50-
* return (
51-
* <SuccessMessage>
52-
* <button onClick={downloadCalendar}>Add to Calendar</button>
53-
* <button onClick={reset}>Book Another</button>
54-
* </SuccessMessage>
55-
* )
56-
* }
5745
* ```
5846
*/
5947
export function useBookAppointment(): UseBookAppointmentReturn {
6048
const { therapistUUID, buildUrl, fingerprint } = useTebutoContext()
61-
const [state, setState] = useState<BookingState>({
62-
booking: null,
49+
const [state, setState] = useState<BookingRequestState>({
6350
isLoading: false,
6451
error: null,
6552
isSuccess: false
6653
})
6754

6855
const book = useCallback(
69-
async (params: BookAppointmentParams): Promise<BookingResponse | null> => {
56+
async (params: BookAppointmentParams): Promise<boolean> => {
7057
const { slot, client, locationSelection } = params
7158

72-
setState(prev => ({ ...prev, isLoading: true, error: null }))
59+
setState({ isLoading: true, error: null, isSuccess: false })
7360

7461
try {
7562
const response = await fetch(buildUrl(`/events/${therapistUUID}/book`), {
@@ -89,52 +76,33 @@ export function useBookAppointment(): UseBookAppointmentReturn {
8976
throw new Error(`Booking failed: ${response.statusText}`)
9077
}
9178

92-
const booking = (await response.json()) as BookingResponse
93-
9479
setState({
95-
booking,
9680
isLoading: false,
9781
error: null,
9882
isSuccess: true
9983
})
10084

101-
return booking
85+
return true
10286
} catch (err) {
10387
const error = err instanceof Error ? err : new Error('Unknown error occurred')
104-
setState(prev => ({ ...prev, isLoading: false, error, isSuccess: false }))
105-
return null
88+
setState({ isLoading: false, error, isSuccess: false })
89+
return false
10690
}
10791
},
10892
[therapistUUID, buildUrl, fingerprint]
10993
)
11094

11195
const reset = useCallback(() => {
11296
setState({
113-
booking: null,
11497
isLoading: false,
11598
error: null,
11699
isSuccess: false
117100
})
118101
}, [])
119102

120-
const downloadCalendar = useCallback(() => {
121-
if (!state.booking?.ics) return
122-
123-
const blob = new Blob([state.booking.ics], { type: 'text/calendar;charset=utf-8' })
124-
const url = window.URL.createObjectURL(blob)
125-
const link = document.createElement('a')
126-
link.href = url
127-
link.setAttribute('download', 'appointment.ics')
128-
document.body.appendChild(link)
129-
link.click()
130-
document.body.removeChild(link)
131-
window.URL.revokeObjectURL(url)
132-
}, [state.booking])
133-
134103
return {
135104
...state,
136105
book,
137-
reset,
138-
downloadCalendar
106+
reset
139107
}
140108
}

src/hooks/useBookingFlow.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useCallback, useMemo, useState } from 'react'
2-
import type { AppointmentLocation, BookingResponse, ClientInfo, EnrichedTimeSlot, TimeSlot } from '../api-types'
2+
import type { AppointmentLocation, ClientInfo, EnrichedTimeSlot, TimeSlot } from '../api-types'
33
import { useAvailableSlots } from './useAvailableSlots'
44
import { useBookAppointment } from './useBookAppointment'
55
import { useClaimSlot } from './useClaimSlot'
@@ -10,8 +10,8 @@ type BookingStep = 'loading' | 'date-selection' | 'time-selection' | 'booking-fo
1010
type UseBookingFlowOptions = {
1111
/** Categories to filter by */
1212
categories?: number[]
13-
/** Callback when booking is complete */
14-
onBookingComplete?: (booking: BookingResponse) => void
13+
/** Callback when booking request is submitted successfully */
14+
onBookingComplete?: () => void
1515
/** Callback on any error */
1616
onError?: (error: Error) => void
1717
}
@@ -212,7 +212,7 @@ export function useBookingFlow(options: UseBookingFlowOptions = {}): UseBookingF
212212
return false
213213
}
214214

215-
onBookingComplete?.(result)
215+
onBookingComplete?.()
216216
setStep('confirmation')
217217
return true
218218
},

tsconfig.build.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
{
22
"extends": "./tsconfig.json",
3-
"exclude": [
4-
"src/**/*.test.ts",
5-
"src/**/*.test.tsx",
6-
"src/**/*.stories.ts",
7-
"src/**/*.stories.tsx"
8-
]
3+
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.stories.ts", "src/**/*.stories.tsx"]
94
}

0 commit comments

Comments
 (0)