fix(web): redirect unauthenticated users back after login

This commit is contained in:
vsxd 2026-03-13 11:43:14 +08:00
parent f16885a39d
commit c499571408
5 changed files with 52 additions and 16 deletions

View file

@ -71,6 +71,7 @@ export function Layout() {
) : (
<Link
to="/login"
search={{ returnTo: '' }}
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
activeProps={{ className: 'text-primary' }}
>

View file

@ -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<string, unknown>) => ({
returnTo: typeof search.returnTo === 'string' ? search.returnTo : '',
}),
component: LoginPage,
})
const registerRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'register',
validateSearch: (search: Record<string, unknown>) => ({
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' })
}

View file

@ -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<HTMLFormElement>) {
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() {
<p className="text-center text-sm text-muted-foreground">
还没有账号?
{' '}
<Link to="/register" className="font-medium text-primary hover:underline">
<Link
to="/register"
search={{ returnTo }}
className="font-medium text-primary hover:underline"
>
立即注册
</Link>
</p>

View file

@ -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<HTMLFormElement>) {
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() {
<p className="text-center text-sm text-muted-foreground">
已有账号?
{' '}
<Link to="/login" className="font-medium text-primary hover:underline">
<Link
to="/login"
search={{ returnTo }}
className="font-medium text-primary hover:underline"
>
返回登录
</Link>
</p>

View file

@ -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) {