feat: SPA navigation for post-auth redirects#1647
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## frontend-base #1647 +/- ##
================================================
Coverage ? 92.12%
================================================
Files ? 92
Lines ? 2045
Branches ? 588
================================================
Hits ? 1884
Misses ? 158
Partials ? 3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jesusbalderramawgu
left a comment
There was a problem hiding this comment.
thank you, this is a great catch
b04929e to
4ff413c
Compare
When a user navigates to a protected SPA route while logged out, the authenticatedLoader redirects to /authn/login?next=%2Fsome-path. Previously, this relative next param was sent to the LMS backend, which resolved it against itself and returned an absolute LMS URL — causing a full page navigation away from the SPA shell. Now, when next starts with /, it is stripped from the API payload and handled locally: after login/registration succeeds, fetchAuthenticatedUser refreshes the auth cache so authenticatedLoader allows the target route, RedirectLogistration uses <Navigate> for SPA navigation, and hydrateAuthenticatedUser runs in the background to populate the full user profile (avatar, etc.) in the header. Also applies the same relative-path SPA navigation pattern to ProgressiveProfilingPageModal and replaces the hardcoded LMS dashboard fallback in the registration API with getUrlByRouteRole for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Routes are nested by default, but navigation constants used absolute paths like '/login', causing navigate() and <Navigate> to go to /login instead of /authn/login. Replace path constants in navigation calls with getUrlByRouteRole() where possible, which resolves the URL for a given route role from the route config. Path constants are still generally useful, but are now all relative (and camelCase). Centralize route roles, path segments, and the dashboard role in src/constants.ts. Last but not least, prefer SPA navigation (<Navigate>, useNavigate) over window.location.href where the target URL may be internal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the backend returns the LMS dashboard URL as redirect_url after login or registration, replace it with getUrlByRouteRole(dashboardRole) so that RedirectLogistration can use <Navigate> for SPA navigation instead of a full page reload. Also hydrate the authenticated user before SPA navigation so the shell header displays the user's name. This applies the same fetchAuthenticatedUser/hydrateAuthenticatedUser pattern already used for the localNextPath flow to any redirect URL that starts with '/'. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ba527eb to
b611c4d
Compare
| setRedirectToResetPasswordPage(true); | ||
| } else { | ||
| window.location.href = redirectUrl || getSiteConfig().lmsBaseUrl.concat(DEFAULT_REDIRECT_URL); | ||
| const url = redirectUrl || getUrlByRouteRole(dashboardRole); |
There was a problem hiding this comment.
Random though we should have in some doc that if someone is not using the dashboard frontend-app they'll need to make sure that the external route for it it's configured
There was a problem hiding this comment.
A good point, but the idea is for the dashboard role to actually be optional. If there's no dashboard role we should just use whatever the LMS gives, and failing that to go to "/", and that's up to the site operator to decide what to do with.
There was a problem hiding this comment.
would that redirectUrl be the one that the LMS gives? if that's the case then great
There was a problem hiding this comment.
Yes, the redirectUrl is what the LMS returns. (Except when we normalize it to be relative for SPA navigation - if the dashboard role exists.)
There was a problem hiding this comment.
I just realized I'm not actually treating the dashboard role as optional in this PR. Note to self: fix it.
If the dashboard route role isn't registered, getUrlByRouteRole() returns undefined, breaking navigation. Fall back to '/' in all four call sites so the app degrades gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
🎉 This PR is included in version 1.0.0-alpha.7 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Description
When a user navigates to a protected SPA route while logged out,
authenticatedLoaderredirects to/authn/login?next=/some-path. Previously, this relativenextparam was forwarded to the LMS backend, which resolved it against itself and returned an absolute LMS URL, causing a full page navigation away from the SPA shell. Now the relative path is handled locally after login/registration succeeds, using<Navigate>for seamless SPA navigation.This also revealed that the authn app's navigation was still using absolute path constants like
'/login', which don't resolve correctly under the/authnroute prefix. All navigation now goes throughgetUrlByRouteRole()to resolve URLs from the route config, and components likeUnAuthOnlyRoute,ChangePasswordPrompt, andProgressiveProfilinguse SPA navigation instead ofwindow.location.hrefwhen the target is internal.Finally, when the LMS backend returns its own dashboard URL as
redirect_urlafter login or registration, it is normalized to the role-based dashboard URL via a sharednormalizeRedirectUrl()utility, soRedirectLogistrationcan navigate without a full page reload.Depends on openedx/frontend-base#183, and indirectly on openedx/frontend-app-learner-dashboard#806.
Note to reviewer
Start the review with
src/constants.tsandsrc/routes.tsx. It makes the rest easier to understand.Testing
Set up a complete frontend-template-site with all the latest changes in frontend-base, authn, and learner-dashboard. After that, make sure login and registration still work, including by manually navigating to
/learner-dashboardwithout having logged in first (if you installed openedx/frontend-app-learner-dashboard#806).LLM usage notice
Built with assistance from Claude models (mostly Opus 4.6).