auto-save 2026-05-15 19:52 (+1, ~4)
This commit is contained in:
11
web/app/login/layout.tsx
Normal file
11
web/app/login/layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: { absolute: "" },
|
||||
description: "",
|
||||
}
|
||||
|
||||
export default function LoginLayout({ children }: { children: ReactNode }) {
|
||||
return children
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export default function LoginPage() {
|
||||
const [remember, setRemember] = useState(true)
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [activeField, setActiveField] = useState<"username" | "password" | null>(null)
|
||||
const [error, setError] = useState("")
|
||||
const [hasError, setHasError] = useState(false)
|
||||
const [status, setStatus] = useState<LoginStatus>("idle")
|
||||
const [eyeOffset, setEyeOffset] = useState({ x: 0, y: 0 })
|
||||
|
||||
@@ -42,17 +42,17 @@ export default function LoginPage() {
|
||||
|
||||
const mood: LoginCharacterMood = useMemo(() => {
|
||||
if (status === "success") return "success"
|
||||
if (error) return "error"
|
||||
if (hasError) return "error"
|
||||
if (showPassword && activeField === "password") return "peek"
|
||||
if (activeField || username || password) return "typing"
|
||||
return "idle"
|
||||
}, [activeField, error, password, showPassword, status, username])
|
||||
}, [activeField, hasError, password, showPassword, status, username])
|
||||
|
||||
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
setError("")
|
||||
setHasError(false)
|
||||
if (!username.trim() || !password) {
|
||||
setError("请输入访问账号和访问密钥")
|
||||
setHasError(true)
|
||||
return
|
||||
}
|
||||
setStatus("loading")
|
||||
@@ -64,22 +64,15 @@ export default function LoginPage() {
|
||||
body: JSON.stringify({ username, password, remember }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
let message = "访问账号或密钥不正确"
|
||||
try {
|
||||
const data = await res.json()
|
||||
message = data?.detail || data?.error || message
|
||||
} catch {
|
||||
// keep default message
|
||||
}
|
||||
throw new Error(message)
|
||||
throw new Error()
|
||||
}
|
||||
setStatus("success")
|
||||
window.setTimeout(() => {
|
||||
window.location.href = "/"
|
||||
}, 420)
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setStatus("idle")
|
||||
setError(err instanceof Error ? err.message : "验证失败,请稍后再试")
|
||||
setHasError(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +100,12 @@ export default function LoginPage() {
|
||||
className="h-full min-w-0 flex-1 bg-transparent text-base text-white outline-none placeholder:text-white/30"
|
||||
value={username}
|
||||
disabled={disabled}
|
||||
aria-label="account"
|
||||
autoComplete="username"
|
||||
onFocus={() => setActiveField("username")}
|
||||
onBlur={() => setActiveField(null)}
|
||||
onChange={(event) => {
|
||||
setUsername(event.target.value)
|
||||
if (error) setError("")
|
||||
if (hasError) setHasError(false)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
@@ -126,21 +118,19 @@ export default function LoginPage() {
|
||||
className="h-full min-w-0 flex-1 bg-transparent text-base text-white outline-none placeholder:text-white/30"
|
||||
value={password}
|
||||
disabled={disabled}
|
||||
aria-label="secret"
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="current-password"
|
||||
onFocus={() => setActiveField("password")}
|
||||
onBlur={() => setActiveField(null)}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value)
|
||||
if (error) setError("")
|
||||
if (hasError) setHasError(false)
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="grid h-9 w-9 place-items-center rounded-[8px] text-white/55 transition hover:bg-white/10 hover:text-white focus:outline-none focus:ring-2 focus:ring-[#d6b36a]/45 disabled:opacity-50"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
aria-label={showPassword ? "隐藏密码" : "显示密码"}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => setShowPassword((value) => !value)}
|
||||
>
|
||||
@@ -156,13 +146,12 @@ export default function LoginPage() {
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
disabled={disabled}
|
||||
aria-label="remember"
|
||||
onChange={(event) => setRemember(event.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="mt-4 min-h-9" aria-live="polite">
|
||||
{error ? (
|
||||
<div className="mt-4 min-h-9">
|
||||
{hasError ? (
|
||||
<div className="inline-flex h-9 w-9 items-center justify-center rounded-[8px] border border-red-400/30 bg-red-500/10 text-red-100">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
</div>
|
||||
@@ -177,7 +166,6 @@ export default function LoginPage() {
|
||||
className="mt-1 flex h-11 w-full items-center justify-center gap-2 rounded-[8px] bg-white px-4 text-base font-semibold text-black shadow-xl shadow-black/25 transition hover:bg-[#f5efe3] focus:outline-none focus:ring-2 focus:ring-[#d6b36a]/60 disabled:cursor-wait disabled:opacity-70"
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
aria-label="submit"
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user