5.4 KiB
5.4 KiB
2026-05-02 — Therapist Self-Serve (Phase B)
Companion to
Initial.md. Predecessor:2026-05-02-admin-crud.md. Closes Phase B of the post-payments UX plan.
Milestone
Therapists can now sign in and manage their own availability without involving admin. They also see their own schedule (upcoming + recent appointments) with customer contact info. The admin-side per-therapist availability editor and the new therapist self-serve editor share the same component, so any future improvement to the editor benefits both surfaces.
What landed
| Path | Role |
|---|---|
src/components/AvailabilityEditor.tsx |
Reusable server component (no client state) — weekly working-hours grid + overrides list/add/delete. Caller supplies the data + 3 server-action callbacks + optional hidden form fields. |
src/app/admin/therapists/[id]/availability/page.tsx (refactored) |
Now uses AvailabilityEditor with hiddenFields={{ therapistId: id }}. Same behavior, less code. |
src/app/therapist/layout.tsx |
Auth-required, role-gated to THERAPIST. Friendly redirect/explanation for ADMIN or CUSTOMER hitting the route. Header with "My schedule" + "Availability" + sign-out. |
src/app/therapist/page.tsx |
Redirects to /therapist/bookings (most-useful default). |
src/app/therapist/availability/page.tsx |
Same editor as the admin page, scoped to session.user.id. Server actions read therapistId from the session and ignore any form-supplied therapistId — defense in depth. Delete-override action also re-checks ownership. |
src/app/therapist/bookings/page.tsx |
Read-only schedule. Upcoming table (When, Service+duration, Customer name+email+phone, Room) + recent list with status pills. |
What's verified
pnpm test— 71/71 (no new tests)pnpm lint— cleanpnpm exec tsc --noEmit— clean- Live smoke test as
mei@touchbase.local(THERAPIST role from seed):- Magic-link sign-in with
callbackUrl=/therapist→ lands on/therapist/bookings /therapist(no trailing path) → 307 →/therapist/bookings/therapist/bookingsshows the two seeded-CLI bookings with customer name + email/therapist/availabilityrenders working-hours editor (Tuesday-Saturday from seed) + override form/admin/bookings(as therapist) renders "Not authorized" — correctly gated
- Magic-link sign-in with
Decisions ratified
| Decision | Resolution |
|---|---|
| Editor reuse | Extracted to src/components/AvailabilityEditor.tsx. Caller passes data + actions + optional hidden fields. Reason: avoid 280 LOC of duplication; one place to fix bugs. |
| Therapist actions ignore form-supplied therapistId | Server actions read therapist id from auth() session, never from FormData. Reason: a malicious THERAPIST could otherwise edit another therapist's schedule by tampering with the form. The component still has a hiddenFields prop for the admin route, but the therapist route doesn't pass any. |
| Delete-override ownership check | Action looks up the override and aborts if therapistId !== session.user.id. Reason: defense in depth; the page only shows own overrides but the action could be replayed with someone else's id. |
| Therapist landing | /therapist redirects to /therapist/bookings (the schedule), not /therapist/availability. Reason: most therapists open the app to see "what's next today" — the schedule is the more useful default. |
| Therapist sees customer contact info | Name + email + phone exposed in the schedule table. Reason: legitimate operational need (running late, customer no-show, etc.). Not HIPAA in our scope, but worth being mindful of. |
| Cross-role friendly errors | ADMIN visiting /therapist sees "Therapists only" with link to /admin/therapists. CUSTOMER sees link to /account/bookings. Reason: mistaken navigation should explain itself. |
Gotchas hit
None this session — clean refactor + extension.
Open questions
- Customer-visible brand name (still pending)
- Currency
- Stripe account ownership
- NEW: should the therapist schedule include a "mark complete / no-show" button, or do we keep that admin-only? Coming up in Phase C.
- NEW: today's-day highlight in the therapist schedule? Defer until requested.
Roadmap status
UX-completeness:
- A — Admin CRUD: done 2026-05-02
- B — Therapist self-serve: done 2026-05-02 (this session)
- C — Customer reschedule + admin "mark complete/no-show" + booking detail pages
- D — PWA shell
- E — Polish
Recommended next step
Phase C — booking lifecycle UX:
- Booking detail page at
/admin/bookings/[id](and maybe/account/bookings/[id]for customers): all fields, history, actions. - Reschedule action for customers (and admin): cancel + rebook in one transaction. Likely add
rescheduleBooking()tosrc/lib/booking.tsthat wraps the operation atomically. - Admin "mark complete / no-show" buttons on the admin booking detail (or directly on the row in
/admin/bookings). Adds two more state transitions to handle.
Estimate ~1 day. Mostly composition over existing primitives.
How to resume
cd /Users/noise/Documents/code/touchbase
docker-compose up -d postgres mailpit
pnpm db:seed
pnpm tsx scripts/book-on-behalf.ts alex@example.com "60-minute Swedish" 2026-05-05T10:00
pnpm dev
# Sign in as a therapist (e.g. mei@touchbase.local) → lands on /therapist/bookings
# Click "Availability" → edit working hours, add an override
# Sign out, sign in as admin@touchbase.local → /admin/* still works as before