feat: client-side navigation for auth state transitions#45
Merged
Conversation
Replaces 6 `window.location.href` calls with TanStack Router's
`useNavigate` across login, register, logout, oauth callback, profile
delete, and associate-complete. No more white flash between auth state
changes.
## The race that wasn't
`login-page.tsx:36-37` previously documented:
> Hard redirect — reloads the page so AuthProvider picks up the new
> cookie cleanly. Client-side navigate has a race condition with the
> onUnauthorized handler.
E2E testing (Playwright against real API + Postgres) shows the race
does NOT manifest with `await auth.checkAuth() + await navigate()`:
React 18's batching plus TanStack Router's context propagation (via
`<RouterProvider context={{ auth }} />` in `main.tsx:105`) means the
updated context is visible by the time `beforeLoad` runs. No
`router.invalidate()` needed.
The pattern by call site:
- **Login / register**: `await auth.checkAuth() → await navigate(...)`.
Refreshes the AuthContext with the new cookie's identity before the
destination's beforeLoad checks `isAuthenticated`.
- **Logout / delete**: `await auth.logout() → await navigate("/login")`.
Clears local state before navigating so /login's "redirect
authenticated users to /profile" guard doesn't bounce the user back.
- **OAuth-complete**: keeps a direct `api.get("auth/me")` (so the
routing decision sees current data without waiting for a render)
PLUS `await auth.checkAuth()` (so the AuthContext is fresh for the
destination route).
- **Associate-complete**: pure in-app nav with no auth state change.
## Preserved hard-nav cases
- Cross-origin redirects (consumer apps like hera-streamer-…) still use
`window.location.href` — useNavigate is for in-app routes only.
- Outbound to provider OAuth (Google / Steam authorize) keeps direct
navigation (CSP + the dev/prod JSON-vs-302 split, fixed in #44).
## Test plan
- [x] `pnpm test:run` — 45 tests pass.
- [x] `pnpm exec tsc -b` clean.
- [x] Live E2E: register → /profile, logout → /login, login → /profile,
delete → /login. No bounce, no white flash. Backend log confirms
request chain (register/login/accept-tos/me/connections; logout;
delete/logout).
Closes #43.
✅ Deploy Preview for criticalbit-auth-web ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #43. Replaces 6 `window.location.href` calls with TanStack Router's `useNavigate` across login, register, logout, OAuth callback, profile delete, and associate-complete. No more white flash between auth state changes.
The race that wasn't
`login-page.tsx:36-37` previously documented:
Live E2E testing shows the race does not manifest with `await auth.checkAuth() + await navigate()`. React 18's batching plus TanStack Router's context propagation (via `<RouterProvider context={{ auth }} />` in `main.tsx:105`) means the updated context is visible by the time `beforeLoad` runs. No `router.invalidate()` needed. The previous comment was likely about pre-React-18 batching or a different flow.
The pattern by call site
Preserved hard-nav cases
Test plan
Unit
Live E2E (Playwright against local API + Postgres on non-default ports)
Not covered E2E
OAuth callback complete paths (Google / Steam). Would need real OAuth client creds in the local API; the conversion mirrors login (await checkAuth + await navigate) which is verified.
Reviewer checklist