Commit Graph

15 Commits

Author SHA1 Message Date
Barkley Trimsworth b0ab41bb4e fix(portal): redirect unauthenticated users to /login
CustomerPortal now redirects to /login after session init completes
with no valid session, preventing portal chrome from rendering for
unauthenticated users. Dashboard !sessionId branch uses Navigate
redirect instead of dead-end UI. Staff redirect in App.tsx verified.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 19:12:38 +00:00
Flea Flicker fe9f1f8f78 fix(GRO-286): remove unused useCallback import and eslint-disable
- ReportCards.tsx: remove unused useCallback from imports
- AccountSettings.tsx: remove unused eslint-disable-next-line directive

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 11:24:17 +00:00
Barkley Trimsworth 5aad2da55a fix(web): add X-Impersonation-Session-Id header to portal API calls
This commit also includes GRO-287 fixes:
- PasswordChange: add stateful form with password-match validation
- ReportCards: replace window.location.reload() with refetch via useRef

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 11:04:03 +00:00
groombook-engineer[bot] 8437dc43dc fix: show Pay Now button during impersonation
Remove readOnly guard from Pay Now button and PaymentModal in BillingPayments.
The readOnly guard was too broad — it hid the Pay Now button during staff
impersonation sessions, making it impossible for staff to collect payments.

Other readOnly guards (Remove payment method, Autopay toggle) remain intact.

Co-authored-by: groombook-engineer[bot] <269742240+groombook-engineer[bot]@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-30 10:56:21 +00:00
groombook-engineer[bot] 73ce16ee74 fix: billing portal session header and response format mismatch (#168)
Fixes GRO-261 — billing portal session header mismatch and response format bug.

- x-session-id → X-Impersonation-Session-Id in BillingPayments.tsx and Dashboard.tsx
- Handle bare array response from /api/portal/invoices

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 01:54:11 +00:00
groombook-engineer[bot] 6cd2ea6ca9 fix(portal): wire Pay Now button with payment modal (GRO-261)
Closes GRO-261 — Pay Now button on Billing page now opens a payment modal with invoice selection and simulated payment flow.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 20:24:56 +00:00
groombook-engineer[bot] 4dabb25ee1 fix(portal/book): wire Rebook Now button + date format validation (GRO-265, GRO-266)
* fix(portal): wire Rebook Now button to navigate to booking wizard (GRO-265)

The "Rebook Now" button on the Report Card detail view had no click
handler. Now navigates to /admin/book with pet info pre-filled via URL
params (petName, serviceName). Button text changed from "Book Now" to
"Rebook Now" per the bug report.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(book): pre-fill form from URL params to ensure React state is set

Add useSearchParams to read URL parameters (e.g., ?clientName=Jane)
and sync them to the BookingBody state on mount via useEffect.
This ensures validation checks React state, not empty initial state.

Fixes GRO-255

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(book): add inline validation for date input format (GRO-266)

Date picker now shows a clear error when the value doesn't match
YYYY-MM-DD, instead of silently failing with a browser console warning.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(portal): wire Rebook Now button + clean .js artifacts (GRO-265)

Cherry-picked from contaminated PR #160:
- ReportCards.tsx: Rebook Now button navigates to /admin/book with pet info
- Book.tsx: pre-fill form from URL params (GRO-255)
- Book.tsx: inline date validation (GRO-266)

Also removes compiled .js artifacts (Book.js, ReportCards.js)
that were incorrectly committed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: groombook-ci[bot] <ci@groombook.bot>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-29 15:14:44 +00:00
groombook-engineer[bot] 4746a63292 feat(portal): replace mock data with real session-driven API calls (#152)
Closes GRO-205. Reviewed and approved by CTO (The Dogfather) and QA (Lint Roller). cc @cpfarhood
2026-03-29 07:08:35 +00:00
groombook-engineer[bot] 6872342d8f fix(auth): resolve redirect loop and mount Better-Auth as sub-app (#144)
## Changes
- Replace toNodeHandler with auth.handler(c.req.raw) sub-app mount for Hono compatibility
- Add /api/auth/ path skip in authMiddleware and resolveStaffMiddleware
- Add OIDC_INTERNAL_BASE env var for split-horizon (hairpin NAT) URL resolution
- Replace render-time signIn.social() with LoginPage component (fixes redirect loop)
- Change auth-client baseURL to relative (empty string) for deployed environments
- Add POST /api/portal/appointments/:id/reschedule endpoint with session auth
- Add RescheduleFlow modal, PetForm component, and wire Dashboard/Appointments UI

## CTO Note
Auth fix is P0-critical. Portal mock data (UAT blocker) predates this PR and is tracked separately in GRO-218.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-28 22:10:50 +00:00
groombook-cto[bot] f1b85bf294 fix(portal): disable non-functional stub buttons in customer portal (#142)
All CI checks pass. Verified on groombook.dev.farh.net. Second approval from groombook-ceo[bot] per GRO-171.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-28 08:24:32 +00:00
groombook-engineer[bot] 9eb0c3d151 fix(gro66): E2E selector fix + groomer isolation + portal confirm/cancel
* Implement confirm/cancel in customer portal (GRO-50)

Backend:
- Add POST /api/portal/appointments/:id/confirm endpoint
  - Validates impersonation session auth and ownership
  - Rejects past/in-progress, non-pending, or already-cancelled/completed
  - Sets confirmationStatus="confirmed", confirmedAt, updatedAt
- Add POST /api/portal/appointments/:id/cancel endpoint
  - Same auth/ownership pattern
  - Rejects past/in-progress or already-cancelled/completed
  - Sets status="cancelled", confirmationStatus="cancelled", cancelledAt, updatedAt

Frontend (Appointments.tsx):
- Add confirmationStatus field to Appointment type and mock data
- Add ConfirmationSection component: shows status badge + confirm button
- Add CancelAppointmentButton: wires to cancel API with loading/error state
- Wire existing Cancel button to CancelAppointmentButton
- Show confirmation status badge in expanded view for upcoming appointments

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(gro-48): row-level data scoping for groomer role (RBAC Phase 2)

Filter query results at the route handler level when staff role is groomer:

- GET /api/appointments: WHERE staffId = groomer OR batherStaffId = groomer
- GET /api/appointments/🆔 403 if not assigned to groomer (as staff or bather)
- GET /api/clients: Clients with ≥1 appointment for this groomer (via exists subquery)
- GET /api/clients/🆔 403 if no appointment linkage
- GET /api/pets: Pets owned by groomer-linked clients (via exists subquery)
- GET /api/pets/:petId: 403 if no appointment linkage

Managers and receptionists: no change.

Added exists to @groombook/db exports (was missing from re-export).
Added groomerIsolation unit tests for role guard and filter logic.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-50): add portal confirm/cancel tests and fix ConfirmationSection state

- Add test coverage for POST /portal/appointments/:id/confirm endpoint
- Add test coverage for POST /portal/appointments/:id/cancel endpoint
- Fix ConfirmationSection not updating local status after successful confirm
- Remove unused onCancel prop from ConfirmationSection call site
- Fix Appointments.test.tsx missing confirmationStatus field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(gro-50): add ConfirmationSection UI component tests

Add tests for the ConfirmationSection component:
- Renders correct badge for each confirmationStatus state
- Shows/hides Confirm button based on status
- Calls confirm API with correct headers
- Handles sessionId null case
- Shows error messages for 401/403/422 responses
- Shows loading state while confirming
- Shows success message briefly after confirm
- Does not call API if user cancels confirm dialog

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-48): address QA review feedback — staffRow?.role and portal TS guards

- appointments.ts: use staffRow?.role (consistent with clients.ts/pets.ts)
  to handle undefined staff context safely
- portal.ts: add null guards on .returning() results for confirm and cancel
  endpoints (TS18048: 'updated' is possibly undefined)
- All 188 tests passing; TypeScript typecheck clean

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro66): use specific selector for banner visibility assertion

Replace ambiguous `getByText("STAFF VIEW")` that matched both the
ImpersonationBanner and the CustomerPortal watermark with a precise
`getByTestId("impersonation-banner")` selector to eliminate strict
mode violations.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-66): add missing afterEach to vitest import

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-48): add icalToken to MANAGER mock after rebase

After rebasing onto origin/main (which added icalToken to the staff
schema via GRO-107), the MANAGER mock in groomerIsolation.test.ts was
missing the new required field. Added icalToken: null to the MANAGER
constant. factories.ts is clean (no duplicate icalToken after rebase).

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-47): add non-null assertions on Drizzle RETURNING results

Drizzle's update().returning() types the array element as T | undefined.
After the if (!appt) guard, updated is still typed as possibly undefined
because RETURNING can succeed with no rows. Add ! assertions since
we already guard with the existence check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Flea Flicker <fleaflicker@groombook.ai>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Flea Flicker <flea-flicker@paperclip.ing>
2026-03-27 14:23:19 +00:00
groombook-engineer[bot] d0b4baf5aa feat: customer-facing appointment notes (GRO-106) (#109)
* feat: add customer-facing appointment notes (GRO-106)

- Migration 0014: add customer_notes column to appointments
- Schema update: add customerNotes field to appointments table
- Factory update: include customerNotes in buildAppointment
- Portal route: PATCH /api/portal/appointments/:id/notes
  - Ownership validation via impersonation session
  - Future-only validation (no edits after start)
  - 500 character limit
- Register portal router in index.ts

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* Fix confirmationToken leak and add unit tests for portal notes endpoint

- Return only id, customerNotes, updatedAt instead of full appointment row
- Add comprehensive unit tests covering auth, ownership, time-gating, and validation
- Fix: confirmationToken no longer returned to portal session

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat: add customer notes UI to portal and staff views (GRO-178)

- Add customerNotes field to Appointment type
- Add read-only customer notes display in staff appointment detail modal
- Add customer notes textarea with save, char counter (500 max), and disabled state
- Wire up PATCH /api/portal/appointments/:id/notes in portal UI
- Update mockData with customerNotes field

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: address QA review feedback - null check and portal route auth

- Add null check after db.update().returning() in portal notes endpoint
- Move portal router registration before auth middleware so clients can access it
- Remove unused ENDED_SESSION variable from test file

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(portal): address QA review - isUpcoming time parsing and session header

- Fixed parseTimeTo24Hour to handle 12-hour AM/PM format correctly
- Added X-Impersonation-Session-Id header to CustomerNotesSection fetch
- Added comprehensive tests for CustomerNotesSection and time parsing
- Fixed TypeScript strict null checks for parseTimeTo24Hour

Fixes QA review issues:
- isUpcoming() now correctly parses 12-hour time format
- CustomerNotesSection sends session ID header for auth
- Added unit tests for new UI component

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: thread sessionId as prop instead of sessionStorage

CustomerNotesSection was reading sessionStorage for the impersonation
session ID, but CustomerPortal stores it in React state. Pass sessionId
as a prop through AppointmentsSection and AppointmentCard instead.

Also update tests to pass sessionId prop and add test for null sessionId
case.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: groombook-cto[bot] <269737991+groombook-cto[bot]@users.noreply.github.com>
2026-03-26 08:24:21 +00:00
Scrubs McBarkley a15585a8e6 fix: address QA feedback on site theming PR (GH #91)
- Fix gradient regression in ReportCards.tsx: use distinct color stops
  (--color-accent-lighter → --color-accent-light) to restore subtle gradient
- Fix BrandingContext meta tag accumulation: cache ref with useRef instead
  of querying DOM on every render to prevent duplicate elements on remount
- Add BrandingContext.test.tsx: verify CSS vars applied, theme-color meta
  created/updated, and no duplicate meta tags on rerender

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:12:57 +00:00
Scrubs McBarkley afde6b7857 feat: unify site theming via CSS custom properties (GH #91)
Replace all hardcoded brand color hex values with CSS custom properties
so BrandingContext drives both the customer portal and staff site.

- index.css: add derived accent/primary vars using color-mix()
  (--color-accent-hover, --color-accent-dark, --color-accent-light,
  --color-accent-lighter, --color-primary-dark); fix focus ring styles
  to use var(--color-primary) instead of hardcoded hex
- BrandingContext.tsx: also update <meta name="theme-color"> in sync
  with primaryColor so PWA theme-color tracks branding at runtime
- portal/sections: replace bg-[#8b7355], text-[#6b5a42], bg-[#f0ebe4],
  bg-[#faf5ef], hover:bg-[#7a6549] etc. with Tailwind v4 CSS var
  utilities (bg-(--color-accent), text-(--color-accent-dark), etc.)
- pages: replace inline style "#4f8a6f"/"#3d7a5f" with
  var(--color-primary) / var(--color-primary-dark) across Appointments,
  Book, Clients, GroupBooking, Invoices, Reports, Services, Staff, and
  DevSessionIndicator

Closes #91

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:50:43 +00:00
groombook-paperclip[bot] 5757cd0631 feat: customer portal with 7 sections and staff impersonation (#54)
* feat(web): add customer portal with 7 sections and staff impersonation

Implements the customer-facing portal for pet parents with:
- Dashboard showing upcoming appointments, pet cards, loyalty rewards
- Multi-step appointment booking flow with recurring scheduling
- Pet profiles with medical/behavioral notes and vaccination tracking
- Grooming report cards with before/after, behavior assessment, sharing
- Billing & payments with invoices, saved methods, autopay, tips, packages
- Communication with chat-style messaging and notification preferences
- Account settings with personal info, password, pet management, agreements
- Staff impersonation mode with required reason, 30-min session timer,
  non-dismissable banner, viewport border, watermark, read-only enforcement,
  and full audit trail viewer

Also adds Tailwind CSS, lucide-react, and recharts as dependencies.

Closes #53

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(web): remove unused imports to pass lint

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 00:23:49 +00:00