import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, ConfigProvider, Form, Input, Layout, Menu, Popover, Space, Spin, message, } from 'antd'; import { KeyOutlined, LockOutlined, MoonFilled, MoonOutlined, SunOutlined, TranslationOutlined, UserOutlined, } from '@ant-design/icons'; import { FormProvider, useForm } from 'react-hook-form'; import { HttpUtil, LanguageManager } from '@/utils'; import { FormField, rhfZodValidate } from '@/components/form/rhf'; import { setMessageInstance } from '@/utils/messageBus'; import SponsorSlot from '@/components/sponsor/SponsorSlot'; import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; import { LoginFormSchema, TwoFactorCodeSchema, type LoginFormValues } from '@/schemas/login'; import './LoginPage.css'; const HEADLINE_INTERVAL_MS = 2000; type LoginForm = LoginFormValues; const basePath = window.X_UI_BASE_PATH || ''; export default function LoginPage() { const { t } = useTranslation(); const { isDark, isUltra, toggleTheme, toggleUltra, antdThemeConfig } = useTheme(); const [messageApi, messageContextHolder] = message.useMessage(); useEffect(() => { setMessageInstance(messageApi); }, [messageApi]); const [fetched, setFetched] = useState(false); const [submitting, setSubmitting] = useState(false); const [twoFactorEnable, setTwoFactorEnable] = useState(false); const [headlineIndex, setHeadlineIndex] = useState(0); const methods = useForm({ defaultValues: { username: '', password: '', twoFactorCode: '' }, }); const [lang, setLang] = useState(() => LanguageManager.getLanguage()); const headlineWords = useMemo(() => [t('pages.login.hello'), t('pages.login.title')], [t]); useEffect(() => { const timer = window.setInterval(() => { setHeadlineIndex((i) => (i + 1) % headlineWords.length); }, HEADLINE_INTERVAL_MS); return () => window.clearInterval(timer); }, [headlineWords.length]); useEffect(() => { let cancelled = false; (async () => { const msg = await HttpUtil.post('/getTwoFactorEnable'); if (cancelled) return; if (msg.success) setTwoFactorEnable(!!msg.obj); setFetched(true); })(); return () => { cancelled = true; }; }, []); const onSubmit = useCallback(async (values: LoginForm) => { setSubmitting(true); try { const msg = await HttpUtil.post('/login', values); if (msg.success) window.location.href = basePath + 'panel/'; } finally { setSubmitting(false); } }, []); const onLangChange = useCallback((next: string) => { setLang(next); LanguageManager.setLanguage(next); }, []); const cycleTheme = useCallback(() => { pauseAnimationsUntilLeave('login-theme-cycle'); if (!isDark) { toggleTheme(); if (isUltra) toggleUltra(); } else if (!isUltra) { toggleUltra(); } else { toggleUltra(); toggleTheme(); } }, [isDark, isUltra, toggleTheme, toggleUltra]); const pageClass = useMemo(() => { const classes = ['login-app']; if (isDark) classes.push('is-dark'); if (isUltra) classes.push('is-ultra'); return classes.join(' '); }, [isDark, isUltra]); const langMenuItems = useMemo( () => (LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[]).map( (l) => ({ key: l.value, label: ( {l.name} ), }), ), [], ); const themeIcon = !isDark ? : !isUltra ? : ; return ( {messageContextHolder}
{!fetched ? (
) : (
3X-UI

{headlineWords[headlineIndex]}

} autoComplete="username" size="large" placeholder={t('username')} autoFocus /> } autoComplete="current-password" size="large" placeholder={t('password')} /> {twoFactorEnable && ( } autoComplete="one-time-code" size="large" placeholder={t('twoFactorCode')} /> )}
)}
); }