Replace the unconditional signIn.social() call on every render with a
LoginPage component that shows a "Sign in with SSO" button. This
prevents redirect loops when the OAuth flow fails or the session
cookie is not yet established.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
In-cluster pods can't reach the external gateway IP (hairpin NAT).
When OIDC_INTERNAL_BASE is set, use explicit OAuth endpoint URLs:
- authorizationUrl: external (browser redirect, no server fetch)
- tokenUrl/userInfoUrl: internal K8s service (server-to-server)
Falls back to discoveryUrl for local dev when not set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hono's basePath() + api.on("/auth/**") didn't match correctly.
Using api.route("/auth", authRouter) with a dedicated sub-app
ensures all /api/auth/* paths reach Better-Auth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hono's basePath() creates a sub-app that captures /api/* requests.
Route handlers on the parent app are not reachable for paths that
match the sub-app's middleware. Moving the handler to the api sub-app
(with path /auth/** instead of /api/auth/**) fixes the 404.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
toNodeHandler writes directly to Node ServerResponse and returns void,
causing Hono to return 404 for all Better-Auth routes. auth.handler
uses web standard Request/Response which Hono expects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
resolveStaffMiddleware crashed with "Cannot read properties of
undefined (reading 'sub')" on /api/auth/* requests because
authMiddleware skips those paths without setting jwtPayload.
Add the same skip so Better-Auth routes pass through cleanly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The auth middleware was intercepting /api/auth/** requests (OAuth
callbacks, session management) and returning 401 before Better-Auth
could process them. This prevented login when AUTH_DISABLED=false.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Import lt, gt, ne from @groombook/db alongside existing eq/and
- Add non-null assertion on appt.staffId since appointment always has staffId
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Add POST /api/portal/appointments/:id/reschedule endpoint with:
- Session auth via X-Impersonation-Session-Id header
- Ownership validation (clientId match)
- Past/in-progress/cancelled/completed guard
- Conflict detection for the target time slot
- Duration-preserving reschedule (keeps original endTime offset)
- Add RescheduleFlow modal component in AppointmentsSection:
- Date picker + time slot grid (same times as BookingFlow)
- Shows current appointment summary
- POSTs to /api/portal/appointments/:id/reschedule
- Reloads page on success
- Wire Reschedule button in AppointmentCard (Appointments section)
- Wire Reschedule button in Dashboard next-appointment card
- Add showReschedule/rescheduleAppointment state in CustomerPortal
Fixes GRO-166
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable the Edit Pet button on Manage Pets (Settings) and Pet Profiles,
and the Add New Pet button. Add PetForm component for editing. Remove
disabled/stub attributes from Reschedule, Cancel, and Add Notes buttons
in Dashboard and Appointments.
Resolves GRO-167.
Co-Authored-By: Paperclip <noreply@paperclip.ing>