Files
iam-front/src/components/auth/login-form.tsx
2026-02-11 13:57:47 +08:00

156 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { CaptchaField } from "@/components/auth/captcha-field";
export function LoginForm(props: {
clientId: string;
tenantId: string;
callback: string;
initialEmail: string;
prefillPassword?: string;
disabled?: boolean;
externalError?: string | null;
onClearExternalError?: () => void;
captcha: string;
onCaptchaChange: (next: string) => void;
captchaKey: string;
onRefreshCaptcha: () => void;
}) {
const [email, setEmail] = React.useState(props.initialEmail);
const [password, setPassword] = React.useState("");
const [rememberMe, setRememberMe] = React.useState(
Boolean(props.initialEmail),
);
const [submitting, setSubmitting] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
setEmail(props.initialEmail);
setRememberMe(Boolean(props.initialEmail));
}, [props.initialEmail]);
React.useEffect(() => {
if (props.prefillPassword) {
setPassword(props.prefillPassword);
}
}, [props.prefillPassword]);
const missingParams = !props.clientId || !props.tenantId || !props.callback;
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
props.onClearExternalError?.();
setSubmitting(true);
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: props.clientId,
tenantId: props.tenantId,
callback: props.callback,
email,
password,
captcha: props.captcha,
rememberMe,
}),
});
const json = (await res.json()) as {
redirectTo?: string;
message?: string;
};
if (!res.ok || !json.redirectTo) {
throw new Error(json.message || "登录失败");
}
window.location.href = json.redirectTo;
} catch (err) {
setError(err instanceof Error ? err.message : "登录失败");
props.onCaptchaChange("");
props.onRefreshCaptcha();
} finally {
setSubmitting(false);
}
}
const disabled = Boolean(props.disabled) || submitting;
return (
<form onSubmit={onSubmit} className="space-y-4">
{missingParams ? (
<div className="text-sm text-destructive">
clientIdtenantId callback
</div>
) : null}
{props.externalError ? (
<div className="text-sm text-destructive">{props.externalError}</div>
) : null}
{error ? <div className="text-sm text-destructive">{error}</div> : null}
<div className="space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
autoComplete="username"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="user@example.com"
disabled={disabled}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password"></Label>
<Input
id="password"
type="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={disabled}
required
/>
</div>
<CaptchaField
captcha={props.captcha}
onCaptchaChange={props.onCaptchaChange}
captchaKey={props.captchaKey}
onRefresh={props.onRefreshCaptcha}
disabled={disabled}
/>
<div className="flex items-center justify-between">
<label className="flex items-center gap-2 text-sm">
<Checkbox
checked={rememberMe}
onCheckedChange={(v) => setRememberMe(Boolean(v))}
disabled={disabled}
/>
</label>
<Link
className="text-sm underline underline-offset-4"
href={`/forgot-password?tenantId=${encodeURIComponent(props.tenantId)}`}
>
</Link>
</div>
<Button
type="submit"
className="w-full"
disabled={disabled || missingParams}
>
{submitting ? "登录中..." : "登录"}
</Button>
</form>
);
}