From c4995714087340edeaba4c258dd2006ea17e0238 Mon Sep 17 00:00:00 2001 From: vsxd Date: Fri, 13 Mar 2026 11:43:14 +0800 Subject: [PATCH] fix(web): redirect unauthenticated users back after login --- web/src/app/layout.tsx | 1 + web/src/app/router.tsx | 29 +++++++++++++++++++++-------- web/src/pages/login.tsx | 14 +++++++++++--- web/src/pages/register.tsx | 14 +++++++++++--- web/src/pages/skill-detail.tsx | 10 ++++++++-- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index ff1e4814..e541adcf 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -71,6 +71,7 @@ export function Layout() { ) : ( diff --git a/web/src/app/router.tsx b/web/src/app/router.tsx index 63ec7c54..50750aed 100644 --- a/web/src/app/router.tsx +++ b/web/src/app/router.tsx @@ -42,10 +42,17 @@ const rootRoute = createRootRoute({ component: Layout, }) -async function requireAuth() { +function buildReturnTo(location: { pathname: string; searchStr?: string; hash?: string }) { + return `${location.pathname}${location.searchStr ?? ''}${location.hash ?? ''}` +} + +async function requireAuth({ location }: { location: { pathname: string; searchStr?: string; hash?: string } }) { const user = await getCurrentUser() if (!user) { - throw redirect({ to: '/login' }) + throw redirect({ + to: '/login', + search: { returnTo: buildReturnTo(location) }, + }) } return { user } } @@ -59,12 +66,18 @@ const skillsRoute = createRoute({ const loginRoute = createRoute({ getParentRoute: () => rootRoute, path: 'login', + validateSearch: (search: Record) => ({ + returnTo: typeof search.returnTo === 'string' ? search.returnTo : '', + }), component: LoginPage, }) const registerRoute = createRoute({ getParentRoute: () => rootRoute, path: 'register', + validateSearch: (search: Record) => ({ + returnTo: typeof search.returnTo === 'string' ? search.returnTo : '', + }), component: RegisterPage, }) @@ -152,8 +165,8 @@ const dashboardReviewDetailRoute = createRoute({ const dashboardPromotionsRoute = createRoute({ getParentRoute: () => rootRoute, path: 'dashboard/promotions', - beforeLoad: async () => { - const { user } = await requireAuth() + beforeLoad: async (ctx) => { + const { user } = await requireAuth(ctx) if (!user.platformRoles?.includes('SKILL_ADMIN') && !user.platformRoles?.includes('SUPER_ADMIN')) { throw redirect({ to: '/dashboard' }) } @@ -199,8 +212,8 @@ const settingsAccountsRoute = createRoute({ const adminUsersRoute = createRoute({ getParentRoute: () => rootRoute, path: 'admin/users', - beforeLoad: async () => { - const { user } = await requireAuth() + beforeLoad: async (ctx) => { + const { user } = await requireAuth(ctx) if (!user.platformRoles?.includes('USER_ADMIN') && !user.platformRoles?.includes('SUPER_ADMIN')) { throw redirect({ to: '/dashboard' }) } @@ -212,8 +225,8 @@ const adminUsersRoute = createRoute({ const adminAuditLogRoute = createRoute({ getParentRoute: () => rootRoute, path: 'admin/audit-log', - beforeLoad: async () => { - const { user } = await requireAuth() + beforeLoad: async (ctx) => { + const { user } = await requireAuth(ctx) if (!user.platformRoles?.includes('AUDITOR') && !user.platformRoles?.includes('SUPER_ADMIN')) { throw redirect({ to: '/dashboard' }) } diff --git a/web/src/pages/login.tsx b/web/src/pages/login.tsx index 8c639e1c..f4fab058 100644 --- a/web/src/pages/login.tsx +++ b/web/src/pages/login.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router' +import { Link, useNavigate, useSearch } from '@tanstack/react-router' import { useState } from 'react' import { LoginButton } from '@/features/auth/login-button' import { useLocalLogin } from '@/features/auth/use-local-auth' @@ -7,15 +7,19 @@ import { Input } from '@/shared/ui/input' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs' export function LoginPage() { + const navigate = useNavigate() + const search = useSearch({ from: '/login' }) const loginMutation = useLocalLogin() const [username, setUsername] = useState('') const [password, setPassword] = useState('') + const returnTo = search.returnTo && search.returnTo.startsWith('/') ? search.returnTo : '/dashboard' + async function handleSubmit(event: React.FormEvent) { event.preventDefault() try { await loginMutation.mutateAsync({ username, password }) - window.location.href = '/dashboard' + await navigate({ to: returnTo }) } catch { // mutation state drives the error UI } @@ -73,7 +77,11 @@ export function LoginPage() {

还没有账号? {' '} - + 立即注册

diff --git a/web/src/pages/register.tsx b/web/src/pages/register.tsx index 6bb2ba90..c6e79f62 100644 --- a/web/src/pages/register.tsx +++ b/web/src/pages/register.tsx @@ -1,4 +1,4 @@ -import { Link } from '@tanstack/react-router' +import { Link, useNavigate, useSearch } from '@tanstack/react-router' import { useState } from 'react' import { LoginButton } from '@/features/auth/login-button' import { useLocalRegister } from '@/features/auth/use-local-auth' @@ -8,16 +8,20 @@ import { Input } from '@/shared/ui/input' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs' export function RegisterPage() { + const navigate = useNavigate() + const search = useSearch({ from: '/register' }) const registerMutation = useLocalRegister() const [username, setUsername] = useState('') const [email, setEmail] = useState('') const [password, setPassword] = useState('') + const returnTo = search.returnTo && search.returnTo.startsWith('/') ? search.returnTo : '/dashboard' + async function handleSubmit(event: React.FormEvent) { event.preventDefault() try { await registerMutation.mutateAsync({ username, email, password }) - window.location.href = '/dashboard' + await navigate({ to: returnTo }) } catch { // mutation state drives the error UI } @@ -80,7 +84,11 @@ export function RegisterPage() {

已有账号? {' '} - + 返回登录

diff --git a/web/src/pages/skill-detail.tsx b/web/src/pages/skill-detail.tsx index dd201188..1dc4b995 100644 --- a/web/src/pages/skill-detail.tsx +++ b/web/src/pages/skill-detail.tsx @@ -1,4 +1,4 @@ -import { useParams, useNavigate } from '@tanstack/react-router' +import { useParams, useNavigate, useRouterState } from '@tanstack/react-router' import { useMutation, useQueryClient } from '@tanstack/react-query' import { MarkdownRenderer } from '@/features/skill/markdown-renderer' import { FileTree } from '@/features/skill/file-tree' @@ -20,6 +20,7 @@ import { export function SkillDetailPage() { const navigate = useNavigate() + const location = useRouterState({ select: (s) => s.location }) const queryClient = useQueryClient() const { namespace, slug } = useParams({ from: '/@$namespace/$slug' }) const { user, hasRole } = useAuth() @@ -53,7 +54,12 @@ export function SkillDetailPage() { }) const requireLogin = () => { - navigate({ to: '/login' }) + navigate({ + to: '/login', + search: { + returnTo: `${location.pathname}${location.searchStr}${location.hash}`, + }, + }) } if (isLoadingSkill) {