Files
chat_room/admin/src/components/UserModerationActions.tsx
T
mobiniqow 75a0ba593d chore: merge admin repo history into monorepo at admin/
git-subtree-dir: admin
git-subtree-mainline: 35f5084005
git-subtree-split: 9f1eccd2f7
2026-09-27 10:30:47 +03:30

163 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
"use client";
import { useState } from "react";
import { ShieldAlert, ShieldCheck, Flag } from "lucide-react";
import { Button } from "@/components/ui/Button";
import { Modal } from "@/components/ui/Modal";
import { useToast } from "@/components/ui/Toast";
import { formatApiError } from "@/lib/hooks";
import { blockUser, unblockUser, reportUser, UserState } from "@/lib/api";
// دکمه‌های بلاک/آنبلاک/گزارش یک کاربر؛ هم توی لیست همه‌ی کاربران و هم توی
// صفحه‌ی جزئیات یک کاربر استفاده می‌شه.
export function UserModerationActions({
userId,
state,
onChanged,
}: {
userId: number;
state: UserState;
onChanged: () => void;
}) {
const toast = useToast();
const [blockOpen, setBlockOpen] = useState(false);
const [reportOpen, setReportOpen] = useState(false);
const [blockReason, setBlockReason] = useState("");
const [reportReason, setReportReason] = useState("");
const [loading, setLoading] = useState(false);
async function handleBlock() {
if (blockReason.trim().length < 3) return;
setLoading(true);
try {
await blockUser(userId, blockReason.trim());
toast.showSuccess("کاربر بلاک شد");
setBlockOpen(false);
setBlockReason("");
onChanged();
} catch (err) {
toast.showError(formatApiError(err));
} finally {
setLoading(false);
}
}
async function handleUnblock() {
setLoading(true);
try {
await unblockUser(userId);
toast.showSuccess("کاربر آنبلاک شد");
onChanged();
} catch (err) {
toast.showError(formatApiError(err));
} finally {
setLoading(false);
}
}
async function handleReport() {
if (reportReason.trim().length < 3) return;
setLoading(true);
try {
await reportUser(userId, reportReason.trim());
toast.showSuccess("گزارش ثبت شد");
setReportOpen(false);
setReportReason("");
onChanged();
} catch (err) {
toast.showError(formatApiError(err));
} finally {
setLoading(false);
}
}
return (
<>
<div className="flex items-center gap-2">
{state === "blocked" ? (
<Button
variant="tertiary"
size="sm"
icon={<ShieldCheck size={14} />}
isLoading={loading}
onClick={handleUnblock}
>
آنبلاک
</Button>
) : (
<Button
variant="tertiary"
size="sm"
icon={<ShieldAlert size={14} />}
className="!text-(--color-status-error) !border-red-200"
onClick={() => setBlockOpen(true)}
>
بلاک
</Button>
)}
<Button variant="ghost" size="sm" icon={<Flag size={14} />} onClick={() => setReportOpen(true)}>
گزارش
</Button>
</div>
<Modal open={blockOpen} onClose={() => setBlockOpen(false)} title="بلاک کردن کاربر">
<div className="flex flex-col gap-4">
<p className="text-[13px] text-(--color-content-secondary)">
کاربر بلاک‌شده دیگه نمی‌تونه وارد اپ بشه (حتی با توکن معتبر فعلیش).
</p>
<textarea
value={blockReason}
onChange={(e) => setBlockReason(e.target.value)}
rows={3}
placeholder="دلیل بلاک کردن..."
className="rounded-(--radius-sm) border-2 border-(--color-border-default) bg-(--color-surface-default) px-4 py-3 text-[14px] focus:outline-none focus:border-(--color-border-interactive) focus:ring-4 focus:ring-(--color-brand-100) resize-none"
/>
<div className="flex justify-end gap-2">
<Button variant="ghost" size="sm" onClick={() => setBlockOpen(false)}>
انصراف
</Button>
<Button
size="sm"
isLoading={loading}
disabled={blockReason.trim().length < 3}
className="!bg-(--color-status-error) !shadow-none hover:!bg-red-700"
onClick={handleBlock}
>
بلاک کن
</Button>
</div>
</div>
</Modal>
<Modal open={reportOpen} onClose={() => setReportOpen(false)} title="ثبت گزارش روی کاربر">
<div className="flex flex-col gap-4">
<p className="text-[13px] text-(--color-content-secondary)">
یک یادداشت نظارتی برای این کاربر ثبت می‌شه (مستقل از بلاک کردن)؛ برای پیگیری
سابقه‌ی شکایت‌ها.
</p>
<textarea
value={reportReason}
onChange={(e) => setReportReason(e.target.value)}
rows={3}
placeholder="دلیل گزارش..."
className="rounded-(--radius-sm) border-2 border-(--color-border-default) bg-(--color-surface-default) px-4 py-3 text-[14px] focus:outline-none focus:border-(--color-border-interactive) focus:ring-4 focus:ring-(--color-brand-100) resize-none"
/>
<div className="flex justify-end gap-2">
<Button variant="ghost" size="sm" onClick={() => setReportOpen(false)}>
انصراف
</Button>
<Button
size="sm"
isLoading={loading}
disabled={reportReason.trim().length < 3}
onClick={handleReport}
>
ثبت گزارش
</Button>
</div>
</div>
</Modal>
</>
);
}