Commit Graph

72 Commits

Author SHA1 Message Date
Scrubs McBarkley 90abb28a0d fix: address PR #102 review feedback (GRO-145)
- factories.ts: add photoKey/photoUploadedAt null defaults to buildPet (TS regression fix)
- s3.ts: lazy singleton S3Client to avoid re-instantiation per call
- routes/pets.ts: server-side 5MB file size limit, explicit content-type allowlist (drops image/svg+xml etc), validate confirm key ownership against pets/${petId}/ prefix, delete old S3 object on re-upload, fix RBAC comment on DELETE photo
- PetPhotoUpload.tsx: bypass canvas resize for GIFs (preserves animation), pass fileSizeBytes in upload-url request
- Add PetPhotoDisplay.test.tsx: 7 tests covering fetch states, placeholder, refetch on petId change, custom size
- Add PetPhotoUpload.test.tsx: 8 tests covering idle state, type validation, upload flow, progress, GIF bypass
- Update petPhotos.test.ts: add SVG rejection, 5MB limit, key ownership, and old-photo deletion tests (18 total)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 15:41:44 +00:00
Scrubs McBarkley 1380848aea feat: pet photo upload via presigned S3 URLs (GH #93, GRO-123)
- DB migration 0012: add photo_key and photo_uploaded_at columns to pets table
- S3 client utility (apps/api/src/lib/s3.ts): presigned PUT/GET, delete via Rook-Ceph RGW
- API photo routes on petsRouter:
  - POST /:petId/photo/upload-url — returns presigned PUT URL + object key
  - POST /:petId/photo/confirm    — records key in DB after successful upload
  - DELETE /:petId/photo          — deletes from storage and clears DB
  - GET /:petId/photo             — returns presigned GET URL
- RBAC: all staff roles (manager, receptionist, groomer) may upload/delete photos;
  restructured index.ts guards so groomer-accessible photo paths don't overlap
  with the manager/receptionist-only general pets write guard
- Frontend PetPhotoDisplay: responsive image with shimmer skeleton and paw placeholder
- Frontend PetPhotoUpload: client-side resize to max 1200px, XHR with progress,
  presigned PUT flow — binary data never passes through the API server
- Wired both components into Clients.tsx staff portal pet cards
- Unit tests: 14 test cases covering all four routes (happy path + error cases)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 00:07:48 +00:00
groombook-ceo[bot] 8fdffb9564 Merge pull request #96 from groombook/feat/impersonation-indexes-gh95
feat(db): add indexes on impersonation tables
2026-03-21 23:44:18 +00:00
Scrubs McBarkley 11c4f0a07b feat(db): add indexes on impersonation tables (GitHub #95)
Add three indexes to prevent full table scans as session volume grows:
- impersonation_sessions(staff_id, status) for active-session lookup
- impersonation_sessions(client_id) for existing-session check
- impersonation_audit_logs(session_id) for audit log lookup by session

Migration 0011 applied and verified on dev database.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:28:09 +00:00
groombook-ceo[bot] 4f233e7bd3 Merge pull request #92 from groombook/feat/dev-data-strategy-gro-110
feat: deterministic seed, impersonation migration, test factories (GRO-110)
2026-03-21 23:18:36 +00:00
Scrubs McBarkley ad6024f3d9 feat: deterministic seed, impersonation migration, test factories (GRO-110)
Phase 1 — Seed Hardening:
- Replace all Math.random() calls in seed.ts with a Mulberry32 seeded PRNG
  (seed 42) so the same data set is reproduced on every run
- Replace crypto.randomUUID() with a PRNG-based UUID v4 generator
- Add manager (Jordan Lee) and receptionist (Sam Rivera) staff members
  to seed — previously all staff were groomers
- New packages/db/src/reset.ts drops all tables/enums and re-runs
  migrate + seed; exposed as `pnpm db:reset` at root
- Generate migration 0010_impersonation_sessions.sql for the
  impersonation_sessions and impersonation_audit_logs tables that were
  already in schema.ts but had no corresponding migration

Phase 2 — Test Factories:
- New packages/db/src/factories.ts with buildStaff, buildClient, buildPet,
  buildService, buildAppointment and resetFactoryCounters helpers
- Exported via @groombook/db/factories subpath (package.json + vitest alias)
- impersonation.test.ts updated to use buildStaff instead of hand-rolled
  fixture objects

Closes #90 (Phases 1 + 2)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 19:34:52 +00:00
groombook-ceo[bot] 49c6550bf9 Merge pull request #89 from groombook/feat/rbac-middleware-gro-103
feat: RBAC middleware and role-based route guards (Phase 1)
2026-03-21 19:31:18 +00:00
Scrubs McBarkley 543c13f182 fix: correct TypeScript types in rbac.test.ts
Use StaffRow type for all staff fixture objects so groomer/receptionist
variants don't cause type errors. Simplify buildApp/buildWithStaff helper
signatures to MiddlewareHandler<AppEnv> / Context<AppEnv> — no more
Parameters<...> inference gymnastics.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 18:52:10 +00:00
Scrubs McBarkley 93a9ae4461 feat: add RBAC middleware with role-based route guards (GRO-103)
- New `apps/api/src/middleware/rbac.ts` with `resolveStaffMiddleware`
  (resolves staff from DB by OIDC sub, supports AUTH_DISABLED dev mode)
  and `requireRole(...roles)` factory for per-route role enforcement
- Wire `resolveStaffMiddleware` after `authMiddleware` on api basePath
- Route guards per permission matrix:
  - Manager only: /staff/*, /admin/*, /reports/*, /invoices/*, /impersonation/*
  - Manager + Receptionist only: /appointment-groups/*, /grooming-logs/*
  - Groomers read-only on /clients/*, /pets/*, /appointments/* (write requires manager/receptionist)
  - Services: all roles read, manager-only write
- Refactor impersonation router to use AppEnv and c.get("staff") instead
  of inline staff resolution; role check delegated to requireRole middleware
- Unit tests in rbac.test.ts covering resolveStaffMiddleware and requireRole
- Update impersonation.test.ts to inject staff directly via context

Closes #88 (Phase 1)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 15:50:45 +00:00
groombook-ceo[bot] 1ac037a20d Merge pull request #85 from groombook/feat/gro-76-unit-tests
test: Phase 1 unit tests for API routes and web components
2026-03-21 03:05:38 +00:00
groombook-ceo[bot] c6a8adc164 Merge pull request #83 from groombook/docs/readme-add-impersonation-feature
docs: add staff impersonation to README features list
2026-03-21 03:05:31 +00:00
Scrubs McBarkley b7145271fb fix: assert on deletedId in DELETE test to resolve unused-vars lint error
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:52:18 +00:00
Scrubs McBarkley d4629baaea fix: update ImpersonationBanner tests to match current component API
- Import ImpersonationSession from @groombook/types (component was updated in #78)
- Remove stale tests: "shows customer name" and "returns null when inactive"
  (component no longer renders customer name or checks session.active)
- Add isExtended prop to all render calls (component now takes isExtended as prop)
- Fix "does not show Extend button when already extended" to pass isExtended={true}
  instead of session.extended (prop was extracted from session in #78)
- Fix clients.test.ts: selectRows typed as Record<string,unknown>[] to allow
  spread in returning() callbacks (resolves TS2698)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:50:51 +00:00
Scrubs McBarkley d85e09cb11 test: add unit tests for email service, clients route, and ImpersonationBanner
- Email service: 16 tests covering buildConfirmationEmail and buildReminderEmail
  (recipient, subject, body content, groomer presence/absence, reminder timing)
- Clients route: 17 tests covering CRUD endpoints including validation,
  404 handling, soft-disable (disabledAt), and confirm-required delete
- ImpersonationBanner: 8 tests covering render, session expiry auto-end,
  Extend button visibility, and End/Audit button callbacks

Part of GRO-76 (Phase 1 unit/integration tests).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:50:02 +00:00
Clipper McGee 0f6544fb0d docs: add staff impersonation to README features list
Staff impersonation mode shipped in v2026.320 — managers can now view
the customer portal as any client, with a live countdown banner, extend/end
controls, and a full audit log. Update the feature list so new visitors
and potential adopters see the capability.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 00:53:23 +00:00
groombook-ceo[bot] bab84ccb84 Merge pull request #82 from groombook/fix/impersonation-end-redirect
fix: redirect to /admin/clients after ending impersonation
2026-03-20 23:37:04 +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
groombook-ceo[bot] 438a064ff5 Merge pull request #78 from groombook/feat/impersonation-frontend-wiring
feat: Wire customer portal impersonation to real backend API
2026-03-20 23:17:10 +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] 70958542f8 feat: Staff Impersonation backend + frontend wiring (#75)
* feat: implement Staff Impersonation backend and wire frontend

Add server-side impersonation session management with full audit
logging, replacing the frontend-only mock. Managers can start
time-limited sessions to view the app as a specific client.

Backend:
- Add impersonation_sessions and impersonation_audit_logs tables
  (Drizzle schema) with proper FK constraints and status enum
- Add Hono API routes: start/get/extend/end session + audit logging
- Server-side session expiration, one-active-per-staff enforcement
- Staff role validation (manager-only)

Frontend:
- Add CustomerPortal wrapper with URL-param session init
- Add ImpersonationBanner with live countdown timer
- Add AuditLogViewer modal for session audit trail
- Add "View as Customer" button on Clients page
- Auto-log page visits during impersonation

Closes #74

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

* chore: remove unused useNavigate import from Clients.tsx

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

* fix: add authorization + expiry checks to impersonation endpoints, add tests

Security: Add ownership verification (resolveStaff + staffId check) to
GET /sessions/:id, POST /sessions/:id/log, and GET /sessions/:id/audit-log
endpoints that were previously unprotected.

Bug: Add time-based expiry checks to extend, end, get-session, and log
endpoints via checkAndExpireSession() helper. Expired sessions are now
auto-marked as expired in the DB and cannot be extended or logged to.

Tests: Add 23 tests covering session creation (happy path, auth, conflict),
extend (active, expired, non-owner, ended), end (active, expired, non-owner),
audit logging (owner, non-owner, expired, ended), and audit-log retrieval
(owner, non-owner, not found).

Addresses QA review on PR #75 (GRO-66).

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

* fix: resolve @groombook/db source in vitest config

Add resolve alias so vitest can resolve @groombook/db from source
TypeScript files without requiring a prior build step. Fixes CI
test failures when dist/ has not been compiled.

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

---------

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
2026-03-20 08:16:09 +00:00
groombook-paperclip[bot] ea5450651d ci: remove 'Update Infra Image Tags' deploy job (#73)
The deploy job required INFRA_DEPLOY_TOKEN (a GitHub PAT) stored as a
repo secret, which violates the board directive against storing tokens
in repo secrets. Flux Image Automation will handle image tag updates
in the infra repo instead.

Fixes #72

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:12:02 +00:00
groombook-paperclip[bot] 5b52c07219 ci: switch Docker image tags to CalVer versioning (#71)
Replace raw 40-char git SHA tags with CalVer format (e.g. 2026.03.19-19e0f5e)
for better readability and proper release date versioning. The deploy job now
consumes a version output from the docker job instead of using raw SHA.

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 20:56:10 +00:00
groombook-paperclip[bot] 3b7b2b346f ci: auto-update infra image tags after Docker push (#70)
Adds a deploy job that runs after Docker images are pushed to GHCR.
It checks out groombook/infra, updates all image SHA tags in the
Kubernetes manifests, and commits directly to main.

This ensures Flux always picks up new images after a successful build,
preventing the previous issue where :latest tags caused no manifest
diff and pods weren't updated.

Requires INFRA_DEPLOY_TOKEN secret with push access to groombook/infra.

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 20:18:04 +00:00
groombook-paperclip[bot] 19e0f5e3ca feat: client disable/deletion with soft-delete (#69)
* feat: add client disable/deletion with soft-delete (#67)

Add soft-delete support for clients: disable is the default action
(hiding from client list and booking flow), with permanent deletion
requiring explicit type-to-confirm. Disabled clients remain in
reporting and can be re-enabled by staff.

- Add client_status enum (active/disabled) and disabled_at column
- API defaults GET /api/clients to active-only, ?includeDisabled=true shows all
- PATCH /api/clients/:id accepts status field for disable/enable
- DELETE requires ?confirm=true query param
- Booking flow skips disabled clients
- Frontend: show disabled toggle, disable/enable buttons, delete confirmation modal

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

* fix: remove unused updateClientSchema (lint error)

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

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 20:03:18 +00:00
groombook-paperclip[bot] b6b4bc21a0 fix(e2e): block service workers to prevent route mock bypass (#68)
The PWA service worker (VitePWA workbox runtimeCaching) intercepts
/api/* requests, which prevents Playwright's page.route() mocks from
working. This caused the booking flow E2E test to fail because the
availability request was handled by the service worker instead of the
test mock, resulting in real (empty) API responses.

Fixes #65

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 13:57:47 +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] 3388895912 Add dev/demo login selector for quick user switching (#62)
* Add dev/demo login selector for quick user switching

When AUTH_DISABLED=true, the app now shows a login selector page that
lists staff members and clients from the database. Selecting a user
sets a localStorage-based session and sends X-Dev-User-Id header on
all API requests. A persistent bottom bar shows the active persona
with a "Switch user" link.

- API: /api/dev/config (public) and /api/dev/users (auth-disabled only)
- API: auth middleware reads X-Dev-User-Id header when auth is disabled
- Frontend: DevLoginSelector page, DevSessionIndicator bar
- Frontend: fetch interceptor injects X-Dev-User-Id on /api/* calls
- Tests: 7 passing (5 nav + 2 dev login)

Closes #60

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

* fix(e2e): seed dev user in localStorage to prevent login redirect

E2E tests were failing because the dev login selector redirects to
/login when AUTH_DISABLED=true and no dev user is in localStorage.
Added a shared Playwright fixture that pre-seeds localStorage with
a default dev user before each test.

Also rebased onto latest main to resolve merge conflict in App.test.tsx.

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

* fix(e2e): mock /api/dev/config to bypass auth redirect in tests

The fixture now also mocks /api/dev/config to return authDisabled: false,
preventing the app from entering the redirect flow during E2E tests.
Previously only seeded localStorage, but the async config fetch from the
real Docker API was still triggering the redirect check.

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

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 07:35:07 +00:00
groombook-paperclip[bot] 1cf1f19e1d Improve admin UI visual design — polish look and feel (#59)
* Improve admin UI visual design — polish look and feel

- Sticky nav bar with subtle shadow, branded GroomBook wordmark, green gradient Book button
- Consistent brand green (#4f8a6f) for primary buttons across all admin pages
- Tables wrapped in white cards with rounded corners and soft shadows
- Uppercase table headers with better spacing and hierarchy
- Input/button border-radius increased to 6px for softer feel
- Global CSS: button transitions, input focus states with brand green ring, subtle card shadows
- Background changed from plain white to light gray (#f0f2f5) for depth
- Reports: polished stat cards with shadows, refined section headers, card-wrapped tables
- Custom scrollbar styling for a cleaner look

Closes groombook/groombook#58

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

* Fix test selectors for branded nav text

- Use regex /Groom\s*Book/ to match split-element brand text
- Use getByRole("link") for Book CTA to avoid matching brand <strong>

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

* Fix brand text test to handle split-element rendering

The nav brand was changed to <span>Groom</span>Book for color styling,
but getByText with a regex can't match text split across child elements.
Use a custom text matcher that checks the STRONG element's textContent.

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

* Fix E2E tests for split-element brand name

The brand is now <span>Groom</span>Book (no space), so Playwright's
getByText needs "GroomBook" instead of "Groom Book".

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

---------

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Groom Book CTO <cto@groombook.app>
2026-03-19 03:33:34 +00:00
groombook-paperclip[bot] c901b1135d feat: flip routing — customer portal at /, admin at /admin (#57)
* feat: flip routing — customer portal at /, admin at /admin

Move all admin dashboard routes under /admin prefix and mount the
customer portal at root (/). This gives customers clean, shareable
URLs while staff bookmark /admin.

- Admin routes: /admin, /admin/clients, /admin/services, etc.
- Customer portal: / (root)
- Admin nav "Customer Portal" link points to / for staff preview
- Updated tests for new route structure and fixed React 19 act compat

Closes #56

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

* fix(e2e): update tests for routing flip — admin at /admin, portal at /

All E2E tests now use /admin prefix for admin routes (clients, services,
staff, invoices, reports, book). Adds customer portal smoke test at /.

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

* fix(e2e): use specific locator for customer portal test

getByText('Paws & Reflect') matched 3 elements causing strict mode
violation. Scope to navigation role for unique match.

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

---------

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 02:47:19 +00:00
groombook-paperclip[bot] 1136824fe3 fix(web): render customer portal as full-page layout without admin nav (#55)
fix: render customer portal as full-page layout

Separates /portal route from admin layout so the customer portal renders independently.
2026-03-19 02:05:08 +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
groombook-paperclip[bot] 9ab05022a6 fix(packages): reorder exports conditions to prevent Node.js .ts resolution (#52)
Node.js v20.20.1 is matching the `types` export condition before `default`,
causing ERR_UNKNOWN_FILE_EXTENSION when it tries to load .ts source files
at runtime. Moving `default` before `types` ensures Node.js resolves to
the compiled .js output first. TypeScript explicitly seeks the `types`
condition regardless of key order, so TS resolution is unaffected.

Fixes the API container CrashLoopBackOff in the groombook namespace.

Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-18 19:47:32 +00:00
groombook-paperclip[bot] 21c0a7b59c fix(reports): fix churn query crash and improve error reporting (#51)
The /api/reports/clients endpoint was crashing with a 500 on every request
because a raw JavaScript Date passed into a sql template literal in .having()
cannot be serialized by postgres-js. The fix serializes it as an ISO string
with an explicit ::timestamptz cast.

Also adds reportsRouter.onError() and improves the frontend error message
to surface which specific endpoint failed and why.

Fixes #49
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 13:36:31 +00:00
groombook-paperclip[bot] 639429d73d Fix reports crash: serialize Date as ISO string in churn risk query (#50)
The /api/reports/clients endpoint crashes with a 500 because
Drizzle's sql template literal in a HAVING clause cannot serialize
a JavaScript Date object — the postgres driver expects a string.

Convert the Date to an ISO string and add an explicit ::timestamptz
cast so PostgreSQL handles the comparison correctly.

Closes groombook/groombook#49

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 13:26:25 +00:00
groombook-paperclip[bot] e546a73496 Add Renovate configuration for automated dependency updates (#48)
Co-authored-by: groombook-paperclip[bot] <268890960+groombook-paperclip[bot]@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-18 11:47:32 +00:00
groombook-paperclip[bot] 37da0b7490 Fix Dockerfile layer order: copy dist before pnpm install (#45)
pnpm install --prod creates workspace symlinks (node_modules/@groombook/db
→ packages/db/), but dist/ files didn't exist yet, causing Node.js to fall
back to resolving .ts source files at runtime (ERR_UNKNOWN_FILE_EXTENSION).

Copy compiled dist files and updated package.json from the builder stage
before running pnpm install so symlinks point to existing dist output.

Co-authored-by: Groom Book CEO <ceo@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-18 03:44:39 +00:00
groombook-paperclip[bot] 4cf94678d4 fix(e2e): mock reports endpoints with shaped responses in navigation tests (#47)
The Reports page expects structured objects from the API (e.g. summary
with nested revenue/appointments fields, revenue with byPeriod/byGroomer,
etc.). Returning a bare [] caused runtime errors when the component
accessed properties like apptData.byPeriod, crashing the React tree and
making "Groom Book" disappear from the DOM on retries.

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-18 03:26:53 +00:00
groombook-paperclip[bot] 01c0e480ac Fix API crash: add exports field and clean runtime image (#44)
The API Docker image was crashing because Node.js ESM resolution
was finding TypeScript source files instead of compiled JS output.
Added explicit exports fields to workspace packages for deterministic
resolution and a cleanup step in the Dockerfile runner stage.

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-18 03:13:47 +00:00
groombook-paperclip[bot] d0e10b4cfe Merge pull request #43 from groombook/feat/playwright-e2e
Add Playwright E2E testing
2026-03-18 02:52:59 +00:00
Groom Book CTO c957ee2504 fix(e2e): fix strict mode violation in clients detail test
After clicking a client, their email appears in both the list row and
the detail panel — causing a strict-mode violation with toBeVisible().
Use toHaveCount(2) instead to assert the detail panel is open.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 02:50:17 +00:00
Groom Book CTO aa3b080c05 fix: exclude e2e workspace from root pnpm test command
The root `pnpm test` runs across all workspaces. The apps/e2e workspace
requires Playwright browsers to be installed before tests can run, but
the unit-test CI job does not install them. Exclude @groombook/e2e from
the root test command so E2E tests only run in the dedicated e2e CI job.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 02:44:04 +00:00
groombook-cto[bot] 6e087cc16b Fix pets API mock route in clients E2E test
The pets endpoint is /api/pets?clientId=... not /api/clients/*/pets.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 02:41:24 +00:00
groombook-cto[bot] a045749673 Add Playwright E2E testing infrastructure
- New apps/e2e workspace with @playwright/test
- playwright.config.ts targeting Docker Compose stack (http://localhost:8080)
- navigation.spec.ts: smoke tests for all pages
- book.spec.ts: full booking wizard happy-path with API mocking
- clients.spec.ts: client list and detail panel tests
- CI job: spins up docker compose, installs Playwright chromium, runs tests
- Playwright report uploaded as artifact on failure
- README docs for running E2E tests locally

Closes #40

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 02:41:24 +00:00
groombook-paperclip[bot] cba502e35f Set up unit testing infrastructure
Extract slot generation from book.ts into pure utility for unit testing.
Add 8 API unit tests and 4 web component tests with coverage thresholds.

Closes #39

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 01:55:02 +00:00
groombook-paperclip[bot] d718821515 Fix tsconfig rootDir for correct dist output paths
The db package's tsconfig included both src/ and drizzle.config.ts, causing
tsc to compute rootDir as the package root. Output went to dist/src/index.js
instead of dist/index.js, mismatching the main field. Set explicit rootDir
in both db and types tsconfigs and remove drizzle.config.ts from build include.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 01:52:26 +00:00
groombook-paperclip[bot] 817a76f8d5 Fix Docker build: compile TS packages for runtime
Fixes ERR_UNKNOWN_FILE_EXTENSION at container startup by compiling @groombook/types and @groombook/db to dist/ before the runtime stage, and copying only compiled JS instead of raw TypeScript source.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 01:34:26 +00:00
groombook-paperclip[bot] 227a687e97 Add seed Docker stage and push migrate/seed images in CI (#37)
- Add seed stage to API Dockerfile (FROM builder, runs pnpm db:seed)
- Add explicit target: runner to API image build (prevents building wrong stage)
- Add CI steps to push ghcr.io/groombook/migrate and ghcr.io/groombook/seed images

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 23:47:56 +00:00
groombook-paperclip[bot] 20fa4698be Add test data seed script with 500 clients, 6 staff, and appointments (#36)
Creates packages/db/src/seed.ts that generates realistic development data:
- 3 groomers + 3 bathers (staff)
- 10 grooming services
- 500 clients with 1-3 dogs each
- ~2500 appointments across 12 months with varied statuses
- Invoices with line items and tip splits for completed appointments
- Grooming visit logs

Run via: pnpm db:seed (requires DATABASE_URL)

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 23:37:18 +00:00
groombook-paperclip[bot] 1c54c4264d Update CI to push Docker images to ghcr.io (#35)
Enable image pushing to GitHub Container Registry on main branch
merges. Tags images with both commit SHA and latest.

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 22:23:33 +00:00