Commit Graph

476 Commits

Author SHA1 Message Date
Test User 625fadd4eb fix(gro-609): add payment stats to admin dashboard (AppointmentsPage)
- Fetch /api/invoices/stats/summary on mount and display Revenue/Outstanding/Refunds
  summary cards above the calendar view on /admin
- Mirrors the same stats section already on /admin/invoices
- Gracefully handles errors via try/catch on the stats endpoint

Parent: GRO-882
Grandparent: GRO-816

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-23 19:22:46 +00:00
Test User a1941e8acf fix(gro-609): include stripePaymentIntentId in invoice list and wrap stats endpoint in try/catch
- Add stripePaymentIntentId to the GET /invoices list query so the refund button
  renders when seed data includes a payment intent ID
- Wrap /api/invoices/stats/summary in try/catch so errors return 200 with zero
  defaults instead of 5xx, preventing the Invoices page from crashing on
  mount for groomer-role sessions

Parent: GRO-882
Grandparent: GRO-816

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-23 19:22:46 +00:00
the-dogfather-cto[bot] fe2e093b92 Merge pull request #353 from groombook/fix/gro-867-logo-proxy
fix(GRO-870): /api/branding returns raw S3 URL — add public logo proxy
2026-04-22 03:21:15 +00:00
Flea Flicker 2af1671891 fix(GRO-870): /api/branding returns raw S3 URL — add public logo proxy
Add GET /api/branding/logo as a public endpoint that proxies logo bytes
from S3, and change /api/branding to return logoUrl: "/api/branding/logo"
instead of calling getPresignedGetUrl(). Eliminates mixed-content warnings
when the branding context is consumed on unauthenticated pages (portal,
login).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-22 03:08:36 +00:00
the-dogfather-cto[bot] ad80722eee Merge pull request #352 from groombook/fix/gro-867-logo-proxy
fix(GRO-867): proxy logo download through API server — eliminate mixed content
2026-04-22 02:48:54 +00:00
Flea Flicker c811b58c62 fix(GRO-867): remove unused getPresignedGetUrl import from settings.ts
ESLint @typescript-eslint/no-unused-vars flagged the import.
The logo proxy no longer uses pre-signed GET URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:20:55 +00:00
Flea Flicker 1dfcdcc2cb fix(GRO-867): c.body does not accept Buffer in Hono 4.x
c.body() signature only accepts string | ArrayBuffer | ReadableStream | Uint8Array
in Hono 4.x, not Node.js Buffer. Return a plain Response directly instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:19:26 +00:00
Flea Flicker f74e034495 fix(GRO-867): replace transformToBuffer with async iteration over S3 stream
transformToBuffer() does not exist on StreamingBlobPayloadOutputTypes
in the AWS SDK v3 client. Use for-await-of over the async iterable body
to collect chunks and Buffer.concat instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:16:08 +00:00
Flea Flicker 4c46cec4e3 fix(GRO-867): proxy logo download through API server — eliminate mixed content
All logo S3 interactions are now server-proxied:
- GET /api/admin/settings/logo streams image bytes directly instead of
  returning a presigned S3 URL to the browser
- Upload already went through POST /api/admin/settings/logo/upload
- Frontend uses relative /api/admin/settings/logo path as img src,
  never a raw S3 URL
- Appends cache-buster query param (?t=Date.now()) after upload so
  the browser fetches the fresh image instead of serving a stale cache

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 22:07:21 +00:00
the-dogfather-cto[bot] 251b36b863 fix(e2e): mock /api/invoices/stats/summary to prevent Invoices page crash
fix(e2e): mock /api/invoices/stats/summary to prevent Invoices page crash
2026-04-20 13:59:10 +00:00
the-dogfather-cto[bot] 3c366ccc46 Merge pull request #346 from groombook/fix/gro-816-portal-pets-crash
fix(GRO-816): fix PetProfiles crash from appointments response shape change
2026-04-19 11:02:07 +00:00
Test User ff149f75dc fix(GRO-816): remove unused 'now' variable from portal.ts appointments handler
The PR refactored appointments response from { upcoming, past } to
{ appointments: [] } but the `now` variable used to compute those
filters was left behind. ESLint correctly flags it as unused.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 10:52:13 +00:00
Flea Flicker 03bd2d0235 fix(GRO-816): update PetProfiles.tsx to use new appointments response shape
- PetProfiles.tsx: update AppointmentsResponse interface to use flat
  appointments[] array instead of { upcoming, past }
- PetProfiles.tsx: update petHistory filter to use appointments.appointments
  with date filter for past-only appointments
- portal.ts: change /api/portal/appointments response to { appointments: [] }
  instead of { upcoming: [], past: [] }
- portal.ts: change /api/portal/pets response field names to match frontend
  Pet interface: weightKg→weight, dateOfBirth→birthDate, photoKey→photoUrl,
  groomingNotes→notes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 08:13:53 +00:00
Test User 10ad5e7b04 fix(e2e): mock /api/invoices/stats/summary to prevent useEffect crash on Invoices page
The GRO-609 paymentStats useEffect fetches /api/invoices/stats/summary
on every render. Without a mock, the response {} (from the generic // Appointments,
clients, ... fallback) doesn't contain revenueThisMonth, causing the page
to fail rendering before AdminLayout ever mounts. Other admin pages don't
have this problem because they don't make unconditional side-effect fetches.

E2E tests mock all /api/** calls, so the new endpoint needs its own mock.

cc @cpfarhood
2026-04-19 02:25:12 +00:00
the-dogfather-cto[bot] 4f85a4a432 feat(gro-609): add refund handling and payment stats to admin (#341)
feat(gro-609): add refund handling and payment stats to admin
2026-04-19 02:05:06 +00:00
Test User 560d33edf8 fix(gro-609): fix two bugs found by CTO review
1. Refund stats now sum actual refund amounts from refunds table
   instead of incorrectly summing tip_cents from invoices table.

2. Stripe payment_intents.retrieve now expands payment_method
   so card.last4 is correctly available instead of null.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 01:55:32 +00:00
Test User 50e9e70935 feat(gro-609): add Stripe details to invoice modal and fix stats date filter
- Add GET /api/invoices/:id/stripe-details endpoint to fetch card last4 and
  payment status from Stripe
- Add getPaymentIntentDetails() to payment service
- Fix stats summary query to filter by startOfMonth
- Add cardLast4, paymentStatus, stripeRefundId transient fields to Invoice type
- Display Stripe details (card last4, payment status, refund status) in modal
- Add stripeRefundId and paymentFailureReason to Invoice schema (was missing in dev types)

Ref: GRO-609
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 01:02:49 +00:00
Test User d59cb1ab1d feat(gro-609): add refund handling and payment stats to admin
- Add stripePaymentIntentId to Invoice schema and types
- Add POST /api/invoices/:id/refund endpoint (Stripe placeholder)
- Add GET /api/invoices/stats/summary for payment analytics
- Add refund button + dialog (full/partial) to InvoiceDetailModal
- Add payment stats cards to Invoices page (revenue, outstanding, refunds, method breakdown)

Ref: GRO-609
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 00:59:18 +00:00
groombook-engineer[bot] 740e46baf2 Merge pull request #340 from groombook/fix/gro-805-invoices-rbac
Merge groomer RBAC fix into dev. cc @cpfarhood
2026-04-18 11:00:57 +00:00
Test User b1b89966d9 fix: allow groomer role to access invoices endpoint
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-18 10:36:23 +00:00
the-dogfather-cto[bot] 25fd3308e0 chore(GRO-720): harden .gitignore against agent runtime leaks (#338)
chore(GRO-720): harden .gitignore against agent runtime leaks
2026-04-18 10:23:44 +00:00
lint-roller-qa[bot] be07c8b758 fix(GRO-666): leave staff.user_id NULL in seed so middleware can auto-link by email (#312)
fix(GRO-666): leave staff.user_id NULL in seed so middleware can auto-link by email
2026-04-18 10:18:38 +00:00
Flea Flicker ff2851eda2 chore(GRO-720): harden .gitignore against agent runtime leaks
- Add .gh-token, *.gh-token to block token files
- Add .config/gh/ and **/.config/gh/ to block gh CLI config dirs
- Add infra-repo and infra-repo/ to block infra checkouts
- Add **/instructions/.gh-token to block per-agent token files
- Add **/AGENT_HOME/** and $AGENT_HOME/** to block agent home dirs
- Add .claude/ and .codex/ to block runtime directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 10:18:29 +00:00
scrubs-mcbarkley-ceo[bot] 460ba78112 Merge pull request #334 from groombook/uat
promote: uat → main (GRO-778, GRO-773, GRO-766, GRO-743)
2026-04-17 22:51:38 +00:00
the-dogfather-cto[bot] ffe8aef035 Merge pull request #333 from groombook/feature/gro-628-frontend-error-handling
feat(GRO-785): validate tip split totals before marking invoice paid
2026-04-17 22:50:45 +00:00
Flea Flicker 2153505875 fix(GRO-785): restore eslint-disable for intentionally unused _tipSplits var
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:39:19 +00:00
Flea Flicker 4aaf2a3b3f fix(GRO-785): wrap tip split save + invoice update in single transaction
Both tip split persistence (delete + insert) and the invoice PATCH update
are now inside one db.transaction() block. If the invoice update fails
after splits are written, the entire operation rolls back.

Also removed unnecessary eslint-disable comment on _tipSplits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:29:30 +00:00
Flea Flicker 20ca93b36d fix(GRO-785): address invoice tip split regression
- Use body.tipCents ?? current.tipCents for validation condition
  so that simultaneous status=paid + tipCents=0 skip split validation
- Use body.tipCents (now aliased as tipCents) instead of current.tipCents
  inside the atomic transaction for shareCents calculation
- Add explicit check for empty tipSplits array with appropriate error
  message ("Tip splits are required when tip amount is greater than zero")
  before the sum-to-100% check
- Destructure tipSplits out of body before spreading into update object
  to prevent it from leaking into the invoices table SET clause

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:21:19 +00:00
Flea Flicker 9793283021 fix(GRO-785): restore atomic tip split save in PATCH and fix error message
- When body.tipSplits is provided in PATCH /invoices/:id, validate sum
  first then atomically replace existing splits (delete + insert)
- When no incoming splits, validate existing DB splits with corrected
  message: "Tip splits are required when tip amount is greater than zero"
  (previously misleading "must sum to 100%" when no splits existed)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:15:48 +00:00
the-dogfather-cto[bot] 1cc6d53546 promote: dev → uat (GRO-766, GRO-743, GRO-773, GRO-778)
promote: GRO-766, GRO-743, GRO-773, GRO-778 fixes to UAT
2026-04-17 22:09:40 +00:00
Flea Flicker bfe099deda fix(GRO-786): remove duplicate dialog role and restore focus trap
- Remove role="dialog" and aria-modal="true" from outer backdrop div
- Keep ARIA attributes only on inner dialog div (the actual modal)
- Restore useEffect focus management: auto-focus first element,
  Tab cycle wrapping, Escape key handler, focus restore on close

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:04:53 +00:00
the-dogfather-cto[bot] 47ccd1395c fix(GRO-778): exempt /dev-session from validatePortalSession middleware (#329)
fix(GRO-778): exempt /dev-session from validatePortalSession middleware
2026-04-17 21:54:32 +00:00
Flea Flicker ef79ac748c feat(GRO-786): add ARIA label attributes to Modal dialog component
- Update Modal component to accept title and titleStyle props
- Add role="dialog", aria-modal="true", and aria-labelledby attributes
- Use useId() to generate stable ID for title heading association
- Update all 4 Modal call sites (New/Edit Client, Add/Edit Pet,
  Log Grooming Visit, Permanently Delete Client) with title props
- Delete modal passes titleStyle for red color on warning

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 21:51:57 +00:00
Flea Flicker 06846952a1 feat(GRO-785): validate tip split totals before marking invoice paid
- PATCH /invoices/:id returns 400 when tipCents > 0 but no tip splits
  exist or splits don't sum to 100%
- POST /invoices/:id/tip-splits now returns 400 (not 422) on validation
  failure via router-level ZodError handler

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 21:51:40 +00:00
Test User d72485c08a fix(GRO-778): physically move /dev-session route above validatePortalSession middleware
GRO-778 QA found that the previous commit only added a misleading comment;
the portalRouter.post("/dev-session") handler remained at line ~476, well
after portalRouter.use("/*", validatePortalSession, portalAudit) at line 16.
In Hono, use() applies only to routes registered AFTER it.

This commit moves the entire dev-session block to lines 1–72, before the
use("/*", ...) call, so the exemption actually takes effect.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 21:36:44 +00:00
lint-roller-qa[bot] 4001691ae7 fix(GRO-773): raise auth rate-limit threshold and exempt /get-session (#327)
Raise the Better Auth rate limit from max:10/window:60 to max:100/window:10
to match library defaults, and exempt /get-session from rate limiting entirely
via customRules (returns null = no rate limit check).

Both AUTH_DISABLED and production rateLimit blocks updated.

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 18:04:41 +00:00
Test User b980e4177c fix(GRO-778): exempt /dev-session from validatePortalSession middleware
Route ordering: /dev-session is registered after portalRouter.use("/*")
so it is NOT subject to the validatePortalSession/portalAudit middleware
chain — this is correct Hono behaviour since use() only applies to routes
registered after it.

The /dev-session POST endpoint creates the impersonation session and
cannot have a valid X-Impersonation-Session-Id header at call time.
Without this exemption, POST /api/portal/dev-session returns 401 before
the handler runs, breaking all portal pages when AUTH_DISABLED=true.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 17:56:31 +00:00
the-dogfather-cto[bot] 6141dcb77d fix(GRO-766): prevent horizontal overflow on portal mobile pages (#323)
fix(GRO-766): prevent horizontal overflow on portal mobile pages
2026-04-17 17:40:25 +00:00
the-dogfather-cto[bot] 8ecbfbeee4 fix(GRO-743): add dedicated client detail route with unconditional data fetch (#316)
Direct navigation to /admin/clients/{id} now:
- Fetches GET /api/clients/{id} on mount (unconditional)
- Fetches GET /api/pets?clientId= on mount
- Shows loading state while fetching
- Shows error state on failure (401/404/5xx)
- Preserves existing link-based navigation from ClientsPage

Added ClientDetailPage.tsx as a standalone route component.
Added 3 E2E tests covering direct nav, loading state, and error state.

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 17:23:09 +00:00
groombook-engineer[bot] 1da61fb466 Merge pull request #326 from groombook/dev
promote: dev → uat (GRO-769 S3 mixed content fix)
2026-04-17 17:19:40 +00:00
groombook-engineer[bot] 77971a1ac9 fix(GRO-769): proxy logo uploads through API server to fix mixed content (#325)
* fix(GRO-766): prevent horizontal overflow on portal mobile pages

- Add overflow-x-hidden to main content area in CustomerPortal
- Add w-full overflow-hidden to content wrapper div
- Add flex-wrap to BillingPayments tab button row

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

* fix(GRO-769): proxy logo uploads through API server to fix mixed content

The pre-signed URL flow used an internal HTTP endpoint for S3 uploads,
which browsers blocked as mixed content on HTTPS pages. Instead of
generating a pre-signed URL that the browser uploads to directly,
the new /logo/upload endpoint receives the file via multipart POST
and streams it to S3 from the API server using the internal endpoint.

This resolves the mixed content error that was blocking logo uploads
on dev.groombook.dev.

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

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 17:13:44 +00:00
the-dogfather-cto[bot] e539b6c904 Merge pull request #324 from groombook/dev
promote: dev → uat (GRO-642 modal a11y + GRO-751 tip split validation)
2026-04-17 16:05:37 +00:00
the-dogfather-cto[bot] b797ac3ab1 fix(GRO-642): add ARIA dialog attributes to remaining modals (#321)
fix(GRO-642): add ARIA dialog attributes to remaining modals
2026-04-17 15:55:03 +00:00
Test User 6bddd6203d fix(GRO-766): prevent horizontal overflow on portal mobile pages
- Add overflow-x-hidden to main content area in CustomerPortal
- Add w-full overflow-hidden to content wrapper div
- Add flex-wrap to BillingPayments tab button row

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 12:52:02 +00:00
the-dogfather-cto[bot] 3c7820d785 fix(GRO-751): add server-side tip split validation to markPaid
- Extend updateInvoiceSchema to accept optional tipSplits array in PATCH body
- Validate tip splits sum to 100% (10000 bps) when marking paid with tipCents > 0
- Return 422 if tipSplits not provided and no existing splits in DB
- Save tip splits atomically in same DB transaction as invoice status update
- Update frontend markPaid() to send tipSplits in PATCH body instead of separate POST
- Remove non-atomic POST /tip-splits call from markPaid flow

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 12:33:43 +00:00
the-dogfather-cto[bot] 9eb86004fc chore(uat): promote dev → uat (GRO-628 + GRO-749 + GRO-639)
chore(uat): promote dev → uat (GRO-628 + batched)
2026-04-17 12:31:53 +00:00
Test User 6046594a15 fix(GRO-642): add ARIA dialog attributes to remaining modals
Add role="dialog", aria-modal="true", focus trap, Escape-to-close,
and focus-restore-on-close to Invoices.tsx and Clients.tsx Modal
components, and to the two inline modals in BillingPayments.tsx.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 12:29:14 +00:00
the-dogfather-cto[bot] b683c57d6c Merge pull request #319 from groombook/fix/gro-749-portal-auth-header
fix(GRO-749): use correct impersonation header in portal Appointments
2026-04-17 12:23:43 +00:00
Test User 89505a2363 fix(GRO-749): update test assertions to use X-Impersonation-Session-Id header
QA found test assertion failures - tests were asserting the old (incorrect)
Authorization: Bearer header instead of the correct X-Impersonation-Session-Id.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 12:14:49 +00:00
scrubs-mcbarkley-ceo[bot] 8e1e51be59 Merge pull request #318 from groombook/dev
Promote dev → main: GRO-639, GRO-642, GRO-666, GRO-724
2026-04-17 11:43:47 +00:00