feat: sync account freeze lifecycle
This commit is contained in:
parent
76bfc5100f
commit
47fcf0ea41
40 changed files with 1363 additions and 196 deletions
File diff suppressed because one or more lines are too long
8
cmd/telesrv-admin/web/dist/assets/index-DRWO_DgE.js
vendored
Normal file
8
cmd/telesrv-admin/web/dist/assets/index-DRWO_DgE.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
cmd/telesrv-admin/web/dist/index.html
vendored
2
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>telesrv admin</title>
|
||||
<script type="module" crossorigin src="/assets/index-BHnkZ_za.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DRWO_DgE.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-W1-UPxwc.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -127,15 +127,21 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"account.waitingData": "Waiting for data",
|
||||
"account.noUsername": "No username",
|
||||
"account.noPhone": "No phone",
|
||||
"account.sendFrozen": "Sending frozen",
|
||||
"account.sendNormal": "Sending allowed",
|
||||
"account.accountFrozen": "Account frozen",
|
||||
"account.accountActive": "Account active",
|
||||
"account.authorizationsTitle": "Authorized Devices",
|
||||
"account.authorizationsCount": "{count} authorizations",
|
||||
"account.recentAdminOps": "Recent Admin Actions",
|
||||
"account.recent30Audit": "Last 30 audit rows",
|
||||
"account.actionDock": "Account Actions",
|
||||
"account.freezeSend": "Freeze sending",
|
||||
"account.unfreezeSend": "Unfreeze sending",
|
||||
"account.freezeAccount": "Freeze account",
|
||||
"account.updateFreeze": "Update freeze",
|
||||
"account.unfreezeAccount": "Unfreeze account",
|
||||
"account.freezeSince": "Frozen since",
|
||||
"account.freezeUntil": "Appeal deadline",
|
||||
"account.freezeUntilAria": "Freeze appeal deadline",
|
||||
"account.freezeAppealURL": "Appeal URL",
|
||||
"account.freezeAppealURLAria": "Freeze appeal URL",
|
||||
"account.premiumMonths": "Premium duration (months)",
|
||||
"account.premiumMonthsAria": "Set premium duration in months",
|
||||
"account.setPremium": "Set premium",
|
||||
|
|
@ -392,15 +398,21 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"account.waitingData": "等待数据",
|
||||
"account.noUsername": "无用户名",
|
||||
"account.noPhone": "无手机号",
|
||||
"account.sendFrozen": "发消息冻结",
|
||||
"account.sendNormal": "发送正常",
|
||||
"account.accountFrozen": "账号已冻结",
|
||||
"account.accountActive": "账号正常",
|
||||
"account.authorizationsTitle": "授权设备",
|
||||
"account.authorizationsCount": "共 {count} 个授权",
|
||||
"account.recentAdminOps": "最近后台操作",
|
||||
"account.recent30Audit": "最近 30 条审计",
|
||||
"account.actionDock": "账号操作",
|
||||
"account.freezeSend": "冻结发消息",
|
||||
"account.unfreezeSend": "解冻发消息",
|
||||
"account.freezeAccount": "冻结账号",
|
||||
"account.updateFreeze": "更新冻结信息",
|
||||
"account.unfreezeAccount": "解冻账号",
|
||||
"account.freezeSince": "冻结开始时间",
|
||||
"account.freezeUntil": "申诉截止时间",
|
||||
"account.freezeUntilAria": "账号冻结申诉截止时间",
|
||||
"account.freezeAppealURL": "申诉链接",
|
||||
"account.freezeAppealURLAria": "账号冻结申诉链接",
|
||||
"account.premiumMonths": "会员时长(月)",
|
||||
"account.premiumMonthsAria": "设置会员时长,单位月",
|
||||
"account.setPremium": "设置会员",
|
||||
|
|
|
|||
|
|
@ -16,12 +16,21 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
|
|||
const [busy, setBusy] = useState(false);
|
||||
const [months, setMonths] = useState("1");
|
||||
const [starsAmount, setStarsAmount] = useState("1000");
|
||||
const [freezeUntil, setFreezeUntil] = useState(() => toDateTimeLocal(new Date(Date.now() + 7 * 86400_000)));
|
||||
const [freezeAppealURL, setFreezeAppealURL] = useState("");
|
||||
|
||||
async function load() {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
setDetail(await api.account(id));
|
||||
const next = await api.account(id);
|
||||
setDetail(next);
|
||||
if (next.Restriction.Frozen) {
|
||||
if (next.Restriction.Until) {
|
||||
setFreezeUntil(toDateTimeLocal(new Date(next.Restriction.Until)));
|
||||
}
|
||||
setFreezeAppealURL(next.Restriction.AppealURL || "");
|
||||
}
|
||||
} catch (err) {
|
||||
setError(errorMessage(err));
|
||||
} finally {
|
||||
|
|
@ -58,7 +67,7 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
|
|||
<div className="entity-badges">
|
||||
{account.PremiumUntil > 0 ? <Badge tone="good">{t("account.premium")}</Badge> : <Badge>{t("account.notPremium")}</Badge>}
|
||||
{detail.Verified ? <Badge tone="good">{t("common.verified")}</Badge> : <Badge>{t("account.notVerified")}</Badge>}
|
||||
{account.Frozen ? <Badge tone="danger">{t("account.sendFrozen")}</Badge> : <Badge>{t("account.sendNormal")}</Badge>}
|
||||
{account.Frozen ? <Badge tone="danger">{t("account.accountFrozen")}</Badge> : <Badge>{t("account.accountActive")}</Badge>}
|
||||
</div>
|
||||
</section>
|
||||
<div className="summary-grid">
|
||||
|
|
@ -70,6 +79,9 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
|
|||
<Summary label={t("account.activeSessions")} value={String(detail.Authorizations.length)} />
|
||||
<Summary label={t("account.accountFlags")} value={`support=${detail.Support} bot=${detail.Bot}`} />
|
||||
<Summary label={t("account.restriction")} value={detail.HasRestriction ? detail.Restriction.Reason || t("account.restricted") : t("common.none")} />
|
||||
<Summary label={t("account.freezeSince")} value={detail.Restriction.Since ? formatDate(detail.Restriction.Since) : t("common.none")} />
|
||||
<Summary label={t("account.freezeUntil")} value={detail.Restriction.Until ? formatDate(detail.Restriction.Until) : t("common.none")} />
|
||||
<Summary label={t("account.freezeAppealURL")} value={detail.Restriction.AppealURL || t("common.none")} />
|
||||
<Summary label={t("account.createdAt")} value={formatDate(account.CreatedAt) || "-"} />
|
||||
</div>
|
||||
{detail.About && <p className="about-text">{detail.About}</p>}
|
||||
|
|
@ -86,13 +98,46 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
|
|||
side={
|
||||
<section className="action-dock">
|
||||
<div className="dock-title">{t("account.actionDock")}</div>
|
||||
<label className="duration-field">
|
||||
<span>{t("account.freezeUntil")}</span>
|
||||
<input
|
||||
aria-label={t("account.freezeUntilAria")}
|
||||
value={freezeUntil}
|
||||
onChange={(event) => setFreezeUntil(event.target.value)}
|
||||
type="datetime-local"
|
||||
/>
|
||||
</label>
|
||||
<label className="duration-field">
|
||||
<span>{t("account.freezeAppealURL")}</span>
|
||||
<input
|
||||
aria-label={t("account.freezeAppealURLAria")}
|
||||
value={freezeAppealURL}
|
||||
onChange={(event) => setFreezeAppealURL(event.target.value)}
|
||||
type="url"
|
||||
placeholder="https://..."
|
||||
/>
|
||||
</label>
|
||||
<ActionButton
|
||||
label={account.Frozen ? t("account.unfreezeSend") : t("account.freezeSend")}
|
||||
label={account.Frozen ? t("account.updateFreeze") : t("account.freezeAccount")}
|
||||
icon={<CircleAlert size={15} />}
|
||||
path="/api/actions/freeze-send"
|
||||
payload={() => ({ user_id: account.ID, frozen: !account.Frozen })}
|
||||
path="/api/actions/set-frozen"
|
||||
payload={() => ({
|
||||
user_id: account.ID,
|
||||
frozen: true,
|
||||
freeze_until: new Date(freezeUntil).toISOString(),
|
||||
freeze_appeal_url: freezeAppealURL.trim()
|
||||
})}
|
||||
onDone={load}
|
||||
/>
|
||||
{account.Frozen && (
|
||||
<ActionButton
|
||||
label={t("account.unfreezeAccount")}
|
||||
icon={<CircleAlert size={15} />}
|
||||
path="/api/actions/set-frozen"
|
||||
payload={() => ({ user_id: account.ID, frozen: false })}
|
||||
onDone={load}
|
||||
/>
|
||||
)}
|
||||
<label className="duration-field">
|
||||
<span>{t("account.premiumMonths")}</span>
|
||||
<input
|
||||
|
|
@ -155,3 +200,8 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
|
|||
</PageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
function toDateTimeLocal(date: Date): string {
|
||||
const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);
|
||||
return local.toISOString().slice(0, 16);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ export type AccountRow = {
|
|||
|
||||
export type RestrictionRow = {
|
||||
Frozen: boolean;
|
||||
Since: string | null;
|
||||
Until: string | null;
|
||||
AppealURL: string;
|
||||
Reason: string;
|
||||
Actor: string;
|
||||
CommandID: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue