refactor(admin): organize user actions and improve content editing UX
This commit is contained in:
1 parent
e17346ab9f
commit
c6b919c01d
10 files changed
+688
-458
No files matched your search
@@ -0,0 +1,185 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
bulkAdjustCurrency,
|
||||
bulkBan,
|
||||
bulkGiveBadge,
|
||||
bulkUnban,
|
||||
} from "@/actions/bulk-users";
|
||||
|
||||
type PendingDialog =
|
||||
| { kind: "ban"; ids: number[] }
|
||||
| { kind: "give-currency"; ids: number[] }
|
||||
| { kind: "give-badge"; ids: number[] }
|
||||
| null;
|
||||
|
||||
export function useUserBulkActions() {
|
||||
const [pending, setPending] = useState<PendingDialog>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [banReason, setBanReason] = useState("Bulk ban");
|
||||
const [banDuration, setBanDuration] = useState("-1");
|
||||
const [currencyType, setCurrencyType] = useState<
|
||||
"credits" | "pixels" | "points"
|
||||
>("credits");
|
||||
const [currencyAmount, setCurrencyAmount] = useState("100");
|
||||
const [badgeCode, setBadgeCode] = useState("");
|
||||
|
||||
function reportResult(
|
||||
label: string,
|
||||
ok: number,
|
||||
total: number,
|
||||
failures = 0,
|
||||
) {
|
||||
if (ok > 0)
|
||||
toast.success(
|
||||
`${label}: ${ok}/${total}${failures ? ` · ${failures} failed` : ""}`,
|
||||
);
|
||||
if (failures > 0 && ok === 0)
|
||||
toast.error(`${label} failed for ${failures} users`);
|
||||
}
|
||||
|
||||
async function handleBulkAction(action: string, ids: (number | string)[]) {
|
||||
if (busy) return;
|
||||
const numericIds = ids.map((i) => Number(i));
|
||||
|
||||
if (action === "unban") {
|
||||
setBusy(true);
|
||||
try {
|
||||
const result = await bulkUnban({ userIds: numericIds });
|
||||
if (result.ok) {
|
||||
const data = result.data as { unbanned: number; total: number };
|
||||
reportResult("Unbanned", data.unbanned, data.total);
|
||||
} else toast.error(result.error);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Network error. The outcome is uncertain; check the user before retrying.",
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "ban") {
|
||||
setPending({ kind: "ban", ids: numericIds });
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "give-currency") {
|
||||
setPending({ kind: "give-currency", ids: numericIds });
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "give-badge") {
|
||||
setPending({ kind: "give-badge", ids: numericIds });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmBan() {
|
||||
if (pending?.kind !== "ban") return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const result = await bulkBan({
|
||||
userIds: pending.ids,
|
||||
reason: banReason,
|
||||
duration: Number(banDuration),
|
||||
});
|
||||
if (result.ok) {
|
||||
const data = result.data as { banned: number };
|
||||
reportResult("Banned", data.banned, pending.ids.length);
|
||||
setPending(null);
|
||||
} else toast.error(result.error);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Network error. The outcome is uncertain; check the user before retrying.",
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmGiveCurrency() {
|
||||
if (pending?.kind !== "give-currency") return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const result = await bulkAdjustCurrency({
|
||||
userIds: pending.ids,
|
||||
amount: Number(currencyAmount),
|
||||
type: currencyType,
|
||||
});
|
||||
if (result.ok) {
|
||||
const data = result.data as {
|
||||
adjusted: number;
|
||||
total: number;
|
||||
failedIds: { userId: number; reason: string }[];
|
||||
};
|
||||
reportResult(
|
||||
`Adjusted ${currencyAmount} ${currencyType}`,
|
||||
data.adjusted,
|
||||
data.total,
|
||||
data.failedIds.length,
|
||||
);
|
||||
setPending(null);
|
||||
} else toast.error(result.error);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Network error. The outcome is uncertain; check the user before retrying.",
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmGiveBadge() {
|
||||
if (pending?.kind !== "give-badge") return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const result = await bulkGiveBadge({
|
||||
userIds: pending.ids,
|
||||
badgeCode: badgeCode.trim().toUpperCase(),
|
||||
});
|
||||
if (result.ok) {
|
||||
const data = result.data as {
|
||||
given: number;
|
||||
total: number;
|
||||
failedIds: { userId: number; reason: string }[];
|
||||
};
|
||||
reportResult(
|
||||
`Given badge ${badgeCode}`,
|
||||
data.given,
|
||||
data.total,
|
||||
data.failedIds.length,
|
||||
);
|
||||
setPending(null);
|
||||
} else toast.error(result.error);
|
||||
} catch {
|
||||
toast.error(
|
||||
"Network error. The outcome is uncertain; check the user before retrying.",
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pending,
|
||||
setPending,
|
||||
busy,
|
||||
banReason,
|
||||
setBanReason,
|
||||
banDuration,
|
||||
setBanDuration,
|
||||
currencyType,
|
||||
setCurrencyType,
|
||||
currencyAmount,
|
||||
setCurrencyAmount,
|
||||
badgeCode,
|
||||
setBadgeCode,
|
||||
handleBulkAction,
|
||||
confirmBan,
|
||||
confirmGiveCurrency,
|
||||
confirmGiveBadge,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user