208 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Link from 'next/link';
import { toast } from 'react-hot-toast';
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import { auth } from '@/lib/supabase';
interface LoginForm {
email: string;
password: string;
}
export default function Login() {
const router = useRouter();
const [form, setForm] = useState<LoginForm>({
email: '',
password: '',
});
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!form.email || !form.password) {
toast.error('请填写所有必填字段');
return;
}
setLoading(true);
try {
// 检查是否为演示模式
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const isDemoMode = !supabaseUrl || supabaseUrl === 'https://demo.supabase.co' || supabaseUrl === '';
if (isDemoMode) {
// 演示模式:检查测试账号
if (form.email === 'admin@demo.com' && form.password === 'admin123') {
toast.success('登录成功!');
// 在演示模式下直接跳转到仪表盘
router.push('/dashboard');
} else {
toast.error('演示模式:请使用测试账号 admin@demo.com / admin123');
}
} else {
// 真实模式:使用 Supabase 认证
try {
await auth.signIn(form.email, form.password);
toast.success('登录成功!');
router.push('/dashboard');
} catch (authError: any) {
console.error('Supabase auth error:', authError);
toast.error(authError.message || '登录失败,请检查邮箱和密码');
}
}
} catch (error: any) {
console.error('Login error:', error);
toast.error('登录过程中发生错误,请稍后重试');
} finally {
setLoading(false);
}
};
// 填入测试账号
const fillTestAccount = () => {
setForm({
email: 'admin@demo.com',
password: 'admin123'
});
};
return (
<>
<Head>
<title> - </title>
</Head>
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
</p>
</div>
{/* 测试账号提示 */}
<div className="bg-blue-50 border border-blue-200 rounded-md p-4">
<div className="flex">
<div className="ml-3">
<h3 className="text-sm font-medium text-blue-800">
</h3>
<div className="mt-2 text-sm text-blue-700">
<p>admin@demo.com</p>
<p>admin123</p>
<button
type="button"
onClick={fillTestAccount}
className="mt-2 text-xs text-blue-600 hover:text-blue-500 underline"
>
</button>
</div>
</div>
</div>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="email" className="sr-only">
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="管理员邮箱"
value={form.email}
onChange={handleInputChange}
/>
</div>
<div className="relative">
<label htmlFor="password" className="sr-only">
</label>
<input
id="password"
name="password"
type={showPassword ? 'text' : 'password'}
autoComplete="current-password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 pr-10 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="管理员密码"
value={form.password}
onChange={handleInputChange}
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeSlashIcon className="h-5 w-5 text-gray-400" />
) : (
<EyeIcon className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="text-sm">
<Link
href="/"
className="font-medium text-blue-600 hover:text-blue-500"
>
</Link>
</div>
<div className="text-sm">
<a
href="#"
className="font-medium text-blue-600 hover:text-blue-500"
>
</a>
</div>
</div>
<div>
<button
type="submit"
disabled={loading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md 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"
>
{loading ? (
<div className="flex items-center">
<div className="loading-spinner-sm mr-2"></div>
...
</div>
) : (
'登录'
)}
</button>
</div>
</form>
</div>
</div>
</>
);
}