Files
tiku-saas-web/src/tenant/ActivationPage.tsx

150 lines
4.8 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 { useEffect, useState } from "react";
import { Alert, Button, Card, Form, Input, Typography } from "antd";
import { KeyRound } from "lucide-react";
import { useNavigate, useParams } from "react-router";
import { ApiError, authApi } from "../api";
import { useRuntime } from "../app/RuntimeProvider";
interface FormValues {
password: string;
confirm: string;
}
function readAndClearToken(activationId: string): string {
const storageKey = `tiku-activation:${activationId}`;
const fragment = new URLSearchParams(window.location.hash.replace(/^#/, ""));
const fromUrl = fragment.get("token");
if (fromUrl) sessionStorage.setItem(storageKey, fromUrl);
if (window.location.hash)
history.replaceState(
null,
"",
window.location.pathname + window.location.search,
);
return fromUrl || sessionStorage.getItem(storageKey) || "";
}
const errorMessages: Record<string, string> = {
activation_invalid: "建站密钥无效,请联系平台重新获取。",
activation_expired: "建站密钥已经过期,请联系平台重新签发。",
activation_consumed: "建站密钥已经使用,不能再次激活。",
activation_password_invalid: "密码至少 8 位,并同时包含字母和数字。",
};
export default function ActivationPage() {
const { activationId = "" } = useParams();
const { runtime } = useRuntime();
const navigate = useNavigate();
const [token, setToken] = useState("");
const [error, setError] = useState("");
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
setToken(readAndClearToken(activationId));
}, [activationId]);
const submit = async (values: FormValues) => {
if (!token) {
setError("链接中没有可用的建站密钥,请联系平台重新获取。");
return;
}
setSubmitting(true);
setError("");
try {
await authApi.completeOwnerActivation({
activationId,
token,
newPassword: values.password,
});
sessionStorage.removeItem(`tiku-activation:${activationId}`);
navigate("/manage/onboarding", { replace: true });
} catch (reason) {
const code = reason instanceof ApiError ? reason.code : "";
setError(errorMessages[code] ?? "激活失败,请联系平台服务人员。");
} finally {
setSubmitting(false);
}
};
return (
<div className="min-h-screen grid place-items-center bg-slate-950 p-5">
<Card
className="w-full max-w-md shadow-2xl"
styles={{ body: { padding: 32 } }}
>
<div className="mb-7 flex items-center gap-4">
<div
className="grid h-12 w-12 place-items-center rounded-2xl text-white"
style={{ background: runtime?.theme.primaryColor }}
>
<KeyRound size={24} />
</div>
<div>
<Typography.Text type="secondary">
{runtime?.tenantName}
</Typography.Text>
<Typography.Title level={3} style={{ margin: 0 }}>
</Typography.Title>
</div>
</div>
<Typography.Paragraph type="secondary">
使
</Typography.Paragraph>
{!token && (
<Alert
className="mb-5"
type="warning"
showIcon
title="缺少建站密钥"
/>
)}
{error && (
<Alert className="mb-5" type="error" showIcon title={error} />
)}
<Form layout="vertical" onFinish={submit} requiredMark={false}>
<Form.Item
name="password"
label="管理员密码"
rules={[
{ required: true },
{ min: 8 },
{
pattern: /^(?=.*[A-Za-z])(?=.*\d).+$/,
message: "必须同时包含字母和数字",
},
]}
>
<Input.Password autoComplete="new-password" size="large" />
</Form.Item>
<Form.Item
name="confirm"
label="确认密码"
dependencies={["password"]}
rules={[
{ required: true },
({ getFieldValue }) => ({
validator: (_, value) =>
value === getFieldValue("password")
? Promise.resolve()
: Promise.reject(new Error("两次密码不一致")),
}),
]}
>
<Input.Password autoComplete="new-password" size="large" />
</Form.Item>
<Button
block
size="large"
type="primary"
htmlType="submit"
loading={submitting}
>
</Button>
</Form>
</Card>
</div>
);
}