201 lines
8.9 KiB
TypeScript
201 lines
8.9 KiB
TypeScript
import { useState } from 'react';
|
||
import { useRouter } from 'next/router';
|
||
import Link from 'next/link';
|
||
import { useForm } from 'react-hook-form';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import * as z from 'zod';
|
||
import { Loader2, Mail, Lock, ArrowRight, Sparkles } from 'lucide-react';
|
||
import { useAuth } from '@/hooks/useAuth';
|
||
|
||
import { Button } from '@/components/ui/button';
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from '@/components/ui/form';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Checkbox } from '@/components/ui/checkbox';
|
||
|
||
const formSchema = z.object({
|
||
email: z.string().email({ message: "请输入有效的邮箱地址" }),
|
||
password: z.string().min(6, { message: "密码至少需要6个字符" }),
|
||
});
|
||
|
||
export default function LoginPage() {
|
||
const router = useRouter();
|
||
const { refreshUser } = useAuth();
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const [error, setError] = useState('');
|
||
|
||
const form = useForm<z.infer<typeof formSchema>>({
|
||
resolver: zodResolver(formSchema),
|
||
defaultValues: {
|
||
email: "",
|
||
password: "",
|
||
},
|
||
});
|
||
|
||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||
setIsLoading(true);
|
||
setError('');
|
||
|
||
try {
|
||
const res = await fetch('/api/auth/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(values),
|
||
});
|
||
|
||
const data = await res.json();
|
||
|
||
if (!res.ok) {
|
||
throw new Error(data.message || '登录失败');
|
||
}
|
||
|
||
// 登录成功,刷新用户状态并跳转
|
||
await refreshUser();
|
||
const redirect = router.query.redirect as string;
|
||
router.push(redirect || '/');
|
||
} catch (err: any) {
|
||
setError(err.message);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen grid lg:grid-cols-2">
|
||
{/* Left Panel - Visual & Branding */}
|
||
<div className="hidden lg:flex flex-col justify-between bg-black text-white p-12 relative overflow-hidden">
|
||
{/* Abstract Background */}
|
||
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop')] bg-cover bg-center opacity-40"></div>
|
||
<div className="absolute inset-0 bg-linear-to-br from-black via-black/80 to-transparent"></div>
|
||
|
||
{/* Content */}
|
||
<div className="relative z-10">
|
||
<div className="flex items-center gap-2 text-2xl font-bold">
|
||
<div className="w-8 h-8 rounded-lg bg-white text-black flex items-center justify-center">
|
||
<Sparkles className="w-5 h-5" />
|
||
</div>
|
||
AOUN AI
|
||
</div>
|
||
</div>
|
||
|
||
<div className="relative z-10 space-y-6 max-w-lg">
|
||
<h1 className="text-5xl font-bold leading-tight tracking-tight">
|
||
释放您的<br />
|
||
<span className="text-transparent bg-clip-text bg-linear-to-r from-blue-400 to-purple-400">无限创意潜能</span>
|
||
</h1>
|
||
<p className="text-lg text-gray-300 leading-relaxed">
|
||
加入数万名创作者的行列,利用最先进的 AI 技术,将您的想法瞬间转化为现实。
|
||
</p>
|
||
</div>
|
||
|
||
<div className="relative z-10 text-sm text-gray-400">
|
||
© 2024 AOUN AI. All rights reserved.
|
||
</div>
|
||
</div>
|
||
|
||
{/* Right Panel - Login Form */}
|
||
<div className="flex items-center justify-center p-8 bg-white">
|
||
<div className="w-full max-w-md space-y-8">
|
||
<div className="text-center space-y-2">
|
||
<h2 className="text-3xl font-bold tracking-tight text-gray-900">欢迎回来</h2>
|
||
<p className="text-gray-500">请输入您的账号信息以继续</p>
|
||
</div>
|
||
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||
<FormField
|
||
control={form.control}
|
||
name="email"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>邮箱地址</FormLabel>
|
||
<FormControl>
|
||
<div className="relative">
|
||
<Mail className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||
<Input placeholder="name@example.com" className="pl-10 h-11" {...field} />
|
||
</div>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="password"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>密码</FormLabel>
|
||
<FormControl>
|
||
<div className="relative">
|
||
<Lock className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||
<Input type="password" placeholder="******" className="pl-10 h-11" {...field} />
|
||
</div>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox id="remember" />
|
||
<label
|
||
htmlFor="remember"
|
||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-500"
|
||
>
|
||
记住我
|
||
</label>
|
||
</div>
|
||
<Link href="#" className="text-sm font-medium text-primary hover:underline">
|
||
忘记密码?
|
||
</Link>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="p-3 rounded-md bg-red-50 text-red-500 text-sm text-center font-medium">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<Button type="submit" className="w-full h-11 text-base" disabled={isLoading}>
|
||
{isLoading ? (
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
) : (
|
||
<span className="flex items-center gap-2">
|
||
立即登录 <ArrowRight className="w-4 h-4" />
|
||
</span>
|
||
)}
|
||
</Button>
|
||
</form>
|
||
</Form>
|
||
|
||
<div className="relative">
|
||
<div className="absolute inset-0 flex items-center">
|
||
<span className="w-full border-t border-gray-100" />
|
||
</div>
|
||
<div className="relative flex justify-center text-xs uppercase">
|
||
<span className="bg-white px-2 text-gray-500">
|
||
还没有账号?
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="text-center">
|
||
<Link href="/auth/register" className="text-primary font-semibold hover:underline">
|
||
免费注册账号
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|