173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Modal from "@/components/Modal";
|
||
import { POST_DELETE_TYPES } from "@/lib/api";
|
||
|
||
export type DeleteCommentPayload = {
|
||
delete_type: string;
|
||
delete_reason: string;
|
||
};
|
||
|
||
/**
|
||
* 评论软删弹窗:
|
||
* - 评论者自删:必填理由(仅管理可见)
|
||
* - 管理删:必选类型;仅「其他」须填理由
|
||
*/
|
||
export default function DeleteCommentModal({
|
||
open,
|
||
mode,
|
||
busy,
|
||
onClose,
|
||
onConfirm,
|
||
}: {
|
||
open: boolean;
|
||
/** self=评论者自删;staff=版主/后台删除 */
|
||
mode: "self" | "staff";
|
||
busy?: boolean;
|
||
onClose: () => void;
|
||
onConfirm: (opts: DeleteCommentPayload) => void;
|
||
}) {
|
||
const [deleteType, setDeleteType] = useState("spam");
|
||
const [reason, setReason] = useState("");
|
||
const [err, setErr] = useState("");
|
||
|
||
useEffect(() => {
|
||
if (open) {
|
||
setDeleteType("spam");
|
||
setReason("");
|
||
setErr("");
|
||
}
|
||
}, [open]);
|
||
|
||
if (!open) return null;
|
||
|
||
const isStaff = mode === "staff";
|
||
const reasonRequired = !isStaff || deleteType === "other";
|
||
|
||
const submit = () => {
|
||
const r = reason.trim();
|
||
if (reasonRequired) {
|
||
if (!r) {
|
||
setErr("请填写删除理由");
|
||
return;
|
||
}
|
||
if ([...r].length > 200) {
|
||
setErr("删除理由不能超过 200 字");
|
||
return;
|
||
}
|
||
}
|
||
onConfirm({
|
||
delete_type: isStaff ? deleteType : "",
|
||
delete_reason: reasonRequired ? r : "",
|
||
});
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
open
|
||
title={isStaff ? "删除评论" : "删除我的评论"}
|
||
description={
|
||
isStaff
|
||
? "评论区保留匿名马赛克占位,读者看不到评论者头像与名称;可见删除类型,「其他」时还会展示补充理由。子回复不受影响。"
|
||
: "评论区保留马赛克占位,读者仅见「评论者已删除此评论」。你填写的理由仅管理员可见。"
|
||
}
|
||
onClose={onClose}
|
||
busy={busy}
|
||
maxWidthClass="max-w-[440px]"
|
||
panelClassName="p-5"
|
||
ariaLabel={isStaff ? "删除评论" : "删除我的评论"}
|
||
footer={
|
||
<>
|
||
<button type="button" className="btn btn-line" onClick={onClose} disabled={busy}>
|
||
取消
|
||
</button>
|
||
<button type="button" className="btn btn-danger" onClick={submit} disabled={busy}>
|
||
{busy ? "删除中…" : "确认删除"}
|
||
</button>
|
||
</>
|
||
}
|
||
>
|
||
{isStaff ? (
|
||
<>
|
||
<p className="mb-2 text-[12.5px] font-medium" style={{ color: "var(--ink)" }}>
|
||
删除类型
|
||
</p>
|
||
<div
|
||
className="grid grid-cols-1 sm:grid-cols-2 gap-1.5 mb-1"
|
||
role="radiogroup"
|
||
aria-label="删除类型"
|
||
>
|
||
{POST_DELETE_TYPES.map((t) => {
|
||
const active = deleteType === t.value;
|
||
return (
|
||
<button
|
||
key={t.value}
|
||
type="button"
|
||
role="radio"
|
||
aria-checked={active}
|
||
onClick={() => {
|
||
setDeleteType(t.value);
|
||
setErr("");
|
||
if (t.value !== "other") setReason("");
|
||
}}
|
||
className="text-left rounded-lg px-3 py-2.5 text-[13px] transition-colors"
|
||
style={{
|
||
border: `1.5px solid ${active ? "var(--accent)" : "var(--line)"}`,
|
||
background: active ? "var(--accent-soft)" : "var(--panel-2)",
|
||
color: active ? "var(--accent)" : "var(--ink)",
|
||
}}
|
||
>
|
||
{t.label}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
<p className="meta text-[11.5px] mb-4 leading-relaxed">
|
||
选「其他」时需补充理由;其余类型直接按标签展示给读者。
|
||
</p>
|
||
</>
|
||
) : null}
|
||
|
||
{reasonRequired ? (
|
||
<>
|
||
<label
|
||
className="block mb-1.5 text-[12.5px] font-medium"
|
||
style={{ color: "var(--ink)" }}
|
||
htmlFor="cmt-delete-reason"
|
||
>
|
||
{isStaff ? "补充理由" : "删除理由"}{" "}
|
||
<span style={{ color: "var(--danger)" }}>*</span>
|
||
</label>
|
||
<textarea
|
||
id="cmt-delete-reason"
|
||
className="field"
|
||
rows={3}
|
||
value={reason}
|
||
onChange={(e) => {
|
||
setReason(e.target.value);
|
||
setErr("");
|
||
}}
|
||
placeholder={
|
||
isStaff
|
||
? "向读者说明删除原因(将展示在占位中)"
|
||
: "例如:发错了 / 不想保留(仅管理员可见)"
|
||
}
|
||
maxLength={200}
|
||
autoFocus={!isStaff}
|
||
/>
|
||
<p className="meta text-[11.5px] mt-1.5">
|
||
{isStaff ? "将公开显示在评论占位旁。" : "普通读者看不到这条理由。"}
|
||
</p>
|
||
</>
|
||
) : null}
|
||
|
||
{err ? (
|
||
<p className="mt-2 text-[12px]" style={{ color: "var(--danger)" }}>
|
||
{err}
|
||
</p>
|
||
) : null}
|
||
</Modal>
|
||
);
|
||
}
|