feat: Implement Vercel environment detection and pause settings auto-fetch after consecutive failures to prevent excessive API calls.

This commit is contained in:
CJACK
2026-02-20 03:22:27 +08:00
parent 1d2a6bf281
commit 2781951ce7
7 changed files with 203 additions and 45 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState, useEffect, useCallback, useMemo } from 'react'
import {
Routes,
Route,
@@ -29,12 +29,12 @@ import Login from './components/Login'
import LandingPage from './components/LandingPage'
import LanguageToggle from './components/LanguageToggle'
import { useI18n } from './i18n'
import { detectRuntimeEnv } from './utils/runtimeEnv'
function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message, onForceLogout }) {
function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message, onForceLogout, isVercel }) {
const { t } = useI18n()
const [activeTab, setActiveTab] = useState('accounts')
const [sidebarOpen, setSidebarOpen] = useState(false)
const [loading, setLoading] = useState(false)
const navItems = [
{ id: 'accounts', label: t('nav.accounts.label'), icon: Users, description: t('nav.accounts.desc') },
@@ -44,7 +44,7 @@ function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message,
{ id: 'settings', label: t('nav.settings.label'), icon: SettingsIcon, description: t('nav.settings.desc') },
]
const authFetch = async (url, options = {}) => {
const authFetch = useCallback(async (url, options = {}) => {
const headers = {
...options.headers,
'Authorization': `Bearer ${token}`
@@ -56,7 +56,7 @@ function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message,
throw new Error(t('auth.expired'))
}
return res
}
}, [onLogout, t, token])
const renderTab = () => {
switch (activeTab) {
@@ -67,9 +67,9 @@ function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message,
case 'import':
return <BatchImport onRefresh={fetchConfig} onMessage={showMessage} authFetch={authFetch} />
case 'vercel':
return <VercelSync onMessage={showMessage} authFetch={authFetch} />
return <VercelSync onMessage={showMessage} authFetch={authFetch} isVercel={isVercel} />
case 'settings':
return <Settings onRefresh={fetchConfig} onMessage={showMessage} authFetch={authFetch} onForceLogout={onForceLogout} />
return <Settings onRefresh={fetchConfig} onMessage={showMessage} authFetch={authFetch} onForceLogout={onForceLogout} isVercel={isVercel} />
default:
return null
}
@@ -213,13 +213,27 @@ export default function App() {
const navigate = useNavigate()
const location = useLocation()
const [config, setConfig] = useState({ keys: [], accounts: [] })
const [loading, setLoading] = useState(true)
const [message, setMessage] = useState(null)
const [token, setToken] = useState(null)
const [authChecking, setAuthChecking] = useState(true)
const isProduction = import.meta.env.MODE === 'production'
const isAdminRoute = location.pathname.startsWith('/admin') || isProduction
const runtimeEnv = useMemo(() => detectRuntimeEnv(), [])
const isVercel = runtimeEnv.isVercel
const showMessage = useCallback((type, text) => {
setMessage({ type, text })
setTimeout(() => setMessage(null), 5000)
}, [])
const handleLogout = useCallback(() => {
setToken(null)
localStorage.removeItem('ds2api_token')
localStorage.removeItem('ds2api_token_expires')
sessionStorage.removeItem('ds2api_token')
sessionStorage.removeItem('ds2api_token_expires')
}, [])
useEffect(() => {
// Only check auth status on admin routes.
@@ -249,12 +263,11 @@ export default function App() {
setAuthChecking(false)
}
checkAuth()
}, [isAdminRoute])
}, [handleLogout, isAdminRoute])
const fetchConfig = async () => {
const fetchConfig = useCallback(async () => {
if (!token) return
try {
setLoading(true)
const res = await fetch('/admin/config', {
headers: { 'Authorization': `Bearer ${token}` }
})
@@ -265,34 +278,19 @@ export default function App() {
} catch (e) {
console.error('Failed to fetch config:', e)
showMessage('error', t('errors.fetchConfig', { error: e.message }))
} finally {
setLoading(false)
}
}
}, [showMessage, t, token])
useEffect(() => {
if (token) {
fetchConfig()
}
}, [token])
const showMessage = (type, text) => {
setMessage({ type, text })
setTimeout(() => setMessage(null), 5000)
}
}, [fetchConfig, token])
const handleLogin = (newToken) => {
setToken(newToken)
}
const handleLogout = () => {
setToken(null)
localStorage.removeItem('ds2api_token')
localStorage.removeItem('ds2api_token_expires')
sessionStorage.removeItem('ds2api_token')
sessionStorage.removeItem('ds2api_token_expires')
}
// Wait for auth checks on admin routes.
if (isAdminRoute && authChecking) {
return (
@@ -320,6 +318,7 @@ export default function App() {
showMessage={showMessage}
message={message}
onForceLogout={handleLogout}
isVercel={isVercel}
/>
) : (
<div className="min-h-screen flex flex-col bg-background relative overflow-hidden">