Promote dev → uat: GRO-2373 in-portal chrome sign-out button #78
Reference in New Issue
Block a user
Delete Branch "promote/GRO-2373-dev-to-uat"
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?
Summary
Promotes the GRO-2373 fix from
devtouat.GRO-2373 is a pre-existing UAT defect discovered during the GRO-2370 regression run: the customer portal chrome (Home, Appointments, My Pets, Report Cards, Billing, Messages, Settings) had no visible sign-out control. The CMPO ruling for GRO-2355 required the logout control to be reachable from the OOBE screen, the in-portal screen, and the deep-link deleted-portal screen — GRO-2358 (P1) covered no-access + OOBE but missed the in-portal chrome.
What changes
src/portal/CustomerPortal.tsx: newSign outbutton in the sidebar footer (above the "Customer Portal v1.0" version label, alongsideEnd Impersonation). Always visible to authenticated customers. Wires to the existinghandleSignOut()→ canonicalsignOut()fromlib/auth-client(same handler as OOBE, no-access card,AdminLayout).src/__tests__/portal.test.tsx: new test rendersCustomerPortalwith a successful SSO bridge, lands on the chrome, clicksportal-chrome-signout, and asserts the sharedsignOutSpyfires +window.location.href→/login.UAT_PLAYBOOK.md: §5.25.6f added — "In-portal chrome sidebar exposes a Sign out button (GRO-2373)" with the full reachability assertion (visible on every portal sub-route, same sharedsignOut()as OOBE / no-access / AdminLayout, 200 from/api/auth/sign-out, navigates to/login, clears Authentik cookie).Test verification
Conflict resolution
The promotion merge from
devtouatproduced conflicts inUAT_PLAYBOOK.mdandsrc/__tests__/portal.test.tsx(both had additive-only changes — the GRO-2359 promote landed 5.25.6e on uat; the GRO-2373 work added 5.25.6f and the new test on dev). Both files were resolved by taking the dev side (the union of uat's prior additions plus dev's new additions), per the dev-to-uat playbook union-merge pattern (GRO-2156 #182).Why this is NOT a GRO-2359 regression
GRO-2359 (OOBE portal-creation routing) does not modify the in-portal chrome. The missing button is a pre-existing gap; the OOBE and no-access logouts are still functional. GRO-2373 is purely a chrome-layout fix.
Hand-off
🤖 Generated with Claude Code
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>QA approved: all acceptance criteria verified.
CustomerPortal.tsx — Sign out button in sidebar footer (data-testid=portal-chrome-signout), present on every sub-route. Calls the canonical signOut() from lib/auth-client then sets window.location.href to /login — same pattern as OOBE, no-access card, and AdminLayout. No raw fetch("/api/auth/sign-out").
portal.test.tsx — GRO-2373 test renders the authenticated chrome via SSO bridge, locates the button by testid, asserts signOutSpy fires once and window.location.href -> /login. All prior tests preserved.
UAT_PLAYBOOK.md — §5.25.6f added; §5.25.6a–e preserved; conflict-resolution union is correct.
CI — Lint & Typecheck: success, Test: success, Build & Push: success.