Files
hive-manage-v3/frontend/src/pages/LoginPage.tsx
T

117 lines
3.9 KiB
TypeScript

import { type FormEvent, useState } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'
import Button from '../components/ui/Button'
import toast from '../components/ui/Toast'
export default function LoginPage() {
const { login } = useAuth()
const navigate = useNavigate()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
setError('')
setLoading(true)
try {
await login(username, password)
toast.success('登录成功')
navigate('/dashboard', { replace: true })
} catch (err: any) {
const status = err?.response?.status
const detail = err?.response?.data?.detail ?? '登录失败'
if (status === 423) {
setError('账户已锁定,请 15 分钟后再试')
} else if (status === 401) {
setError('用户名或密码错误')
} else {
setError(detail)
}
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center" style={{ background: 'var(--bg-root)' }}>
<div
className="p-8 rounded-[var(--radius)] w-[360px]"
style={{ background: 'var(--bg-card)', boxShadow: 'var(--shadow-lg)' }}
>
{/* Logo */}
<h1 className="text-center" style={{ color: 'var(--honey)', fontSize: 20, fontWeight: 700 }}>
🐝 知微蜂巢
</h1>
<p className="text-center mt-1" style={{ color: 'var(--text-dim)', fontSize: 13 }}>
Hive Manage v3
</p>
{/* Error */}
{error && (
<div
className="mt-4 p-3 rounded-[var(--radius)] text-[13px] font-medium"
style={{ background: 'var(--bg-error)', color: 'var(--red)' }}
>
{error}
</div>
)}
{/* Form */}
<form onSubmit={handleSubmit} className="mt-6 flex flex-col gap-4">
<div>
<label className="text-[12px] font-semibold uppercase tracking-wider" style={{ color: 'var(--text-dim)' }}>
用户名
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="请输入用户名"
required
autoFocus
className="w-full mt-1 px-3 py-[9px] rounded-[var(--radius-sm)] text-[13px] outline-none transition-colors"
style={{
background: 'var(--bg-root)',
color: 'var(--text)',
border: '1px solid var(--border)',
}}
/>
</div>
<div>
<label className="text-[12px] font-semibold uppercase tracking-wider" style={{ color: 'var(--text-dim)' }}>
密码
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入密码"
required
className="w-full mt-1 px-3 py-[9px] rounded-[var(--radius-sm)] text-[13px] outline-none transition-colors"
style={{
background: 'var(--bg-root)',
color: 'var(--text)',
border: '1px solid var(--border)',
}}
/>
</div>
<Button type="submit" variant="primary" loading={loading} className="w-full !py-[9px]">
登入
</Button>
</form>
{/* Register link */}
<p className="text-center mt-4 text-[13px]" style={{ color: 'var(--text-dim)' }}>
还没有账号?{' '}
<Link to="/register" style={{ color: 'var(--honey)', fontWeight: 600 }}>
注册
</Link>
</p>
</div>
</div>
)
}