Skip to content

Commit f202acf

Browse files
refactor(scheduling): wire availability form to action and address PR review
- Make AvailabilityCalendar controlled (value/onChange) and SSR-safe via anchor prop - Wire AvailabilityForm to selectAvailabilities with useActionState; submit button now actually submits - Replace hardcoded hex colors with design tokens; use Textarea/Button/Label primitives - Consolidate drag state into a single discriminated union (no more parallel state/ref pairs) - Add keyboard support (Space/Enter to toggle) and improved aria-labels - Remove hardcoded weekend block; daysOfWeek is now the single source of truth - Fix tooltip to follow the pointer instead of being pinned to viewport center - Persist optional coach message via notes field on coaching_sessions - Rename users-availability-calendar -> availability-calendar - Rename user-availability-calendar-submit-coach-message -> availability-form Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 8d7b873 commit f202acf

7 files changed

Lines changed: 660 additions & 567 deletions

File tree

app/scheduling/actions.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ const selectAvailabilitiesSchema = z.object({
2929
time_slots: z
3030
.array(timeSlotSchema)
3131
.min(1, "At least one time slot is required")
32-
.max(50,"You can select up to 50 time slots")
32+
.max(50,"You can select up to 50 time slots"),
33+
notes: z
34+
.string()
35+
.max(2000, "Message must be 2000 characters or fewer")
36+
.optional(),
3337
});
3438

3539
const selectTimeSlotSchema = z.object({
@@ -120,9 +124,14 @@ export async function selectAvailabilities(
120124
return {errors: {time_slots: ["Invalid time slots format"]}};
121125
}
122126

127+
const rawNotes = formData.get("notes");
123128
const parsed = selectAvailabilitiesSchema.safeParse({
124129
session_id: formData.get("session_id")?.toString(),
125-
time_slots: rawTimeSlots
130+
time_slots: rawTimeSlots,
131+
notes:
132+
typeof rawNotes === "string" && rawNotes.trim().length > 0
133+
? rawNotes
134+
: undefined,
126135
})
127136

128137
if (!parsed.success) {
@@ -131,6 +140,7 @@ export async function selectAvailabilities(
131140

132141
const sessionId = parsed.data.session_id;
133142
const timeSlots = parsed.data.time_slots.map(normalizeSlots)
143+
const notes = parsed.data.notes ?? null;
134144

135145
// 3. Authorization: caller must be the coach or user on this session
136146
const result = await getAuthorizedSession(sessionId, callerId);
@@ -153,6 +163,7 @@ export async function selectAvailabilities(
153163
.update(coachingSessions)
154164
.set({
155165
selectedTimeSlots: timeSlots,
166+
notes,
156167
updatedAt: new Date(),
157168
})
158169
.where(eq(coachingSessions.id, sessionId));

0 commit comments

Comments
 (0)