82 lines
3.1 KiB
TypeScript
82 lines
3.1 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import {
|
|
Field,
|
|
FieldDescription,
|
|
FieldGroup,
|
|
FieldLabel,
|
|
} from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
export function LoginForm({
|
|
className,
|
|
action,
|
|
error,
|
|
...props
|
|
}: React.ComponentProps<"div"> & {
|
|
action?: React.ComponentProps<"form">["action"];
|
|
error?: string;
|
|
}) {
|
|
return (
|
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
|
<Card>
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="text-xl">管理员登录</CardTitle>
|
|
<CardDescription>
|
|
使用管理员账号密码进入 Wallora 后台
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form action={action}>
|
|
<FieldGroup>
|
|
<Field data-invalid={Boolean(error)}>
|
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
placeholder="admin@example.com"
|
|
autoComplete="username"
|
|
aria-invalid={Boolean(error)}
|
|
required
|
|
/>
|
|
</Field>
|
|
<Field data-invalid={Boolean(error)}>
|
|
<FieldLabel htmlFor="password">
|
|
Password
|
|
</FieldLabel>
|
|
<Input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
aria-invalid={Boolean(error)}
|
|
required
|
|
/>
|
|
{error ? (
|
|
<FieldDescription className="text-destructive">
|
|
{error}
|
|
</FieldDescription>
|
|
) : null}
|
|
</Field>
|
|
<Field>
|
|
<Button type="submit">登录</Button>
|
|
</Field>
|
|
</FieldGroup>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
<FieldDescription className="px-6 text-center">
|
|
后台仅限授权管理员访问。登录状态由 NextAuth session 保存。
|
|
</FieldDescription>
|
|
</div>
|
|
);
|
|
}
|