fix(css): fix version
This commit is contained in:
150
src/components/auth/login-form.tsx
Normal file
150
src/components/auth/login-form.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
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">
|
||||
缺少 clientId、tenantId 或 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user