* 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>
- Fix route mismatch: mock /api/impersonation/sessions/session-1 (plural)
- Navigate to /?sessionId=session-1 so CustomerPortal fetches session
- Replace .bg-amber-500 with data-testid="impersonation-banner"
- Remove waitForTimeout(1100), use proper waitFor
- Fix locale-dependent time regex in "banner shows started time" test
- Fix loading state race by waiting for response before fulfilling
- Add data-testid to ImpersonationBanner component
- Add trailing newlines to both spec files
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- 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>
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>
Closes#81
- Add window.location.href = '/admin/clients' after clearing session
state in handleEnd so staff are sent back to the admin panel
- Add a test that verifies the redirect fires when End Session is clicked
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Replaces the local impersonationReducer (mock-based) with real API calls
to the /api/impersonation/sessions endpoints added in PR #75.
Changes:
- CustomerPortal: reads ?sessionId= param via useSearchParams, fetches
real session on mount, calls /extend and /end on user action, logs
page views to /sessions/:id/log. Removes demo sidebar button.
- ImpersonationBanner: updated to use ImpersonationSession from
@groombook/types instead of the old mockData shape. Accepts isExtended
prop to control Extend button visibility.
- AuditLogViewer: now fetches from /api/impersonation/sessions/:id/audit-log
instead of receiving auditLog[] as a prop. Handles loading/error states.
- Clients.tsx: "View as Customer" button now POSTs to
/api/impersonation/sessions first, then navigates to /?sessionId=<id>.
Handles 409 (existing active session) by reusing it.
- mockData.ts: removed ImpersonationSession and AuditEntry interfaces
(now live in @groombook/types).
- test/setup.ts: set NODE_ENV=test for React 19 + testing-library compat.
- portal.test.tsx: 13 new tests covering banner, audit log viewer, and
portal session loading behavior (20 total pass).
Closes#76
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Staff can now click "View as Customer" on any client profile in the admin
panel. This navigates to the customer portal with impersonation auto-activated,
showing the portal exactly as that customer would see it (read-only, with
full audit trail).
The portal reads impersonate/clientName/reason/staffName from URL search
params on mount, auto-starts the impersonation session, then cleans up the
URL.
Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
* feat: add customizable business branding (name, logo, colors)
Add admin settings for business branding with name, logo upload, and
color scheme via CSS custom properties. Includes database migration,
API endpoints, admin settings page, and dynamic branding in both
admin nav and customer portal.
Closes#61
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review feedback on branding PR
- Replace dynamic import with static import for @groombook/db in public branding endpoint
- Restore active nav item background highlight (bg-stone-100) in CustomerPortal
- Remove non-null assertion in settings route, add proper error handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: trigger CI
* fix: resolve lint error and test failure for branding feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update E2E tests for branding changes
- Update navigation test to expect "GroomBook" (default branding) instead
of hardcoded "Paws & Reflect" since CustomerPortal now uses dynamic branding
- Add /api/branding mock to shared E2E fixtures so BrandingProvider resolves
immediately in all tests, preventing unhandled fetch interference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: GroomBook CTO <cto@groombook.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: GroomBook CTO <cto@groombook.app>