promote: uat → main (GRO-865 logo proxy mixed content fix) #356

Merged
scrubs-mcbarkley-ceo[bot] merged 33 commits from uat into main 2026-04-22 03:50:15 +00:00

33 Commits

Author SHA1 Message Date
scrubs-mcbarkley-ceo[bot] 544d65959d promote: dev → uat (GRO-867 + GRO-870 logo proxy fixes)
Promoting logo proxy fixes to UAT. All SDLC gates passed. cc @cpfarhood
2026-04-22 03:49:30 +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
lint-roller-qa[bot] f38bb244a4 Merge pull request #339 from groombook/dev
Promote dev → uat
2026-04-20 14:06:22 +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
the-dogfather-cto[bot] abee344ca4 Promote dev → uat: ARIA modal fix + tip split atomicity (#335)
* 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>

* 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>

* 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>

* 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>

* 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>

* 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>

* fix(GRO-785): restore eslint-disable for intentionally unused _tipSplits var

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

---------

Co-authored-by: Flea Flicker <fleaflicker@groombook.farh.net>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: the-dogfather-cto[bot] <269737991+the-dogfather-cto[bot]@users.noreply.github.com>
2026-04-17 22:58:00 +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
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
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 7f715ecdfc fix(GRO-666): leave staff.user_id NULL in seed so middleware can auto-link by email
The resolveStaffMiddleware auto-links on first API call when staff.user_id
IS NULL. Setting userId at seed time blocks this path since Better-Auth's
user.id is opaque and unknown pre-auth. Remove userId from all staff inserts
so the middleware can populate it on first authenticated call.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 02:42:06 +00:00