208 lines
7.2 KiB
TypeScript
208 lines
7.2 KiB
TypeScript
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>
|
||
</>
|
||
);
|
||
}
|