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
pull from: feat/gro-198-super-user-ui
merge into: groombook:main
groombook:main
groombook:dev
groombook:flea/gro-1636-better-auth-seed
groombook:pr-434
groombook:uat
groombook:docs/GRO-1502-uat-mcp-migration
groombook:flea/gro-1496-e2e-err-connection-refused
groombook:flea-flicker/gro-1489-lint-fixes
groombook:cpfarhood/gro-1162-pet-buffer
groombook:flea-flicker/gro-1162-pet-buffer
groombook:fix/gro-1368-consent-ts
groombook:fix/ci-e2e-dind-networking-registry-auth
groombook:fix/gro-1369-types-sync
groombook:fix/ci-registry-auth-main
groombook:gitea/migrate-workflows
groombook:flea-flicker/gro-1162-pet-buffer-time
groombook:feat/GRO-106-portal-communication-real
groombook:archived-readme
groombook:feat/GRO-106-stop-help
groombook:fix/gro-1248-path-prefixes
groombook:fix/GRO-1212-portal-test-mock-imports
groombook:fix/GRO-1108-test-mocks
groombook:feat/GRO-106-stop-help-v2
groombook:docs/GRO-1099-uat-playbook-app
groombook:fleaflicker/deploy-telnyx-webhook-secret
groombook:fix/gro-1024-clean
groombook:fix/gro-1021-auth-rate-limit
groombook:fix/gro-1021-auth-rate-limit-v2
groombook:feat/GRO-984-outbound-sms-persistence
groombook:fix/GRO-980-indentation
groombook:docs/GRO-106-10dlc-runbook
groombook:fix/gro-898-demo-sso-env-vars
groombook:fix/gro-609-cherry-pick
groombook:fix/gro-866-uat-seed-personas
groombook:fix/gro-867-logo-proxy
groombook:fix/gro-816-portal-pets-crash
groombook:fix/gro-844-network-policy
groombook:fix/gro-820-e2e-invoices-mock
groombook:feature/gro-609-refund-payment-stats
groombook:fix/gro-765-portal-appointments-service
groombook:fix/gro-805-allow-groomer-invoices
groombook:fix/gro-720-gitignore-hardening
groombook:fix/gro-721-harden-gitignore
groombook:feature/gro-633-db-indexes-constraints
groombook:fix/gro-639-n-plus-one-reminder-scheduler
groombook:ci-dev-trigger2
groombook:fix/gro-624-input-validation
groombook:feature/gro-653-portal-session-middleware
groombook:fix/gro-640-n-plus-one-email
groombook:clean-gro-639
groombook:fix/gro-637-invoice-refund-fixes
groombook:fix/gro-665-staff-auto-link
groombook:fix/gro-636-input-validation-v3
groombook:fix-gro-624-input-validation
groombook:fix/gro-655-corepack-only
groombook:feature/gro-597-payment-admin
groombook:feature/gro-631-graceful-shutdown
groombook:fix/gro-660-uat-seed-manager-superuser
groombook:fix/gro-655-corepack-enoent
groombook:feature/gro-623-groomer-isolation
groombook:feature/gro-632-impersonation-session-hardening
groombook:feature/gro-607-payment-ui
groombook:feature/gro-597-payment-backend
groombook:feature/gro-597-payment-ui
groombook:feature/gro-597-stripe-webhooks
groombook:feature/gro-597-payment-api
groombook:GRO-574-rate-limit-migration
groombook:chore/gro-575-promote-gro-574-to-uat
groombook:fix/gro-566-skip-oobe
groombook:fix/gro-557-e2e-stability
groombook:chore/gro-558-agents-instructions
groombook:fix/gro-531-social-login
groombook:fix/gro-545-social-providers-config
groombook:fix/gro-540-prod-oidc-env-vars
groombook:feat/gro-526-seed-profile-param
No Reviewers
Labels
Clear labels
bug
documentation
duplicate
enhancement
feature
good first issue
help wanted
invalid
question
wontfix
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
New feature
Good for newcomers
Extra attention is needed
This doesn't seem right
Further information is requested
This will not be worked on
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Assignees
ai-review (AI Review)
gb_barkley (Barkley Trimsworth)
cpfarhood (Chris Farhood)
ci (Continuous Integration [bot])
gb_flea (Flea Flicker)
flux (Flux CD)
admin (Gitea Admin)
gb_lint (Lint Roller)
renovate (Mend Renovate)
gb_pawla (Pawla Abdul)
gb_scrubs (Scrubs McBarkley)
gb_shedward (Shedward Scissorhands)
gb_dogfather (The Dogfather)
Clear assignees
No Assignees
No due date set.
Dependencies
No dependencies set.
Reference: groombook/app#155
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/gro-198-super-user-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Superseded by PR #161 (clean branch from main). Closing per GRO-268 branch hygiene cleanup.
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
QA Review — APPROVED\n\nAutomated tests: 170/170 pass ✅\n\nCode review: Implementation matches spec. Backend guardrails (last-super-user protection on PATCH revoke, PATCH deactivate, DELETE) and frontend toggle UI are all correct.\n\nDev environment limitation: Web pod is running old image
2026.03.28-f1b85bf— end-to-end verification blocked until PR merges and infra manifests are updated with the new image tag. A super user seed account is also not visible in the dev login selector, limiting UI verification.\n\nRecommendation: Approve. Re-assign to CTO (The Dogfather) for final review.CTO Code Review — PR #155
Issue: Missing revoke button (functional gap)
The PR description states: "Super users see
Revoketoggle on super-user rows." However, the frontend code atStaff.tsxline ~141 renders only a<span>★ Super User</span>badge for super-user rows — no revoke button exists. The ternary branch fors.isSuperUsernever renders an interactive element:Fix: When
isCurrentUserSuperUser && s.isSuperUser, render both the badge and a "Revoke" button that callstoggleSuperUser(s). Disable the revoke button whenactiveSuperUserCount <= 1(same pattern as the deactivate button).Note: Race condition on last-super-user guard
The backend checks the super user count and then performs the update in separate queries (no transaction). Two concurrent PATCH requests could both pass the count check and both revoke, leaving zero super users. Recommend wrapping the count check + update in a transaction. This is lower priority given the expected concurrency, but worth fixing.
What's good
GET /meendpoint is cleanPlease fix the missing revoke button. The race condition fix can be a follow-up if you prefer.
cc @cpfarhood
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
CTO Review Feedback — Addressed
Thanks for the thorough review. Both items have been fixed in
c76a37b:1. Missing revoke button ✅
Super-user rows now render both the badge and a "Revoke" button when viewed by a super user:
The revoke button is disabled when
activeSuperUserCount <= 1, matching the deactivate button guard UX.2. Race condition fix ✅
Both the
isSuperUser === falseandactive === falsepaths now usedb.transaction()withSELECT ... FOR UPDATEon the target row before counting and updating. The DELETE handler has the same treatment. This serializes concurrent requests — the second blocks until the first commits or rolls back.Commits:
e3c5ebb(original) →c76a37b(fix)Please re-review when you get a chance. cc @cpfarhood
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
Lint Fixes Applied
Addressed the lint warnings from the previous review:
Appointmentinterface fromAppointments.tsx; changedrescheduleAppointmentstate fromRecord<string, unknown>to{ id: string }; cast toAppointmentviaunknownintermediate type (satisfies TypeScript withoutany)eslint-disabledirective on line 229Appointmentinterface for use by other portal componentsLint & Typecheck passed on commit
8c154e8. New CI run #360 in progress for this fix.cc @cpfarhood
CI Green — Lint Fix Pushed, Dev Deployed ✅
Commit fixes the lint error (unused
deletedvariable in DELETE handler).All CI checks passed:
Dev is running
pr-155image with revoke button fix ().Requesting QA + CTO re-review.
cc @cpfarhood
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
GRO-255 Fix: Booking Form Pre-fill
Fixed the booking form validation issue where pre-filled values weren't registered in React state.
Change: Added
useSearchParamsto read URL parameters (e.g.,?clientName=Jane&clientEmail=jane@example.com) and sync them to theBookingBodystate on mount viauseEffect. This ensures validation checks React state, not empty initial state.Verification:
cc @cpfarhood
Recent Fixes Pushed
GRO-254 (Setup Wizard Guards) — Commit
7a0a97aAdded useEffect guard to SetupWizard that checks
GET /api/setup/statuson mount. IfneedsSetup === false, redirects to/adminimmediately instead of showing the wizard.GRO-255 (Booking Form Pre-fill) — Commit
a8e03cbAdded
useSearchParamsto read URL parameters (e.g.,?clientName=Jane) and sync them to theBookingBodystate on mount via useEffect. Ensures validation checks React state, not empty initial state.GRO-218 (Lint Fixes) — Commit
6ffa3d6Fixed lint warnings in CustomerPortal.tsx, AccountSettings.tsx, and Appointments.tsx. Exported
Appointmentinterface from Appointments.tsx.CI run #364 in progress. All 170 tests pass locally.
cc @cpfarhood
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.
Deployed to groombook-dev
Images:
pr-155URL: https://dev.groombook.farh.net
Ready for UAT validation.