Commit 24b905e2 authored by yaoke.yk's avatar yaoke.yk

登录页面优化

parent 3db44894
import { useState } from "react";
import { type FormEvent, useState } from "react";
import { Link } from "react-router";
import { ArrowRight, Eye, EyeOff, Film, Loader2, LockKeyhole, Mail, ShieldCheck, Sparkles } from "lucide-react";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { useAuth } from "../../hooks/useAuth";
import loginAiStudioBg from "../../assets/login-ai-studio-bg.png";
export function LoginPage() {
const { login, loginPending, loginError } = useAuth();
const [form, setForm] = useState({ email: "", password: "" });
const [formError, setFormError] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const visibleError =
formError ?? (loginError instanceof Error ? loginError.message : loginError ? "登录失败,请稍后重试" : null);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const email = form.email.trim();
const password = form.password.trim();
const password = form.password;
if (!email || !password) {
setFormError("请输入邮箱和密码");
......@@ -25,63 +30,152 @@ export function LoginPage() {
login({ email, password });
};
const updateField = (field: "email" | "password", value: string) => {
setFormError(null);
setForm((current) => ({ ...current, [field]: value }));
};
return (
<div className="min-h-screen flex items-center justify-center bg-background px-4">
<Card className="w-full max-w-sm">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Dramix</CardTitle>
<CardDescription>登录您的账号</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
<main className="relative min-h-dvh overflow-hidden bg-[#090d18] text-white">
<div
aria-hidden="true"
className="absolute inset-0 bg-cover bg-[46%_center] opacity-95"
style={{ backgroundImage: `url(${loginAiStudioBg})` }}
/>
<div
aria-hidden="true"
className="absolute inset-0 bg-[linear-gradient(90deg,rgba(5,8,17,0.66)_0%,rgba(8,12,24,0.30)_42%,rgba(8,12,24,0.62)_70%,rgba(5,8,17,0.92)_100%)]"
/>
<div aria-hidden="true" className="absolute inset-x-0 bottom-0 h-1/3 bg-[linear-gradient(0deg,rgba(6,9,17,0.78),rgba(6,9,17,0))]" />
<div className="relative z-10 flex min-h-dvh flex-col px-5 py-6 sm:px-8 lg:px-12">
<Link to="/" className="flex w-fit items-center gap-2 text-white" aria-label="返回 Dramix.ai 首页">
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-white/14 shadow-[0_12px_28px_rgba(0,0,0,0.22)] ring-1 ring-white/18 backdrop-blur">
<Film className="h-5 w-5 text-white" />
</span>
<span className="text-lg font-semibold tracking-normal">Dramix.ai</span>
</Link>
<section className="grid flex-1 items-center gap-10 py-10 lg:grid-cols-[minmax(420px,0.95fr)_minmax(380px,460px)_minmax(48px,0.28fr)] lg:py-14">
<div className="hidden max-w-[680px] lg:block">
<div className="inline-flex items-center gap-2 rounded-full border border-white/16 bg-black/18 px-3 py-1.5 text-sm text-[#dbeafe] backdrop-blur">
<Sparkles className="h-4 w-4 text-[#7dd3fc]" />
从灵感到成片的一体化流程
</div>
<h1 className="mt-5 text-[48px] font-semibold leading-[1.06] tracking-normal text-white xl:text-[64px]">
让每个短剧项目都有清晰的制作节奏。
</h1>
<p className="mt-5 max-w-[560px] text-base leading-8 text-[#d7e0ee]">
在控制台里完成剧本拆解、角色设定、分镜审阅和视频生成,团队可以围绕同一套素材持续迭代。
</p>
</div>
<div className="w-full max-w-[460px] justify-self-center rounded-2xl border border-white/18 bg-white/[0.12] p-5 shadow-[0_28px_80px_rgba(0,0,0,0.34)] backdrop-blur-2xl sm:p-7">
<div className="mb-7">
<div className="mb-5 inline-flex items-center gap-2 rounded-full border border-white/16 bg-white/12 px-3 py-1.5 text-sm font-medium text-[#e5eefc]">
<ShieldCheck className="h-4 w-4 text-[#6ce9a6]" />
AI 漫剧平台控制台
</div>
<h2 className="text-[32px] font-semibold leading-tight tracking-normal text-white sm:text-[38px]">
登录工作台
</h2>
<p className="mt-3 text-base leading-7 text-[#d7e0ee]">
管理剧本、角色、分镜和视频生成任务,继续推进你的短剧项目。
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-5" noValidate>
<div className="space-y-2">
<Label htmlFor="email">邮箱</Label>
<Label htmlFor="email" className="text-sm font-semibold text-[#f2f4f7]">
邮箱
</Label>
<div className="relative">
<Mail className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#98a2b3]" />
<Input
id="email"
type="email"
placeholder="you@example.com"
autoComplete="email"
inputMode="email"
placeholder="admin@yaoai.com"
required
aria-invalid={Boolean(visibleError)}
aria-describedby={visibleError ? "login-error" : undefined}
className="h-12 rounded-lg border-white/18 bg-white/92 pl-10 text-[16px] text-[#101828] shadow-[0_1px_2px_rgba(16,24,40,0.08)] placeholder:text-[#98a2b3] focus-visible:border-[#8f7bff] focus-visible:ring-[#8f7bff]/30"
value={form.email}
onChange={(e) => {
setFormError(null);
setForm((f) => ({ ...f, email: e.target.value }));
}}
onChange={(event) => updateField("email", event.target.value)}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">密码</Label>
<Label htmlFor="password" className="text-sm font-semibold text-[#f2f4f7]">
密码
</Label>
<div className="relative">
<LockKeyhole className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#98a2b3]" />
<Input
id="password"
type="password"
placeholder="••••••••"
type={showPassword ? "text" : "password"}
autoComplete="current-password"
placeholder="请输入密码"
required
aria-invalid={Boolean(visibleError)}
aria-describedby={visibleError ? "login-error" : undefined}
className="h-12 rounded-lg border-white/18 bg-white/92 pl-10 pr-12 text-[16px] text-[#101828] shadow-[0_1px_2px_rgba(16,24,40,0.08)] placeholder:text-[#98a2b3] focus-visible:border-[#8f7bff] focus-visible:ring-[#8f7bff]/30"
value={form.password}
onChange={(e) => {
setFormError(null);
setForm((f) => ({ ...f, password: e.target.value }));
}}
onChange={(event) => updateField("password", event.target.value)}
/>
<button
type="button"
aria-label={showPassword ? "隐藏密码" : "显示密码"}
onClick={() => setShowPassword((value) => !value)}
className="absolute right-2 top-1/2 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-md text-[#667085] transition hover:bg-[#eef2f6] hover:text-[#344054] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8f7bff]/40"
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
{(formError || loginError) && (
<p className="text-sm text-destructive">
{formError ?? (loginError instanceof Error ? loginError.message : "登录失败")}
</p>
{visibleError && (
<div
id="login-error"
role="alert"
className="rounded-lg border border-[#fecdca] bg-[#fff4f3] px-3.5 py-3 text-sm leading-6 text-[#b42318]"
>
{visibleError}
</div>
)}
<Button type="submit" className="w-full" disabled={loginPending}>
{loginPending ? "登录中..." : "登录"}
<Button
type="submit"
className="h-12 w-full rounded-lg bg-[#8064f1] text-base font-semibold text-white shadow-[0_16px_36px_rgba(79,70,229,0.34)] transition hover:bg-[#7357e8] focus-visible:ring-[#a99bff]/40"
disabled={loginPending}
>
{loginPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
登录中
</>
) : (
<>
登录
<ArrowRight className="h-4 w-4" />
</>
)}
</Button>
</form>
<p className="text-center text-sm text-muted-foreground">
<p className="mt-7 text-center text-sm text-[#d7e0ee]">
没有账号?{" "}
<Link to="/register" className="text-primary underline-offset-4 hover:underline">
立即注册
<Link to="/register" className="font-semibold text-white underline-offset-4 hover:underline">
创建账号
</Link>
</p>
</form>
</CardContent>
</Card>
</div>
</section>
<p className="relative z-10 text-xs leading-5 text-[#b8c2d3] lg:text-right">© 2026 Dramix.ai</p>
</div>
</main>
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment