Fix back navigation from My History tab to My Work tab#302
Conversation
Pressing Back from the My History tab sent authenticated translators to the Login page. Three changes, one per root cause: - Put the dashboard tab in the URL (/?tab=my-history) so each tab switch is a real history entry; Back from My History now lands on My Work. The tab no longer needs to live in the persisted zustand store. - Guard /login: an authenticated user navigating there (e.g. via Back) is redirected to a sanitized returnTo or the app home. A session-aware effect in LoginPage covers hard loads, mirroring AuthenticatedLayout. - Replace (not push) the history entry on successful login so /login no longer sits underneath the dashboard in the history stack. returnTo values are now sanitized to same-origin paths to keep the login flow from acting as an open redirect, and never point back at /login. Fixes #290
|
@coderabbitai full review |
There was a problem hiding this comment.
Pull request overview
This PR fixes an authenticated back-navigation trap where translators could land on /login (and see the login form) when navigating back from My History. It does this by making dashboard tab selection part of the URL (so tab switches create real browser history entries) and by guarding /login for authenticated users, including hard-load cases.
Changes:
- Moved translator dashboard tab state into the home route’s URL search params (
/?tab=my-history) viavalidateSearchon/_authenticated/, removing the persisted zustanduserDashboardTab. - Added a
/loginbeforeLoadredirect for authenticated users and added session-aware redirect logic inLoginPagefor hard loads; sanitizedreturnToto prevent open redirects and/loginredirect loops. - Switched post-login navigation to
window.location.replace(returnTo)to keep/loginout of the browser history stack.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/setup.ts | Adds a ResizeObserver stub for jsdom to support components that read it during tests. |
| src/store/store.ts | Removes persisted userDashboardTab state now that tabs are URL-driven. |
| src/routes/login.tsx | Guards /login with a beforeLoad redirect for authenticated users using a sanitized returnTo. |
| src/routes/_authenticated/index.tsx | Defines tab as a validated optional search param so tab switches become history entries. |
| src/features/dashboard/user/UserHomePage.tsx | Derives the active tab from useSearch and navigates by updating ?tab= rather than zustand state. |
| src/features/dashboard/user/UserHomePage.test.tsx | Adds unit coverage for URL-driven tab selection and tab-click navigation. |
| src/features/auth/route-guards.test.ts | Adds unit coverage for the new /login guard behavior and returnTo sanitization expectations. |
| src/features/auth/return-to.ts | Introduces resolveReturnTo helper to sanitize returnTo targets and avoid redirect loops. |
| src/features/auth/LoginPage.tsx | Redirects authenticated visitors away from /login after session resolution and uses location.replace on successful login. |
| src/features/auth/LoginPage.test.tsx | Adds coverage for location.replace, returnTo sanitization, and session-based redirect behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR implements session-aware login route protection with safe returnTo validation, and migrates the user dashboard tab selection from Zustand app state to URL search parameters. This fixes back/forward browser navigation for tab switching and prevents authenticated users from being redirected to login on in-app back navigation. ChangesLogin Route Guards and Safe Redirect Handling
Dashboard Tab State Migration to URL
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
Summary
Fixes back navigation for translators: pressing Back from the My History tab no longer dumps an authenticated user on the Login page — it returns to My Work, as expected.
Root cause
window.location.href = returnTopushed a new entry, leaving/loginin the history stack directly beneath the dashboard — exactly where Back landed./loginroute had no auth guard, so the authenticated user was shown the login form.Changes
/?tab=my-history) viavalidateSearchon/_authenticated/; each tab switch is a real history entry, so Back from My History lands on My Work. TheuserDashboardTabstore field is gone. Returning from a chapter via Back also restores the tab you left from, since it is part of the URL./loginis now guarded: abeforeLoadredirect (for in-app navigation) plus a session-aware effect inLoginPage(for hard loads, mirroringAuthenticatedLayout) send authenticated visitors to a sanitizedreturnToor the app home.window.location.replace(returnTo)so/loginno longer lingers in the history stack.returnTovalues are sanitized to same-origin paths (no open redirect, never back into/login).Screenshots
1) Login page (fresh session)
Unauthenticated visit to
/redirects to/login?returnTo=%2F— unchanged for logged-out users.2) My Work after login
Translator
t@fluent.locallands on/with the My Work tab active.3) My History tab is a real history entry
Clicking My History navigates to
/?tab=my-history(note the Submitted Date column) — the tab switch is now part of browser history.4) Back from My History lands on My Work (the fix)
Pressing the browser Back button from My History returns to
/with My Work active — not the Login page.5) Authenticated visit to /login bounces into the app
Navigating straight to
/loginwith a live session (hard load) redirects to the dashboard instead of showing the login form.Test plan
/loginguard — loading no-op, unauthenticated pass-through, authenticated redirect,returnTosanitization (external /////loginloop targets)UserHomePage— tab derived from URL search param, tab clicks navigate with?tab=LoginPage—location.replace(returnTo)on success, authenticated visitor redirected away, no redirect while session loadspnpm lint,pnpm format:check,pnpm typecheck,pnpm test(38 passing)/login; direct/loginvisit while authenticated bounces to the dashboardCloses #290
Summary by CodeRabbit
Bug Fixes
New Features
Tests