import {useCallback, useEffect, useRef, useState} from "react"; import {Link, useLocation, useNavigate} from "react-router-dom"; import {ArrowRight, Command, Eye, EyeOff, Loader2, ShieldAlert} from "lucide-react"; import {apiAuthCaptcha, type ApiResponseCaptchaResponse} from "@/client"; import {getApiErrorMessage /*, isTotpRequiredError*/} from "@/lib/api-error"; // 2FA disabled import {useUser} from "@/contexts"; import {AuthLayout} from "@/components/auth/auth-layout"; import {Button} from "@/components/ui/button"; import {Input} from "@/components/ui/input"; import {rsaEncrypt} from "@/lib/rsa"; import {AnimatePresence, motion} from "framer-motion"; export function LoginPage() { const {login} = useUser(); const navigate = useNavigate(); const location = useLocation(); const from = (location.state as { from?: string })?.from || "/w/me"; const usernameRef = useRef(null); const [form, setForm] = useState({username: "", password: "", captcha: ""}); const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); // const [needsTotp, setNeedsTotp] = useState(false); // 2FA disabled const [captcha, setCaptcha] = useState(null); const [captchaLoading, setCaptchaLoading] = useState(false); const [error, setError] = useState(null); const loadCaptcha = useCallback(async () => { setCaptchaLoading(true); try { // 使用 dark: false 保持验证码背景干净,用 CSS 滤镜适配暗黑模式 const resp = await apiAuthCaptcha({body: {w: 100, h: 32, dark: false, rsa: true}}); if (resp.data?.data) setCaptcha(resp.data.data); } catch { /* ignored */ } finally { setCaptchaLoading(false); } }, []); useEffect(() => { usernameRef.current?.focus(); loadCaptcha(); }, [loadCaptcha]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (!form.username.trim() || !form.password) { setError("Please fill in all required fields."); return; } setIsLoading(true); try { const encryptedPassword = captcha?.rsa ? rsaEncrypt(form.password, captcha.rsa.public_key) : form.password; await login({ username: form.username, password: encryptedPassword, captcha: form.captcha, // totp_code: undefined, // 2FA disabled }); navigate(from, {replace: true}); } catch (err: unknown) { // if (isTotpRequiredError(err)) { // 2FA disabled // setNeedsTotp(true); // } else { setError(getApiErrorMessage(err, "Invalid credentials. Please try again.")); await loadCaptcha(); setForm((p) => ({...p, captcha: ""})); // } } finally { setIsLoading(false); } }; return ( {/* 头部区域 */}

Sign in

{from && from !== "/" ? <>Continue to {from} : "Welcome back to GitDataAI" }

{/* 核心表单卡片 */}
{/* Username */}
setForm((p) => ({...p, username: e.target.value}))} className="h-10 px-3 bg-zinc-50 dark:bg-zinc-900/50 border-zinc-200 dark:border-zinc-800 rounded-lg focus-visible:ring-1 focus-visible:ring-zinc-400 dark:focus-visible:ring-zinc-600 transition-shadow" disabled={isLoading} />
{/* Password */}
Forgot?
setForm((p) => ({...p, password: e.target.value}))} className="h-10 pl-3 pr-10 bg-zinc-50 dark:bg-zinc-900/50 border-zinc-200 dark:border-zinc-800 rounded-lg focus-visible:ring-1 focus-visible:ring-zinc-400 dark:focus-visible:ring-zinc-600 transition-shadow" disabled={isLoading} />
{/* 2FA disabled { {needsTotp && ( setForm((p) => ({ ...p, totp_code: e.target.value.replace(/\D/g, "") }))} className="h-10 text-center tracking-[0.5em] font-mono text-lg bg-zinc-50 dark:bg-zinc-900/50 border-zinc-200 dark:border-zinc-800 rounded-lg focus-visible:ring-1 focus-visible:ring-zinc-400" disabled={isLoading} /> )} } */} {/* 优雅的内嵌验证码设计 */} {/* {needsTotp && (} */}
setForm((p) => ({...p, captcha: e.target.value}))} className="h-10 pl-3 pr-[110px] bg-zinc-50 dark:bg-zinc-900/50 border-zinc-200 dark:border-zinc-800 rounded-lg focus-visible:ring-1 focus-visible:ring-zinc-400 dark:focus-visible:ring-zinc-600 transition-shadow" disabled={isLoading || captchaLoading} />
{/* )} */} {/* 错误提示 - 使用非常克制的柔和边框风格 */} {error && (

{error}

)}

Don't have an account?{" "} Create one

); }