Commit Graph

18 Commits

Author SHA1 Message Date
groombook-cto[bot] c438f5772c feat(GRO-607): Stripe Elements payment UI replacing mock flow
* GRO-605: Stripe SDK integration + payment service

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

* GRO-606: Add payment API endpoints (pay invoice, payment methods, refunds)

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

* feat(GRO-597): Stripe payment backend — schema, service, API, webhooks

Consolidates GRO-605, GRO-606, GRO-608 into a single clean PR:
- GRO-605: Stripe SDK integration + payment service
- GRO-606: Payment API endpoints (pay invoice, payment methods, refunds)
- GRO-608: Stripe webhook handler

Migration consolidation:
- Single 0026_stripe_payment.sql migration adds stripeCustomerId to clients
  and stripe_payment_intent_id, stripe_refund_id, payment_failure_reason to invoices
- Removed duplicate 0027_stripe_identifiers.sql

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

* GRO-607: Install Stripe frontend packages

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

* GRO-607: Add /portal/config endpoint + rename date field

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

* GRO-607: Replace mock payment flow with real Stripe Elements

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

* fix(GRO-607): Stripe Elements payment UI - lint/type fixes

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

* fix(GRO-607): remove unused eslint-disable directive in CustomerPortal

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

* fix(GRO-607): CTO review fixes — payment security and correctness

- Fix multi-invoice total calculation: use inArray() instead of eq()
  on single ID, sum all invoices not just first
- Add ownership check to payment method deletion: verify the payment
  method belongs to the authenticated Stripe customer before detaching
- Remove duplicate /config endpoint in portal.ts
- Fix webhook Stripe client: use getStripeClient() from payment service
  instead of constructing with WEBHOOK_SECRET
- Remove unnecessary body validator on /invoices/:id/pay route
- Export getStripeClient() for use by stripe-webhooks.ts
- Add inArray import to payment.ts

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 08:27:03 +00:00
groombook-engineer[bot] 57382b10ec fix(portal): prevent /login redirect for client dev users (GRO-354)
* fix(portal): prevent /login redirect for client dev users when session.id is null

When a client clicks "Abigail Brooks" in the dev login selector,
POST /api/portal/dev-session returns 201 but the session may not have
id set immediately (timing issue or API response). This caused both
CustomerPortal and Dashboard to redirect to /login because session?.id
was null.

Changes:
- CustomerPortal: don't redirect to /login for client dev users even
  if session is null — the dev-session flow has verified the user
- Dashboard: check for dev user before redirecting when sessionId is null

This ensures client dev users see the portal rather than being
immediately redirected back to /login.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix(portal): remove .js extension from DevLoginSelector import in Dashboard

TS2307: Cannot find module "../pages/DevLoginSelector.js"
The source file is .tsx, not .ts/js. Fixes typecheck failure in CI.

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

* fix(portal): correct import path for DevLoginSelector in Dashboard

Dashboard.tsx is at portal/sections/ (2 levels deep from src/),
so the import path needs ../../pages/ not ../pages/.

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

---------

Co-authored-by: Barkley Trimsworth <barkley@groombook.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: groombook-qa[bot] <269744346+groombook-qa[bot]@users.noreply.github.com>
2026-04-01 10:35:46 +00:00
Barkley Trimsworth b55496fdde fix(portal): remove unused sessionAttempted state variable
The sessionAttempted state was removed from the redirect condition
(commit df32509) but its declaration and setter calls were left
behind, causing a TypeScript/ESLint unused-variable error.

Removed:
- sessionAttempted useState declaration
- All 4 setSessionAttempted(true) calls
- Stale comment referencing sessionAttempted in redirect block

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 21:21:52 +00:00
Barkley Trimsworth df32509186 fix(portal): remove sessionAttempted from redirect condition (GRO-309) 2026-03-31 18:45:08 +00:00
Barkley Trimsworth 991660405d fix(portal): prevent Dashboard redirect during impersonation session load
When navigating to /?sessionId=xxx, Dashboard would immediately
redirect to /login because sessionId was null before the fetch
completed. The impersonation banner never rendered.

Add isImpersonating state: true while impersonation fetch is in-flight,
prevents Dashboard from redirecting until session loads.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 17:43:00 +00:00
Barkley Trimsworth fdc324d445 fix(portal): remove stray } in logo data URL and restore Dashboard redirect
- CustomerPortal.tsx: fix stray } in base64 data URL src attribute
- Dashboard.tsx: restore Navigate to /login for !sessionId (defense-in-depth)

The stray } was introduced in commit fa92a65 which also reverted
the Dashboard redirect. This commit restores both fixes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 17:29:57 +00:00
groombook-engineer[bot] 49aa6ac989 fix(portal): prevent premature redirect with sessionAttempted flag
Fixes E2E race condition where setSession and setInitComplete are batched
by React concurrent rendering, causing redirect to fire before session
is set. The sessionAttempted flag tracks "did we try" so redirect only
fires when there was NO attempt, not when the state update is pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 16:54:22 +00:00
Barkley Trimsworth 5860d822cf fix(portal): redirect unauthenticated users to login — never show portal chrome (GRO-309)
- CustomerPortal.tsx: add initComplete state to track async session
  initialization. After init completes with no valid session, redirect:
  staff dev users → /admin, all others → /login
- Dashboard.tsx: change !sessionId fallback from dead-end UI to
  <Navigate to="/login" replace /> (defense-in-depth)
- All 85 web unit tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-31 00:53:59 +00:00
Barkley Trimsworth 51431c7bc1 fix(portal): wire dev client login to portal session
When a client user selects their account from the dev login selector,
the portal previously had no way to establish an authenticated session —
it only checked for a ?sessionId= URL param (used by the real staff
impersonation flow). This caused the portal to always show "Hi, Guest".

Changes:
- POST /api/portal/dev-session: new endpoint (auth-disabled only) that
  creates an impersonation session for a given clientId, using a fixed
  dev staff ID to avoid conflicts with the one-active-session-per-staff
  rule in the real impersonation flow. Sessions are long-lived (24h).
- CustomerPortal: on mount, after checking for ?sessionId=, also check
  for a dev client user in localStorage and call /api/portal/dev-session
  to obtain a session. This mirrors the real impersonation flow so all
  existing portal API calls (which require X-Impersonation-Session-Id)
  work without modification.

cc @cpfarhood

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-30 17:07:49 +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] 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-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 17a965552a fix: redirect to /admin/clients after ending impersonation session
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>
2026-03-20 23:19:06 +00:00
Scrubs McBarkley 8de6528bd3 feat: wire customer portal impersonation to real backend API
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>
2026-03-20 17:26:45 +00:00
groombook-paperclip[bot] 12ad7c66a0 feat: add View as Customer impersonation button on Clients page (#64)
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>
2026-03-19 12:47:26 +00:00
groombook-paperclip[bot] f2501d9972 feat: customizable business branding (name, logo, colors) (#63)
* 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>
2026-03-19 11:07:07 +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