From 0df4712831b15258bab4116856db5bb662ae9b0d Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 13 Jun 2026 11:07:27 +0800 Subject: [PATCH] fix(frontend): resolve login redirect loop and clean up info tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prevent infinite session probe requests on the login page by guarding the check with the authenticated user state and using the Latest Ref pattern. - Decouple useEffect from resolveRedirectTarget by using resolveRedirectTargetRef to avoid searchParams dependency loops. - Remove the unused '服务连接' Card from the settings info tab and clean up unused imports, queries, and properties. --- frontend/components/auth/login-page.tsx | 37 ++++++++++++++++--- .../components/common/settings/info-tab.tsx | 32 ++-------------- .../common/settings/system-settings.tsx | 11 +----- 3 files changed, 35 insertions(+), 45 deletions(-) diff --git a/frontend/components/auth/login-page.tsx b/frontend/components/auth/login-page.tsx index 0b05752b..807d7cb9 100644 --- a/frontend/components/auth/login-page.tsx +++ b/frontend/components/auth/login-page.tsx @@ -1,6 +1,6 @@ "use client" -import {useCallback, useEffect, useState} from "react" +import {useCallback, useEffect, useRef, useState} from "react" import {AnimatePresence, motion} from "motion/react" import {useRouter, useSearchParams} from "next/navigation" import {toast} from "sonner" @@ -27,7 +27,7 @@ import {safeRedirectTarget} from "@/lib/utils" export function LoginPage() { const router = useRouter() const searchParams = useSearchParams() - const { setUser } = useAuth() + const { user, setUser } = useAuth() const [showOTP, setShowOTP] = useState(false) /* 处理OAuth回调 */ @@ -39,6 +39,8 @@ export function LoginPage() { const [isCheckingSession, setIsCheckingSession] = useState(() => !searchParams.get('state') || !searchParams.get('code')) const [loginSuccess, setLoginSuccess] = useState(false) + const redirectedRef = useRef(false) + const callbackProcessedRef = useRef(false) const resolveRedirectTarget = useCallback(() => { const callbackUrl = searchParams.get('callbackUrl') @@ -52,6 +54,11 @@ export function LoginPage() { return safeRedirectTarget(target) }, [searchParams]) + const resolveRedirectTargetRef = useRef(resolveRedirectTarget) + useEffect(() => { + resolveRedirectTargetRef.current = resolveRedirectTarget + }, [resolveRedirectTarget]) + /* 登录页兜底:已登录用户直接跳转 */ useEffect(() => { @@ -63,6 +70,15 @@ export function LoginPage() { return } + if (user) { + if (!redirectedRef.current) { + redirectedRef.current = true + router.replace(resolveRedirectTargetRef.current()) + setIsCheckingSession(false) + } + return + } + let cancelled = false const checkExistingSession = async () => { @@ -81,7 +97,10 @@ export function LoginPage() { if (payload?.data) { setUser(payload.data) } - router.replace(resolveRedirectTarget()) + if (!redirectedRef.current) { + redirectedRef.current = true + router.replace(resolveRedirectTargetRef.current()) + } return } } catch (error) { @@ -100,7 +119,7 @@ export function LoginPage() { return () => { cancelled = true } - }, [router, searchParams, resolveRedirectTarget, setUser]) + }, [router, searchParams, setUser, user]) /* 回调逻辑 */ useEffect(() => { @@ -109,6 +128,9 @@ export function LoginPage() { const code = searchParams.get('code') if (state && code) { + if (callbackProcessedRef.current) return + callbackProcessedRef.current = true + setIsProcessingCallback(true) try { const result = await services.auth.handleCallback({ state, code }) @@ -125,7 +147,10 @@ export function LoginPage() { toast.success(result.status === "bound" ? "绑定成功" : "登录成功") setTimeout(() => { - router.replace(resolveRedirectTarget()) + if (!redirectedRef.current) { + redirectedRef.current = true + router.replace(resolveRedirectTargetRef.current()) + } }, 1500) } catch (error) { console.error('OAuth callback error:', error) @@ -136,7 +161,7 @@ export function LoginPage() { } } handleOAuthCallback() - }, [searchParams, router, resolveRedirectTarget, setUser]) + }, [router, searchParams, setUser]) return ( diff --git a/frontend/components/common/settings/info-tab.tsx b/frontend/components/common/settings/info-tab.tsx index b6b49c07..54fa2d73 100644 --- a/frontend/components/common/settings/info-tab.tsx +++ b/frontend/components/common/settings/info-tab.tsx @@ -1,10 +1,10 @@ "use client" import {useMutation, useQuery} from "@tanstack/react-query" -import {ExternalLink, RefreshCw, Server, Sparkles} from "lucide-react" +import {ExternalLink, RefreshCw, Sparkles} from "lucide-react" import {toast} from "sonner" -import {AdminService, apiConfig} from "@/lib/services" +import {AdminService} from "@/lib/services" import { AlertDialog, AlertDialogAction, @@ -30,12 +30,7 @@ function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { ) } -interface InfoTabProps { - systemConfigsLength: number - authSourcesLength: number -} - -export function InfoTab({ systemConfigsLength, authSourcesLength }: InfoTabProps) { +export function InfoTab() { const updateQuery = useQuery({ queryKey: ["admin", "update"], queryFn: () => AdminService.getUpdateStatus(), @@ -163,27 +158,6 @@ export function InfoTab({ systemConfigsLength, authSourcesLength }: InfoTabProps ) : null} - - - -
-
- -
-
- 服务连接 - 前端 API 客户端的基础连接参数 -
-
-
- - - - - - - -
) } diff --git a/frontend/components/common/settings/system-settings.tsx b/frontend/components/common/settings/system-settings.tsx index e2f58f39..fd02ae36 100644 --- a/frontend/components/common/settings/system-settings.tsx +++ b/frontend/components/common/settings/system-settings.tsx @@ -35,12 +35,6 @@ export function SystemSettingsMain() { enabled: !!user?.is_admin, }) - const authSourcesQuery = useQuery({ - queryKey: ["auth", "sources"], - queryFn: () => AdminService.listAuthSources(), - enabled: !!user?.is_admin, - }) - const configs = useMemo( () => systemConfigMap(systemConfigsQuery.data ?? []), [systemConfigsQuery.data], @@ -112,10 +106,7 @@ export function SystemSettingsMain() { - +