From dc6b3a9e2c3a00e276d350d566a4110fa804ebb2 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Sun, 14 Jun 2026 13:45:46 +0700 Subject: [PATCH] refactor(webui): route auth redirects through lib/routes Step 3 of the route-registry migration: every /login redirect and post-login landing fallback builds from lib/routes instead of hardcoded strings. - protected-route, axios 401/403 interceptor, user-provider (logout, session-expiry, /login + public-route checks) -> routes.login(...) / routes.login() - oauth-result error redirect -> routes.login() - login-form / public-route / login page post-login fallback -> routes.newFlow getReturnUrlParam now has a single in-app caller (the routes.login builder) plus the logout suffix-override; dropped its now-unused imports from axios and protected-route. Co-Authored-By: Claude Fable 5 --- frontend/src/components/routes/protected-route.tsx | 6 ++---- frontend/src/components/routes/public-route.tsx | 3 ++- frontend/src/features/authentication/login-form.tsx | 3 ++- frontend/src/lib/axios.ts | 12 +++++------- frontend/src/pages/login.tsx | 3 ++- frontend/src/pages/oauth-result.tsx | 3 ++- frontend/src/providers/user-provider.tsx | 10 +++++----- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/routes/protected-route.tsx b/frontend/src/components/routes/protected-route.tsx index 67c2c70e..d5d70be6 100644 --- a/frontend/src/components/routes/protected-route.tsx +++ b/frontend/src/components/routes/protected-route.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Navigate, useLocation } from 'react-router-dom'; -import { getReturnUrlParam } from '@/lib/utils/auth'; +import { routes } from '@/lib/routes'; import { useUser } from '@/providers/user-provider'; function ProtectedRoute({ children }: { children: React.ReactNode }) { @@ -13,12 +13,10 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) { } if (!isAuthenticated()) { - const returnParam = getReturnUrlParam(location.pathname); - return ( ); } diff --git a/frontend/src/components/routes/public-route.tsx b/frontend/src/components/routes/public-route.tsx index 2b19b33d..6655cd3c 100644 --- a/frontend/src/components/routes/public-route.tsx +++ b/frontend/src/components/routes/public-route.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Navigate, useSearchParams } from 'react-router-dom'; +import { routes } from '@/lib/routes'; import { getSafeReturnUrl } from '@/lib/utils/auth'; import { useUser } from '@/providers/user-provider'; @@ -29,7 +30,7 @@ function PublicRoute({ children }: { children: React.ReactNode }) { return children; } - const returnUrl = getSafeReturnUrl(searchParams.get('returnUrl'), '/flows/new'); + const returnUrl = getSafeReturnUrl(searchParams.get('returnUrl'), routes.newFlow); return ( >({ defaultValues: { mail: '', diff --git a/frontend/src/lib/axios.ts b/frontend/src/lib/axios.ts index 8af1b094..7fb8ce9b 100644 --- a/frontend/src/lib/axios.ts +++ b/frontend/src/lib/axios.ts @@ -5,7 +5,7 @@ import Axios from 'axios'; import { AUTH_STORAGE_KEY } from '@/providers/user-provider'; import { Log } from './log'; -import { getReturnUrlParam } from './utils/auth'; +import { routes } from './routes'; // ── shared API protocol types ──────────────────────────────────────────────── // @@ -106,9 +106,8 @@ axios.interceptors.response.use( const currentPath = window.location.pathname; - if (currentPath !== '/login') { - const returnParam = getReturnUrlParam(currentPath); - window.location.href = `/login${returnParam}`; + if (currentPath !== routes.login()) { + window.location.href = routes.login(currentPath); } break; @@ -129,9 +128,8 @@ axios.interceptors.response.use( const currentPath = window.location.pathname; - if (currentPath !== '/login') { - const returnParam = getReturnUrlParam(currentPath); - window.location.href = `/login${returnParam}`; + if (currentPath !== routes.login()) { + window.location.href = routes.login(currentPath); } } else { Log.warn(err.response?.data); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 3a16cc24..3182a7b1 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -3,6 +3,7 @@ import { useLocation, useSearchParams } from 'react-router-dom'; import Logo from '@/components/icons/logo'; import LoginForm from '@/features/authentication/login-form'; +import { routes } from '@/lib/routes'; import { getSafeReturnUrl } from '@/lib/utils/auth'; import { useUser } from '@/providers/user-provider'; @@ -12,7 +13,7 @@ function Login() { const { authInfo, isLoading } = useUser(); const authProviders = authInfo?.providers || []; - const returnUrl = getSafeReturnUrl((location.state?.from as string) || searchParams.get('returnUrl'), '/flows/new'); + const returnUrl = getSafeReturnUrl((location.state?.from as string) || searchParams.get('returnUrl'), routes.newFlow); return (
diff --git a/frontend/src/pages/oauth-result.tsx b/frontend/src/pages/oauth-result.tsx index 4e081c06..f41386cd 100644 --- a/frontend/src/pages/oauth-result.tsx +++ b/frontend/src/pages/oauth-result.tsx @@ -1,6 +1,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import Logo from '@/components/icons/logo'; +import { routes } from '@/lib/routes'; function OAuthResult() { const [statusMessage, setStatusMessage] = useState('Authentication in progress...'); @@ -79,7 +80,7 @@ function OAuthResult() { } } else { updateMessage('Authentication window opened directly. Redirecting to login page...'); - handleRedirect('/login', errorDelay / 2); + handleRedirect(routes.login(), errorDelay / 2); handleClose(errorDelay); } diff --git a/frontend/src/providers/user-provider.tsx b/frontend/src/providers/user-provider.tsx index 9d4ec473..181466d4 100644 --- a/frontend/src/providers/user-provider.tsx +++ b/frontend/src/providers/user-provider.tsx @@ -8,6 +8,7 @@ import type { AuthInfo } from '@/models/info'; import type { User } from '@/models/user'; import { api } from '@/lib/axios'; +import { routes } from '@/lib/routes'; import { getReturnUrlParam } from '@/lib/utils/auth'; import { baseUrl } from '@/models/api'; @@ -147,7 +148,7 @@ export function UserProvider({ children }: { children: ReactNode }) { }, [setAuth, clearAuth]); useEffect(() => { - if (location.pathname === '/login' && !isLoading) { + if (location.pathname === routes.login() && !isLoading) { // eslint-disable-next-line react-hooks/set-state-in-effect -- refreshAuthInfo's setState runs after an async fetch, not synchronously refreshAuthInfo(); } @@ -165,7 +166,7 @@ export function UserProvider({ children }: { children: ReactNode }) { toast.error('Logout failed, but clearing local session'); } finally { clearAuth(); - window.location.href = `/login${finalReturnUrl}`; + window.location.href = `${routes.login()}${finalReturnUrl}`; } }, [clearAuth, location.pathname], @@ -328,7 +329,7 @@ export function UserProvider({ children }: { children: ReactNode }) { useEffect(() => { const updateAuth = async () => { - const publicRoutes = ['/login', '/oauth/result']; + const publicRoutes = [routes.login(), routes.oauthResult]; if (publicRoutes.includes(location.pathname)) { return; @@ -350,8 +351,7 @@ export function UserProvider({ children }: { children: ReactNode }) { } else { clearAuth(); toast.error('Session expired. Please login again.'); - const returnParam = getReturnUrlParam(location.pathname); - navigate(`/login${returnParam}`); + navigate(routes.login(location.pathname)); } } catch { // A transient /info failure on navigation must not log the user out — a network