149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import type { AccountDeletionRequestRecord } from "@/db/repository";
|
|
|
|
type SubmitState = { loading: boolean; message: string; error: boolean };
|
|
const idle: SubmitState = { loading: false, message: "", error: false };
|
|
|
|
async function postDeletion(payload: Record<string, unknown>) {
|
|
const response = await fetch("/api/account/deletion", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
...payload,
|
|
idempotencyKey: crypto.randomUUID(),
|
|
}),
|
|
});
|
|
const body = (await response.json()) as { message?: string };
|
|
if (!response.ok) throw new Error(body.message || "操作失败");
|
|
}
|
|
|
|
function formatRequestDate(value: string) {
|
|
return new Intl.DateTimeFormat("zh-CN", {
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
}).format(new Date(value));
|
|
}
|
|
|
|
export function AccountDeletionAction({
|
|
requests,
|
|
}: {
|
|
requests: AccountDeletionRequestRecord[];
|
|
}) {
|
|
const active = requests.find((request) => request.status === "requested");
|
|
const processing = requests.find((request) =>
|
|
request.status === "processing" || request.status === "relay_purged"
|
|
);
|
|
const [confirmed, setConfirmed] = useState(false);
|
|
const [reason, setReason] = useState("");
|
|
const [state, setState] = useState(idle);
|
|
|
|
async function submitRequest(event: React.FormEvent) {
|
|
event.preventDefault();
|
|
setState({ loading: true, message: "", error: false });
|
|
try {
|
|
await postDeletion({ action: "request", confirmed, reason });
|
|
setState({ loading: false, message: "注销申请已记录。", error: false });
|
|
window.setTimeout(() => window.location.reload(), 700);
|
|
} catch (error) {
|
|
setState({
|
|
loading: false,
|
|
message: error instanceof Error ? error.message : "操作失败",
|
|
error: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
async function cancelRequest() {
|
|
if (!active) return;
|
|
setState({ loading: true, message: "", error: false });
|
|
try {
|
|
await postDeletion({ action: "cancel", requestId: active.id });
|
|
setState({ loading: false, message: "注销申请已撤回。", error: false });
|
|
window.setTimeout(() => window.location.reload(), 700);
|
|
} catch (error) {
|
|
setState({
|
|
loading: false,
|
|
message: error instanceof Error ? error.message : "操作失败",
|
|
error: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (active) {
|
|
return (
|
|
<div className="deletion-request-state">
|
|
<span>待人工核对</span>
|
|
<small>{formatRequestDate(active.requested_at)}</small>
|
|
{active.reason && <p>{active.reason}</p>}
|
|
<button
|
|
className="button button-secondary"
|
|
type="button"
|
|
onClick={cancelRequest}
|
|
disabled={state.loading}
|
|
>
|
|
{state.loading ? "正在撤回…" : "撤回注销申请"}
|
|
</button>
|
|
{state.message && (
|
|
<p className={state.error ? "form-error" : "form-success"} role="status">
|
|
{state.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (processing) {
|
|
return (
|
|
<div className="deletion-request-state">
|
|
<span>{processing.status === "relay_purged" ? "Relay 数据已逻辑删除" : "正在永久删除 Relay 数据"}</span>
|
|
<small>{formatRequestDate(processing.requested_at)}</small>
|
|
<p>
|
|
{processing.status === "relay_purged"
|
|
? "实时 Relay 数据、附件与备份已删除;账户身份和依法保留记录仍按最终退出政策处理。"
|
|
: "访问已经暂停,删除期间不能撤回申请。"}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form className="deletion-request-form" onSubmit={submitRequest}>
|
|
<label>
|
|
<span>补充说明(可选)</span>
|
|
<textarea
|
|
maxLength={500}
|
|
value={reason}
|
|
onChange={(event) => setReason(event.target.value)}
|
|
placeholder="例如:暂时不再参加公测"
|
|
/>
|
|
</label>
|
|
<label className="deletion-confirmation">
|
|
<input
|
|
type="checkbox"
|
|
checked={confirmed}
|
|
onChange={(event) => setConfirmed(event.target.checked)}
|
|
required
|
|
/>
|
|
<span>我知道申请不会立即删除主机上的原生会话,并可在处理前撤回。</span>
|
|
</label>
|
|
<button
|
|
className="button button-secondary"
|
|
type="submit"
|
|
disabled={state.loading || !confirmed}
|
|
>
|
|
{state.loading ? "正在提交…" : "提交注销申请"}
|
|
</button>
|
|
{state.message && (
|
|
<p className={state.error ? "form-error" : "form-success"} role="status">
|
|
{state.message}
|
|
</p>
|
|
)}
|
|
</form>
|
|
);
|
|
}
|