148 lines
6.1 KiB
TypeScript
148 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Lock, User, ArrowRight, AlertCircle, Loader2 } from 'lucide-react';
|
|
|
|
export default function LoginPage() {
|
|
const router = useRouter();
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
router.push('/');
|
|
router.refresh();
|
|
} else {
|
|
setError(data.error || '登录失败');
|
|
}
|
|
} catch (err) {
|
|
setError('网络错误,请稍后重试');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4 sm:px-6 lg:px-8 transition-colors duration-200">
|
|
<div className="max-w-md w-full space-y-8">
|
|
<div className="text-center">
|
|
<div className="mx-auto h-16 w-16 bg-blue-600 rounded-2xl flex items-center justify-center shadow-lg transform rotate-3 hover:rotate-0 transition-transform duration-300">
|
|
<Lock className="h-8 w-8 text-white" />
|
|
</div>
|
|
<h2 className="mt-6 text-3xl font-extrabold text-gray-900 dark:text-white tracking-tight">
|
|
系统登录
|
|
</h2>
|
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
请输入您的管理员凭证以继续
|
|
</p>
|
|
</div>
|
|
|
|
<div className="bg-white dark:bg-gray-800 py-8 px-4 shadow-xl rounded-2xl sm:px-10 border border-gray-100 dark:border-gray-700">
|
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
{error && (
|
|
<div className="rounded-md bg-red-50 dark:bg-red-900/20 p-4 border border-red-200 dark:border-red-800 animate-in fade-in slide-in-from-top-2">
|
|
<div className="flex">
|
|
<div className="flex-shrink-0">
|
|
<AlertCircle className="h-5 w-5 text-red-400" aria-hidden="true" />
|
|
</div>
|
|
<div className="ml-3">
|
|
<h3 className="text-sm font-medium text-red-800 dark:text-red-200">
|
|
{error}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
用户名
|
|
</label>
|
|
<div className="mt-1 relative rounded-md shadow-sm">
|
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<User className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
</div>
|
|
<input
|
|
id="username"
|
|
name="username"
|
|
type="text"
|
|
autoComplete="username"
|
|
required
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg leading-5 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-colors"
|
|
placeholder="请输入用户名"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
密码
|
|
</label>
|
|
<div className="mt-1 relative rounded-md shadow-sm">
|
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<Lock className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
</div>
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
required
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg leading-5 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-colors"
|
|
placeholder="请输入密码"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full flex justify-center items-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 transform active:scale-[0.98]"
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<Loader2 className="animate-spin -ml-1 mr-2 h-4 w-4" />
|
|
登录中...
|
|
</>
|
|
) : (
|
|
<>
|
|
登录
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<p className="text-center text-xs text-gray-500 dark:text-gray-400">
|
|
© {new Date().getFullYear()} WinUpdate Neo. All rights reserved.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|