import { useEffect, useState } from 'react' import { useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { AlertCircle, FileText, HelpCircle, Key, Lock, Moon, Sun, Terminal, Zap, } from 'lucide-react' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { WavesBackground } from '@/components/waves-background' import { useTheme } from '@/components/use-theme' import { useAnimation } from '@/hooks/use-animation' import { parseResponse } from '@/lib/api-helpers' import { checkAuthStatus } from '@/lib/fetch-with-auth' import { cn } from '@/lib/utils' import { APP_FULL_NAME } from '@/lib/version' export function AuthPage() { const [token, setToken] = useState('') const [isValidating, setIsValidating] = useState(false) const [error, setError] = useState('') const [checkingAuth, setCheckingAuth] = useState(true) const navigate = useNavigate() const { t } = useTranslation() const { enableWavesBackground, setEnableWavesBackground } = useAnimation() const { theme, setTheme } = useTheme() // 如果已经认证,直接跳转到首页 useEffect(() => { const verifyAuth = async () => { try { const isAuth = await checkAuthStatus() if (isAuth) { navigate({ to: '/' }) } } catch { // 忽略错误,保持在登录页 } finally { setCheckingAuth(false) } } verifyAuth() }, [navigate]) // 获取实际应用的主题(处理 system 情况) const getActualTheme = () => { if (theme === 'system') { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' } return theme } const actualTheme = getActualTheme() // 主题切换(无动画) const toggleTheme = () => { const newTheme = actualTheme === 'dark' ? 'light' : 'dark' setTheme(newTheme) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') if (!token.trim()) { setError(t('auth.tokenRequired')) return } setIsValidating(true) console.log('开始验证 token...') try { // 向后端发送请求验证 token(后端会设置 HttpOnly Cookie) const response = await fetch('/api/webui/auth/verify', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 确保接收并存储 Cookie body: JSON.stringify({ token: token.trim() }), }) console.log('Token 验证响应状态:', response.status) const result = await parseResponse<{ valid: boolean is_first_setup?: boolean message?: string }>(response) if (!result.success) { console.error('Token 验证失败:', result.error) setError(result.error) return } const data = result.data console.log('Token 验证响应数据:', data) if (data.valid) { console.log('Token 验证成功,准备跳转...') console.log('is_first_setup:', data.is_first_setup) // Token 验证成功,Cookie 已由后端设置 // 等待一小段时间确保 Cookie 已设置 await new Promise((resolve) => setTimeout(resolve, 100)) // 再次检查认证状态 const authCheck = await checkAuthStatus() console.log('跳转前认证状态检查:', authCheck) // 直接使用验证响应中的 is_first_setup 字段,避免额外请求 if (data.is_first_setup) { console.log('跳转到首次配置页面') // 需要首次配置,跳转到配置向导 navigate({ to: '/setup' }) } else { console.log('跳转到首页') // 不需要配置或配置已完成,跳转到首页 navigate({ to: '/' }) } } else { console.error('Token 验证失败:', data.message) setError(data.message || t('auth.verifyFailed')) } } catch (err) { console.error('Token 验证错误:', err) setError( err instanceof Error ? err.message : t('auth.connFailed') ) } finally { setIsValidating(false) } } // 正在检查认证状态时显示加载 if (checkingAuth) { return (
{enableWavesBackground && }
{t('auth.checkingAuth')}
) } return (
{/* 波浪背景 - 独立控制 */} {enableWavesBackground && } {/* 认证卡片 - 磨砂玻璃效果 */} {/* 主题切换按钮 */} {/* Logo/Icon */}
{t('auth.welcome')} {t('auth.accessDesc')}
{/* Token 输入框 */}
setToken(e.target.value)} className={cn('pl-10', error && 'border-red-500 focus-visible:ring-red-500')} disabled={isValidating} autoFocus autoComplete="off" aria-invalid={error ? true : undefined} aria-describedby={error ? 'token-error' : undefined} />
{/* 错误提示 */} {error && ( )} {/* 提交按钮 */} {/* 帮助文本 */} {t('auth.helpTitle')} {t('auth.helpDesc')}
{/* 方式一:查看控制台 */}

{t('auth.method1Title')}

{t('auth.method1Desc')}

{t('auth.method1Example1')}

{t('auth.method1Example2')}

{/* 方式二:查看配置文件 */}

{t('auth.method2Title')}

{t('auth.method2Desc')}

data/webui.json

{t('auth.method2FileHint')} access_token

{/* 安全提示 */}

{t('auth.securityTipTitle')}

  • {t('auth.securityTip1')}
  • {t('auth.securityTip2')}
{/* 性能优化选项 */} {t('auth.disableAnimTitle')} {t('auth.disableAnimDesc')}

{t('auth.disableAnimDetail')}

{t('common.cancel')} setEnableWavesBackground(false)} > {t('auth.disableAnimBtn')}
{/* 页脚信息 */}

{APP_FULL_NAME}

) }