feat(staff): super user grant/revoke UI with last-user guardrail (GRO-206) #155

Closed
groombook-engineer[bot] wants to merge 7 commits from feat/gro-198-super-user-ui into main

7 Commits

Author SHA1 Message Date
groombook-ci[bot] 1bd30807fb fix(api): add error handling to /api/staff/me endpoint
Wrap c.json() in try/catch to surface serialization errors rather
than crashing silently. Also guard against null staff context.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 14:05:52 +00:00
groombook-ci[bot] 7a0a97aea3 fix(setup): add frontend guard to redirect when setup not needed
Add a useEffect in SetupWizard that checks GET /api/setup/status
on mount. If needsSetup === false, redirect to /admin immediately
instead of showing the wizard. Shows a "Checking setup status..."
loading state while the check is in flight.

Fixes GRO-254

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 12:44:45 +00:00
groombook-ci[bot] a8e03cbf4c fix(book): pre-fill form from URL params to ensure React state is set
Add useSearchParams to read URL parameters (e.g., ?clientName=Jane)
and sync them to the BookingBody state on mount via useEffect.
This ensures validation checks React state, not empty initial state.

Fixes GRO-255

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 12:39:52 +00:00
groombook-ci[bot] 6ffa3d69dd fix(portal): address lint warnings and type safety
- Export Appointment interface from Appointments.tsx for use elsewhere
- CustomerPortal: use { id: string } for reschedule state instead of
  Record<string, unknown>; cast to Appointment via unknown intermediate
- AccountSettings: remove unused eslint-disable directive

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 12:25:49 +00:00
Flea Flicker 8c154e82f4 fix(api): remove unused 'deleted' variable in staff DELETE handler
The transaction callback returns [guardError, deleted] but only
guardError is consumed. Destructure only what we need.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 12:20:37 +00:00
groombook-ci[bot] c76a37b15c fix(staff): add revoke button to super user rows + serialize guardrail in transaction
Frontend:
- Super users now see a "Revoke" button (disabled when last super user)
  alongside the ★ badge on super-user rows in the Staff table.
  Non-super-user rows show the existing "+ Grant" button.

Backend (race condition fix):
- PATCH /api/staff/:id (isSuperUser=false or active=false): count check +
  update now wrapped in a db.transaction() with FOR UPDATE lock on the
  target row, preventing a race where two concurrent revokes could both
  pass the guard and leave zero super users.
- DELETE /api/staff/🆔 same transaction + FOR UPDATE guard applied.

GRO-206 CTO review feedback

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 12:11:53 +00:00
Flea Flicker e3c5ebb337 feat(staff): super user grant/revoke UI with last-user guardrail
Backend:
- GET /api/staff/me — returns current staff record (includes isSuperUser)
- PATCH /api/staff/:id — accepts optional isSuperUser boolean; only super users
  can change this field; last-super-user guardrail prevents revoking the only
  active super user
- PATCH /api/staff/:id (active=false) — guardrail prevents deactivating the
  last active super user
- DELETE /api/staff/:id — guardrail prevents deleting the last active super user

Frontend (Staff.tsx):
- Fetches current user via GET /api/staff/me to determine isSuperUser
- Shows ★ Super User badge for super user staff rows
- Super user sees "+ Grant" button on non-super-user rows
- Super user sees "Revoke" toggle on super-user rows (disabled if last one)
- Deactivate button disabled for the last active super user
- Error message displayed when backend guardrail triggers

GRO-206

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-29 11:52:51 +00:00