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>
|
|||
|
</>
|
|||
|
);
|
|||
|
}
|