168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
|
|
import { LoginForm } from "@/components/auth/login-form";
|
|
import { RegisterForm } from "@/components/auth/register-form";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
|
|
const LoginFormCard = (props: {
|
|
clientId: string;
|
|
tenantId: string;
|
|
callback: string;
|
|
initialEmail: string;
|
|
}) => {
|
|
const [tab, setTab] = React.useState<"login" | "register">("login");
|
|
const [captcha, setCaptcha] = React.useState("");
|
|
const [captchaKey, setCaptchaKey] = React.useState(() => String(Date.now()));
|
|
const [loginExternalError, setLoginExternalError] = React.useState<
|
|
string | null
|
|
>(null);
|
|
const [loginPrefill, setLoginPrefill] = React.useState<{
|
|
email: string;
|
|
password: string;
|
|
} | null>(null);
|
|
|
|
function refreshCaptcha() {
|
|
setCaptchaKey(String(Date.now()));
|
|
}
|
|
|
|
async function loginTokenAndStore(payload: {
|
|
email: string;
|
|
password: string;
|
|
}) {
|
|
const tokenRes = await fetch("/api/auth/login-token", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
tenantId: props.tenantId,
|
|
email: payload.email,
|
|
password: payload.password,
|
|
captcha,
|
|
}),
|
|
});
|
|
const tokenJson = (await tokenRes.json()) as {
|
|
access_token?: string;
|
|
refresh_token?: string;
|
|
token_type?: string;
|
|
expires_in?: number;
|
|
message?: string;
|
|
};
|
|
if (!tokenRes.ok || !tokenJson.access_token || !tokenJson.refresh_token) {
|
|
throw new Error(tokenJson.message || "登录失败");
|
|
}
|
|
|
|
try {
|
|
sessionStorage.setItem("iam_access_token", tokenJson.access_token);
|
|
sessionStorage.setItem("iam_refresh_token", tokenJson.refresh_token);
|
|
if (tokenJson.token_type) {
|
|
sessionStorage.setItem("iam_token_type", tokenJson.token_type);
|
|
}
|
|
if (typeof tokenJson.expires_in === "number") {
|
|
sessionStorage.setItem("iam_expires_in", String(tokenJson.expires_in));
|
|
}
|
|
sessionStorage.setItem("iam_token_issued_at", String(Date.now()));
|
|
} catch {}
|
|
}
|
|
|
|
async function loginAndRedirect(payload: {
|
|
email: string;
|
|
password: string;
|
|
}) {
|
|
await loginTokenAndStore(payload);
|
|
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: payload.email,
|
|
password: payload.password,
|
|
captcha,
|
|
rememberMe: true,
|
|
}),
|
|
});
|
|
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;
|
|
}
|
|
|
|
return (
|
|
<Card className="w-full max-w-md">
|
|
<CardHeader>
|
|
<CardTitle>统一登录</CardTitle>
|
|
<CardDescription>使用 IAM 账号登录以访问业务系统</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs
|
|
value={tab}
|
|
onValueChange={(v) => setTab(v as "login" | "register")}
|
|
className="w-full"
|
|
>
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="login">登录</TabsTrigger>
|
|
<TabsTrigger value="register">注册</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="login">
|
|
<LoginForm
|
|
clientId={props.clientId}
|
|
tenantId={props.tenantId}
|
|
callback={props.callback}
|
|
initialEmail={loginPrefill?.email ?? props.initialEmail}
|
|
prefillPassword={loginPrefill?.password}
|
|
externalError={loginExternalError}
|
|
onClearExternalError={() => setLoginExternalError(null)}
|
|
captcha={captcha}
|
|
onCaptchaChange={setCaptcha}
|
|
captchaKey={captchaKey}
|
|
onRefreshCaptcha={refreshCaptcha}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="register">
|
|
<RegisterForm
|
|
tenantId={props.tenantId}
|
|
captcha={captcha}
|
|
onCaptchaChange={setCaptcha}
|
|
captchaKey={captchaKey}
|
|
onRefreshCaptcha={refreshCaptcha}
|
|
onRegisterSuccessPrefillLogin={(p) => {
|
|
setLoginPrefill(p);
|
|
}}
|
|
onLoginAfterRegister={async (p) => {
|
|
try {
|
|
await loginAndRedirect(p);
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : "登录失败";
|
|
setLoginExternalError(
|
|
`注册成功,但登录失败:${msg}。请在“登录”页修正验证码后继续。`,
|
|
);
|
|
setTab("login");
|
|
setCaptcha("");
|
|
refreshCaptcha();
|
|
}
|
|
}}
|
|
/>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default LoginFormCard;
|