ScheduleMeetingScreen, at the route /calendar.
Files
Entry points
The calendar is reachable only whenuser.calendarEnabled !== false on the signed-in user in useAuthStore.
- Classic home: the calendar button in
HomeHeropushes/calendar. - Themed home screens:
useHomeModel()exposescalendarEnabledandopenCalendar(), and each theme draws its own button.
Endpoints
getAvailableSlots always asks for a fixed window. from is now and to is now plus SLOT_RANGE_DAYS = 14 days. Both are sent as full ISO timestamps from Date.toISOString(), URL-encoded.
Hooks
Invalidating both keys after either mutation is what removes a booked slot from the list and returns a cancelled one to it. The app does no optimistic update.
Screen layout
ScheduleMeetingScreen uses Screen with scroll and pull to refresh, which refetches slots and meetings together. The header is the shared TrackHeader from the tracking feature with a back button.
While either query is loading, the screen shows skeletons. After that it has two sections.
Upcoming meetings
The list is filtered on the device:MeetingCard shows the type label (training, conversation, or a generic meeting label for OTHER), the day heading and the time range. title and location are on the type but the card does not render them. A cancel button appears when the meeting is in the future and its status is CONFIRMED.
Available slots
groupSlotsByDay(slots) groups slots under the trainee’s local calendar day:
- The group key is
localIsoDate(new Date(slot.startsAt)), so a slot is grouped by the device’s date, not the server’s. - Groups keep the order in which their first slot arrives. Slots are not re-sorted on the device.
- The heading is
formatDayHeading(startsAt): long weekday, day number and long month in the app language (getLanguageTag()).
SlotGroup renders one tonal Button per slot labelled with formatTime(startsAt) (two-digit hour and minute in the app language). The duration and slot type are not shown on the button.
With no slots at all, the section shows an empty-state line.
Booking flow
1
Pick a slot
Tapping a slot button calls
confirmBook(slot).2
Confirm
A native
Alert shows the day heading and start time with cancel and confirm actions.3
Book
On confirm, the screen stores
slotKey(slot) in pendingSlot, which disables that one button, then awaits book.mutateAsync(...) with coachId, slotType, startsAt and endsAt copied from the slot.4
Result
Success shows a toast with the booked string. Any error shows the booking-failed string.
pendingSlot is cleared in both cases. The invalidated queries then refetch, so the new meeting appears under upcoming meetings and the slot leaves the list.slotKey is startsAt joined with slotType. It is used as the React key and as the pending marker. Two coaches offering the same start time and slot type would share a key.
Cancel flow
The cancel button opens a destructive confirmationAlert. On confirm, the screen stores the meeting id in pendingCancel, which disables the button, and awaits cancel.mutateAsync(meeting.id). Success shows the cancelled toast. An error shows the generic error string. After the refetch the meeting drops out of the upcoming list because its status is now CANCELLED.
Things to know
- Error handling is generic. The screen does not read the server error code, so a slot taken by someone else in the meantime shows the same booking-failed toast as a network failure.
ScheduleMeetingScreenandMeetingCardimport the staticcolorsobject from@/themeinstead of callinguseColors(), unlike most newer screens. See Theming and branding before changing colours here.- There is no reschedule action. The trainee cancels and books again.