fix(GRO-2099): show loading state during CustomerPortal SSO bridge bootstrap #44

Merged
Scrubs McBarkley merged 1 commits from flea/gro-2099-fix-authed-portal-nav into dev 2026-06-02 16:38:03 +00:00
Owner

Root cause

Dashboard.tsx:194 runs !sessionId && !isImpersonating && !getDevUser() and redirects to /login if sessionId is null. For SSO customers, the CustomerPortal's useEffect has to call /api/auth/get-session then /api/portal/session-from-auth to populate portalSessionId. During that 100-300ms bootstrap window, sessionId is null and the Dashboard's guard fires — redirecting the user to /login. App.tsx additionally returned null at /login for authenticated users (showCustomerPortal is false at /login), leaving a blank React root even when the redirect target was /login itself.

Reproduction (verified on UAT image 2026.06.01-ec29f71)

  1. From about:blank, complete customer SSO as uat-customer.
  2. browser_navigate to /portal.
  3. Pre-fix: redirected to /login, React root innerHTML.length === 0.
  4. Post-fix: URL stays at /portal, dashboard renders with customer name.

Fix

  • CustomerPortal.tsx: show a role="status" 'Loading…' state while !initComplete. The portal chrome and its child sections only mount once the bootstrap has resolved.
  • App.tsx: at /login with a valid session, redirect to /. The !authDisabled && !session early-return now only fires at /login, so other portal routes defer the auth check to CustomerPortal (where the SSO bridge runs).
  • UAT_PLAYBOOK.md: add §5.27 with 8 cases covering the bug, the loading state, the /login auto-redirect, the unauth fallback, and the groomer / impersonation non-regressions.
  • src/__tests__/portal.test.tsx: regression test that asserts the loading state is shown during the bridge and the portal nav is NOT in the DOM mid-bootstrap.

Verification

  • pnpm typecheck: clean.
  • pnpm test: 137/137 pass (1 new GRO-2099 regression test added).
  • Manual UAT verification pending: see GRO-2099.

Refs: GRO-2099, GRO-1859, GRO-2026, GRO-1867.

## Root cause `Dashboard.tsx:194` runs `!sessionId && !isImpersonating && !getDevUser()` and redirects to `/login` if `sessionId` is null. For SSO customers, the CustomerPortal's useEffect has to call `/api/auth/get-session` then `/api/portal/session-from-auth` to populate `portalSessionId`. During that 100-300ms bootstrap window, `sessionId` is null and the Dashboard's guard fires — redirecting the user to `/login`. `App.tsx` additionally returned `null` at `/login` for authenticated users (`showCustomerPortal` is false at `/login`), leaving a blank React root even when the redirect target was /login itself. ## Reproduction (verified on UAT image `2026.06.01-ec29f71`) 1. From `about:blank`, complete customer SSO as `uat-customer`. 2. `browser_navigate` to `/portal`. 3. **Pre-fix:** redirected to `/login`, React root `innerHTML.length === 0`. 4. **Post-fix:** URL stays at `/portal`, dashboard renders with customer name. ## Fix - `CustomerPortal.tsx`: show a `role="status"` 'Loading…' state while `!initComplete`. The portal chrome and its child sections only mount once the bootstrap has resolved. - `App.tsx`: at `/login` with a valid session, redirect to `/`. The `!authDisabled && !session` early-return now only fires at `/login`, so other portal routes defer the auth check to `CustomerPortal` (where the SSO bridge runs). - `UAT_PLAYBOOK.md`: add §5.27 with 8 cases covering the bug, the loading state, the /login auto-redirect, the unauth fallback, and the groomer / impersonation non-regressions. - `src/__tests__/portal.test.tsx`: regression test that asserts the loading state is shown during the bridge and the portal nav is NOT in the DOM mid-bootstrap. ## Verification - `pnpm typecheck`: clean. - `pnpm test`: 137/137 pass (1 new GRO-2099 regression test added). - Manual UAT verification pending: see GRO-2099. Refs: GRO-2099, GRO-1859, GRO-2026, GRO-1867.
Scrubs McBarkley added 1 commit 2026-06-02 16:35:43 +00:00
fix(GRO-2099): show loading state during CustomerPortal SSO bridge bootstrap
CI / Test (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Successful in 29s
CI / Build & Push Docker Image (pull_request) Successful in 45s
f1cf58dc56
Root cause: `Dashboard.tsx:194` runs its own `!sessionId && !isImpersonating &&
!getDevUser()` auth guard, redirecting to `/login` if `sessionId` is null. For
SSO customers, the CustomerPortal's useEffect has to call `/api/auth/get-session`
and then `/api/portal/session-from-auth` to populate `portalSessionId`. During
that bootstrap window (typically 100-300ms), `sessionId` is null and the guard
fires — redirecting the user to `/login` and breaking the post-sign-in flow.
App.tsx additionally returned `null` at `/login` for authenticated users
(`showCustomerPortal` is false at `/login`), leaving a blank React root even
if the redirect target was /login itself.

Fix:
- `CustomerPortal.tsx`: show a 'Loading…' state (`role=status`) while
  `!initComplete`. The portal chrome and its child sections only mount once
  the bootstrap has resolved, so child auth guards don't fire prematurely.
- `App.tsx`: at `/login` with a valid session, redirect to `/` so the
  customer lands on the portal instead of seeing a blank page.
- `App.tsx`: only return `LoginPage` when at `/login` — other portal
  routes defer the auth check to `CustomerPortal` (the customer SSO bridge
  resolves `portalSessionId` on mount).
- `UAT_PLAYBOOK.md`: add §5.27 with 8 cases covering the bug, the loading
  state, the /login auto-redirect, the unauth fallback, and the groomer /
  impersonation non-regressions.
- `src/__tests__/portal.test.tsx`: add a regression test that asserts the
  loading state is shown during the bridge and the portal nav is NOT in the
  DOM mid-bootstrap.

Reproduction (Shedward, run b4ae0155; reproduced locally on UAT image
`2026.06.01-ec29f71`):
1. From `about:blank`, complete customer SSO as `uat-customer`.
2. `browser_navigate` to `/portal`.
3. Pre-fix: redirected to `/login` with blank React root.
4. Post-fix: URL stays at `/portal`, dashboard renders with customer name.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Scrubs McBarkley merged commit 746fad635f into dev 2026-06-02 16:38:03 +00:00
Sign in to join this conversation.