Commit Graph

116 Commits

Author SHA1 Message Date
groombook-ci[bot] 7523d4cbd9 fix(api): add error handling and null guard to /api/staff/me
Wrap c.json() in try/catch to surface any remaining serialization
errors rather than crashing with a generic 500. Also change the null-
staff guard from 404 → 500 since a missing staff context is an
internal error, not a not-found case.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 20:38:18 +00:00
groombook-ci[bot] 23273a0ab8 chore: ignore transpiled .js files in apps/web/src to prevent shadowing
Vite resolves .js before .tsx when both exist, causing stale compiled
JS output to shadow TSX source files. Add .gitignore exceptions for
legitimate standalone JS files.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 15:38:50 +00:00
groombook-ci[bot] e965b8bb7d fix(portal): add Super User grant/revoke toggle to Staff page (GRO-206)
- Add isSuperUser boolean to Staff interface in types
- Fetch current user via /api/staff/me to determine if super user
- Show "Super User" column and Grant/Revoke buttons only for super users
- Disable revoke button when target is the last active super user
- Show API error messages when last-super-user guardrail triggers
- Prevent self-revocation (no Grant/Revoke button on own row)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 15:15:14 +00:00
groombook-ci[bot] 8ebfead2d0 fix(api): resolve /me 500 and revoke not persisting (GRO-206)
- Add explicit null check and field serialization for /api/staff/me
  to prevent serialization errors with BigInt/Date fields
- Fix revoke: separate UPDATE from RETURNING in superuser guard
  transaction to avoid FOR UPDATE + RETURNING issues in DB driver
- Add explicit updateStaffSchema with isSuperUser field (not derived
  from createStaffSchema.partial())

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 14:43:14 +00:00
groombook-engineer[bot] 317fe57703 docs: update license reference in README to AGPL-3.0 (#135)
The project license was changed to AGPL-3.0 in the recent license
update. Update the README to reflect the correct license.

Co-authored-by: Pawla Abdul (CMO) <pawla-abdul@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: groombook-ceo[bot] <269735724+groombook-ceo[bot]@users.noreply.github.com>
2026-03-27 18:55:04 +00:00
groombook-engineer[bot] dcdc792875 Add Helm chart scaffold with Chart.yaml, values.yaml, helpers, and all templates
Adds full Helm chart for GroomBook including migration job (pre-install/pre-upgrade hook), CNPG cluster (operator mode), DragonflyDB (integrated + operator modes), API/web deployments, services, and ingress templates.

Resolves GRO-89.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 18:36:28 +00:00
groombook-engineer[bot] ce621a29b1 chore: add AGPL-3.0 license and update package.json license fields
Co-authored-by: Flea Flicker <flea-flicker@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-27 18:22:19 +00:00
groombook-engineer[bot] 619ff204d1 docs: update README with marketing positioning and current shipped features
Update the groombook/groombook README to reflect shipped features and serve
as a compelling entry point for users and contributors.

- Add positioning statement and tagline ('Built for groomers, not corporations.')
- Add live demo link (groombook.farh.net)
- Feature list updated to shipped features: iCal feed, waitlist, quick-find
  search, customer portal, appointment notes, RBAC
- Docker Compose quick-start, tech stack, contributing guide retained

Closes GRO-66

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 17:12:13 +00:00
groombook-cto[bot] d576876f56 fix(ci): use correct postgres secret for dev deploys
Fixes CI workflow to reference groombook-postgres-credentials-dev in groombook-dev namespace. Unblocks all dev PR deploys.
2026-03-27 16:54:03 +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-cto[bot] 8ab6319311 feat: quick-find search for clients and pets (GRO-46)
Closes #119

- Debounced typeahead search on clients/pets pages
- Auto-select client from GlobalSearch highlight param
- Mobile-friendly with big touch targets
- 141-line test suite, all 70 web tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-27 07:16:52 +00:00
groombook-engineer[bot] 6539eb4554 feat: iCal calendar feed (GRO-107)
feat: iCal calendar feed (GRO-107)

Closes GRO-107
2026-03-27 02:37:06 +00:00
groombook-engineer[bot] e3220af9ce fix(gro-38): prod/demo auth and API-based seed (#117)
Closes GRO-38. Adds POST /api/admin/seed (manager-only, gated by SEED_KNOWN_USERS_ONLY) and separates dev vs prod seeding paths. Reviewed and approved by CTO and QA.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-26 20:51:08 +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
groombook-engineer[bot] 553fa435ed feat: add PR preview deployment to groombook-dev (#113)
feat: add PR preview deployment to groombook-dev
2026-03-26 03:56:50 +00:00
Groom Book CTO f1235c6d3d fix: install kubectl on ARC runner for deploy-dev job
ARC self-hosted runners don't include kubectl. Download it before
running the deployment commands.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-25 11:03:38 +00:00
Groom Book CTO 2b6c437446 feat: add PR preview deployment to groombook-dev
Extend CI to build PR-tagged Docker images and auto-deploy them to
groombook-dev when all checks pass. This unblocks Flea Flicker UAT
validation for open PRs.

Changes:
- Docker build job now runs on PRs (tagged as pr-{number}) and main
- New deploy-dev job uses self-hosted runners with kubectl access
- Runs migration, updates api/web deployments, comments on PR

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-25 11:03:38 +00:00
groombook-engineer[bot] 4fc84ae891 chore: remove docs/ folder (migrated to groombook.github.io) (#112)
Refs GRO-195

Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-25 06:39:51 +00:00
Chris Farhood 7e782d0b7f Merge pull request #108 from groombook/docs/github-pages-marketing-site
Add marketing docs for GitHub Pages site
2026-03-24 22:03:16 -04:00
Groom Book CTO ad87183259 fix: correct double verb grammar in why-we-built.html
"you've basically have" → "you basically have" — QA item #5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 22:05:09 +00:00
Groom Book CTO 735c0f09fd fix(docs): address CTO review feedback on marketing site
- Convert mobile-groomers.html from Markdown to proper HTML
- Fix "Client/p pet profiles" typo → "Client/pet profiles"
- Add missing trailing newlines to all files
- Remove duplicate "Back to Home" link in getting-started/index.html
- Fix link paths: use relative URLs instead of /groombook/docs/ absolute paths
- Remove placeholder testimonials with fake names

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:56:16 +00:00
Groom Book CTO 6945efdbed Add marketing docs for GitHub Pages site
Add landing page, blog posts (launch announcement, why we built),
getting-started guide, mobile groomers page, and Jekyll config
to docs/ folder for GitHub Pages publishing.

Closes groombook/groombook#168

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:41:44 +00:00
groombook-ceo[bot] 0c1ec6dade feat: appointment confirmation and cancellation (GH #98)
feat: appointment confirmation and cancellation (GH #98)
2026-03-24 21:15:07 +00:00
Scrubs McBarkley f3923ddf54 fix(email): remove unused baseUrl variable in buildReminderEmail
Fixes ESLint @typescript-eslint/no-unused-vars error in CI.
baseUrl was declared but never used; confirmUrl/cancelUrl correctly
use apiUrl instead.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 17:25:09 +00:00
Scrubs McBarkley d1ab91adfa feat: appointment confirmation and cancellation (GH #98, GRO-153)
Add customer confirmation/cancellation flow for appointments:

- DB migration (0013): add confirmation_status, confirmed_at, cancelled_at,
  confirmation_token to appointments table with index on token column
- schema.ts + factories.ts + types: expose new columns and ConfirmationStatus type
- GET /api/book/confirm/:token — tokenized confirm via email link (redirects)
- GET /api/book/cancel/:token — tokenized cancel via email link, single-use token
- POST /api/appointments/:id/confirm — portal/staff confirm endpoint
- POST /api/appointments/:id/cancel — portal/staff cancel endpoint
- Reminder emails now include Confirm/Cancel CTA buttons with tokenized links
- Reminder service generates confirmation token if missing before sending
- Staff calendar shows confirmation status indicator on appointment cards
  and in the detail modal (confirmed ✓ / customer cancelled ✗)
- /booking/confirmed, /booking/cancelled, /booking/error redirect pages
- 23 new unit tests covering all new endpoints and edge cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:02:58 +00:00
groombook-ceo[bot] 75d0e4c3e6 feat: pet photo upload via presigned S3 URLs (GH #93)
feat: pet photo upload via presigned S3 URLs (GH #93)
2026-03-22 19:45:41 +00:00
groombook-ceo[bot] 020e758916 Merge branch 'main' into feat/pet-photo-upload-gh93 2026-03-22 19:45:35 +00:00
groombook-ceo[bot] 86cc5829e4 test(e2e): add login and impersonation test coverage (GRO-77)
test(e2e): add login and impersonation test coverage (GRO-77)
2026-03-22 15:43:00 +00:00
groombook-ceo[bot] e9c8bff784 Merge branch 'main' into feat/e2e-login-impersonation-gro-77 2026-03-22 15:42:45 +00:00
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
Lint Roller b3514626a1 fix(e2e): fix test failures after CTO review
- Scope STAFF VIEW locator to impersonation-banner testid
- Fix loading state test: unroute before setting delayed handler

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 11:42:32 +00:00
Lint Roller 355f11fdaa fix(e2e): address CTO review feedback on PR #101
- 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>
2026-03-22 11:36:07 +00:00
groombook-ceo[bot] 871830183a Merge pull request #103 from groombook/feat/quick-find-search-gh97
feat: quick-find search for clients and pets (GH #97)
2026-03-22 08:22:46 +00:00
groombook-ceo[bot] 6ca09d739b Merge branch 'main' into feat/quick-find-search-gh97 2026-03-22 08:20:02 +00:00
groombook-ceo[bot] a732a3c47b Merge pull request #100 from groombook/feat/site-theming-unification-gh91
feat: unify site theming via CSS custom properties (GH #91)
2026-03-22 04:17:30 +00:00
Scrubs McBarkley 0c182da366 fix: address CTO review feedback on quick-find search (GH #97, GRO-134)
- Remove unused makeSelectChain function from search.test.ts (lint blocker)
- Fix handleClientClick/handlePetClick to navigate to /admin/clients?highlight={id}
  so the target client is identified in the URL rather than silently ignored
- Add console.warn for fetch errors in GlobalSearch instead of swallowing silently

Auth middleware verified: searchRouter is registered on the api Hono instance
which applies authMiddleware + resolveStaffMiddleware globally — no coverage gap.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 04:10:54 +00:00
Scrubs McBarkley c826f65bd6 feat: quick-find search for clients and pets (GH #97, GRO-140)
Backend:
- GET /api/search?q={query} — returns up to 10 matching active clients and 10
  matching pets in a single request; clients matched on name/email/phone,
  pets matched on name/breed with owner name included
- Special chars (%, _, \) escaped before ILIKE to prevent injection/accidents
- Disabled clients excluded; pets from disabled client owners excluded via JOIN filter
- Route registered under protected API (auth + RBAC middleware applies automatically)
- Export `ilike` from @groombook/db alongside existing drizzle-orm helpers

Frontend:
- GlobalSearch component in sticky admin header: debounced input (300ms),
  grouped dropdown (Clients / Pets sections), loading/empty states
- Client results show name + phone; pet results show name, breed, owner name
- Touch-friendly: 44px input height, 48px min row height, full-width dropdown
- Outside-click closes dropdown; selecting a result navigates to /admin/clients

Tests (apps/api/src/__tests__/search.test.ts):
- 400 on missing/empty/whitespace q
- Returns matching clients and pets
- Empty arrays on no match
- Response shape always has clients/pets keys
- Special character inputs handled without errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:16:28 +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 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] 4060d5b515 Merge branch 'main' into feat/site-theming-unification-gh91 2026-03-22 00:05:08 +00:00
groombook-ceo[bot] c625104bfd Merge pull request #99 from groombook/feat/factory-unit-tests-gh94
test(db): add unit tests for test factories
2026-03-22 00:04:38 +00:00
groombook-ceo[bot] 83ee230975 Merge branch 'main' into feat/factory-unit-tests-gh94 2026-03-22 00:01:43 +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
Scrubs McBarkley 891cc39ae1 fix: remove stale vitest entry from packages/db lockfile
vitest was erroneously added to the packages/db importer in
pnpm-lock.yaml during factory test setup, but packages/db/package.json
does not declare vitest as a dependency. This caused CI to fail with
ERR_PNPM_OUTDATED_LOCKFILE.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:47:15 +00:00
Lint Roller a466053000 E2E tests: add login and impersonation test coverage (GRO-77)
- apps/e2e/tests/login.spec.ts: 8 tests for DevLoginSelector page
  - renders staff and clients sections
  - shows loading state
  - displays staff with role/email, clients with pet count
  - clicking staff navigates to /admin with dev-user stored
  - clicking client navigates to / with dev-user stored
  - skip login removes dev-user and navigates to /admin
  - handles empty users response

- apps/e2e/tests/impersonation.spec.ts: 8 tests for ImpersonationBanner
  - banner displays when session is active
  - shows reason and started time
  - End Session and Audit buttons visible
  - clicking End Session calls API and hides banner
  - Extend button appears when time < 5 mins and not extended
  - URL is cleaned when session ends

- apps/e2e/tests/fixtures.ts: added /api/dev/users mock for login tests
2026-03-21 23:47:01 +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 1f50fdff54 test(db): add unit tests for test factories (GitHub #94)
Tests cover resetFactoryCounters(), counter determinism, override
merging, and compile-time enforcement of required fields on
buildAppointment. All 16 new tests pass (92 total).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:43:47 +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