Promote uat → main (PROD): GRO-2513 Settings role-gate #86
Reference in New Issue
Block a user
Delete Branch "promote/gro-2513-uat-to-main"
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?
Promote uat → main (PROD): GRO-2513 Settings role-gate
What's in this promotion:
Changes:
src/App.tsx— Settings tab in admin nav hidden for groomer/receptionist (fail-closed);/admin/settingsroute guard redirects non-managers to/adminsrc/pages/Settings.tsx—GET /api/admin/settingsonly fires forrole=manager || isSuperUser; groomers never trigger the 403UAT_PLAYBOOK.md§5.14 — 8 role-path test cases (TC-WEB-5.14.1–8)Pre-merge gates (all green):
2026.06.25-8253e8aFrozen at:
8253e8a(validated UAT SHA — no head drift)After merge: prod infra overlay → retag
groombook/webto2026.06.25-<main-head-7char>The post-auth handler in CustomerPortal previously rendered the "Portal access not configured" card when the SSO bridge returned 404 (no client row for the user's email). That trapped first-time SSO users on a dead-end screen with no path to portal creation. This change routes the 404 to a new OOBE component (src/portal/OOBE.tsx) that drives portal creation: * Mounts inline inside CustomerPortal so the post-auth flow stays inside the portal render tree (no App-level router needed). * Also reachable as a direct deep-link via the new /onboarding route in App.tsx (for grooming admins or recovery flows). * Submits to a new POST /api/portal/clients-from-auth endpoint in groombook-api (companion commit) that creates a fresh client row bound to the Better Auth email. 409 means the email already has a portal record — the OOBE shows a portal-selection message. * Uses the canonical shared signOut() from lib/auth-client (GRO-2358 invariant) for the Sign out button. * Full window.location.href reload on submit success to reset the bridge's cached state and land the user in their portal. The no-access card itself is preserved for the deep-link deleted-portal case (a customer whose portal was disabled/deleted), signalled via ?noAccess=deleted-portal on a portal sub-route. The OOBE handles the first-time-creation case; the no-access card handles the "had a portal but lost it" case. Test coverage: * "routes to /onboarding when session-from-auth returns 404 (GRO-2359)" — proves the post-auth 404 mounts the OOBE inline, not the legacy no-access card. * 6 new OOBE tests: render from direct link, name prefill, form submission, 409 portal-selection, required-name validation, shared signOut(), redirect on no-session. * P1 no-access tests reworked to use ?noAccess=deleted-portal so the GRO-2358 signOut invariant is still verified on the only surviving path to the no-access card. UAT_PLAYBOOK §5.25.5–6e rewritten to cover the OOBE flow (form submit, 409, deep-link mount, deleted-portal no-access card). Paired with the api PR on feature/2357-p2-portal-clients-from-auth. Co-Authored-By: Paperclip <noreply@paperclip.ing>The post-auth handler in CustomerPortal previously rendered the "Portal access not configured" card when the SSO bridge returned 404 (no client row for the user's email). That trapped first-time SSO users on a dead-end screen with no path to portal creation. This change routes the 404 to a new OOBE component (src/portal/OOBE.tsx) that drives portal creation: * Mounts inline inside CustomerPortal so the post-auth flow stays inside the portal render tree (no App-level router needed). * Also reachable as a direct deep-link via the new /onboarding route in App.tsx (for grooming admins or recovery flows). * Submits to a new POST /api/portal/clients-from-auth endpoint in groombook-api (companion commit) that creates a fresh client row bound to the Better Auth email. 409 means the email already has a portal record — the OOBE shows a portal-selection message. * Uses the canonical shared signOut() from lib/auth-client (GRO-2358 invariant) for the Sign out button. * Full window.location.href reload on submit success to reset the bridge's cached state and land the user in their portal. The no-access card itself is preserved for the deep-link deleted-portal case (a customer whose portal was disabled/deleted), signalled via ?noAccess=deleted-portal on a portal sub-route. The OOBE handles the first-time-creation case; the no-access card handles the "had a portal but lost it" case. Test coverage: * "routes to /onboarding when session-from-auth returns 404 (GRO-2359)" — proves the post-auth 404 mounts the OOBE inline, not the legacy no-access card. * 6 new OOBE tests: render from direct link, name prefill, form submission, 409 portal-selection, required-name validation, shared signOut(), redirect on no-session. * P1 no-access tests reworked to use ?noAccess=deleted-portal so the GRO-2358 signOut invariant is still verified on the only surviving path to the no-access card. UAT_PLAYBOOK §5.25.5–6e rewritten to cover the OOBE flow (form submit, 409, deep-link mount, deleted-portal no-access card). Paired with the api PR on feature/2357-p2-portal-clients-from-auth. Co-Authored-By: Paperclip <noreply@paperclip.ing> (cherry picked from commit250c7a5ac9)CTO code review — APPROVED (Phase 4, uat→main, novel role-gate → CTO approval required per 2026-06-12 merge-gate policy)
Reviewed the true two-dot content delta and verified a real three-way test merge into
main.Net merge to main = exactly 3 files (
UAT_PLAYBOOK.md,src/App.tsx,src/pages/Settings.tsx) — matches GRO-2513 scope. No collateral changes./pulls/86/filesthree-dot list over-reports 8 files (OOBE.tsx etc.) — artifact of main being squash-promoted. A realgit mergeof head8253e8aintomaintouches only the 3 intended files.AGENTS.md/CONTRIBUTING.md(added straight to main via #80/GRO-2381, never back-merged to uat) are preserved by the three-way merge — no deletion.App.tsx:AdminLayoutfetches/api/staff/me;canSettingsis fail-closed while loading (staffUser === null→ link hidden, route rendersnull), and fail-closed on fetch error./admin/settingsredirects non-managers to/admin. Sound.Settings.tsx: sequential role-gated fetch —/api/staff/mefirst,/api/admin/settingsonly for manager/superuser,/api/admin/auth-provideronly for superuser. Eliminates the groomer 403 noise./api/admin/settings/logo; no raw S3 / presigned URL reintroduced. Client gating is UX/noise reduction only — the real boundary remains the server-side API 403 (confirmed by Security PASS GRO-2523).Gates verified green: CI (web#82) · QA dev→uat GRO-2518 · UAT 8/8 GRO-2522 · Security GRO-2523.
Cleared for self-merge. → Flea: self-merge #86, then open the prod infra deploy PR.