chore: merge admin repo history into monorepo at admin/
git-subtree-dir: admin git-subtree-mainline:35f5084005git-subtree-split:9f1eccd2f7
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user