1
0
Fork 0
panel/components/pages/inspector/UserActions.tsx

553 lines
20 KiB
TypeScript

"use client";
import Link from "next/link";
import { Button, buttonVariants } from "../../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../../ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "../../ui/alert-dialog";
import { Input } from "../../ui/input";
import {
banUser,
closeReportsByUser,
resetBotToken,
sendAlert,
suspendUser,
transferBot,
unsuspendUser,
updateBotDiscoverability,
updateUserBadges,
wipeUserProfile,
} from "@/lib/actions";
import { useRef, useState } from "react";
import { useToast } from "../../ui/use-toast";
import { Bot, User } from "revolt-api";
import { Card, CardHeader } from "../../ui/card";
import { cn } from "@/lib/utils";
import { decodeTime } from "ulid";
import { Checkbox } from "@/components/ui/checkbox";
import UserSelector from "@/components/ui/user-selector";
const badges = [1, 2, 4, 8, 16, 32, 128, 0, 256, 512, 1024];
export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
const alertMessage = useRef("");
const { toast } = useToast();
const [userDraft, setUserDraft] = useState(user);
const [botDraft, setBotDraft] = useState(bot);
const [wipeDraft, setWipeDraft] = useState({
banner: false,
avatar: false,
bio: false,
displayName: false,
status: false,
});
const [transferTarget, setTransferTarget] = useState<User | null>(null);
const [transferResetToken, setTransferResetToken] = useState(true);
const userInaccessible = userDraft.flags === 4 || userDraft.flags === 2;
return (
<>
<Card>
<CardHeader className="w-full flex flex-row gap-2 justify-center items-center flex-wrap">
{badges.map((badge) => {
const sysBadge =
(badge === 0 && user._id === "01EX2NCWQ0CHS3QJF0FEQS1GR4") ||
(badge === 256 && decodeTime(user._id) < 1629638578431);
return (
// eslint-disable-next-line
<img
key={badge}
src={`/badges/${badge === 8 ? 2048 : badge}.svg`}
className={cn(
"w-7 h-7 transition-all !m-0",
sysBadge
? ""
: (userDraft.badges ?? 0) & badge
? "cursor-pointer"
: "cursor-pointer opacity-40 hover:opacity-60 grayscale hover:grayscale-0"
)}
onClick={async () => {
if (sysBadge) return;
try {
const badges =
(decodeTime(user._id) < 1629638578431 ? 256 : 0) |
((userDraft.badges ?? 0) ^ badge);
await updateUserBadges(user._id, badges);
setUserDraft((user) => ({ ...user!, badges }));
toast({
title: "Updated user badges",
});
} catch (err) {
toast({
title: "Failed to update user badges",
description: String(err),
variant: "destructive",
});
}
}}
/>
);
})}
</CardHeader>
</Card>
<div className="flex gap-2">
{bot ? (
botDraft!.discoverable ? (
<Button
className="flex-1"
onClick={async () => {
try {
await updateBotDiscoverability(bot._id, false);
setBotDraft((bot) => ({ ...bot!, discoverable: false }));
toast({
title: "Removed bot from Discover",
});
} catch (err) {
toast({
title: "Failed to remove bot from Discover",
description: String(err),
variant: "destructive",
});
}
}}
>
Remove from Discover
</Button>
) : (
<Button
className="flex-1"
onClick={async () => {
try {
await updateBotDiscoverability(bot._id, true);
setBotDraft((bot) => ({ ...bot!, discoverable: true }));
toast({
title: "Added bot to Discover",
});
} catch (err) {
toast({
title: "Failed to add bot to Discover",
description: String(err),
variant: "destructive",
});
}
}}
>
Add to Discover
</Button>
)
) : (
<Link
className={`flex-1 ${buttonVariants()}`}
href={`/panel/inspect/account/${user._id}`}
>
Account
</Link>
)}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
className="flex-1 bg-orange-400 hover:bg-orange-300"
disabled={userInaccessible}
>
{userDraft.flags === 1 ? "Unsuspend" : "Suspend"}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to{" "}
{userDraft.flags === 1 ? "unsuspend" : "suspend"} this user?
</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() =>
userDraft.flags === 1
? unsuspendUser(user._id)
.then(() => {
setUserDraft((user) => ({ ...user, flags: 0 }));
toast({ title: "Unsuspended user" });
})
.catch((err) =>
toast({
title: "Failed to unsuspend user!",
description: String(err),
variant: "destructive",
})
)
: suspendUser(user._id)
.then(() => {
setUserDraft((user) => ({ ...user, flags: 1 }));
toast({ title: "Suspended user" });
})
.catch((err) =>
toast({
title: "Failed to suspend user!",
description: String(err),
variant: "destructive",
})
)
}
>
{userDraft.flags === 1 ? "Unsuspend" : "Suspend"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
className="flex-1"
variant="destructive"
disabled={userInaccessible}
>
{userDraft.flags === 4 ? "Banned" : "Ban"}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to ban this user?
</AlertDialogTitle>
<AlertDialogDescription>
All messages sent by this user will be deleted immediately.
<br className="text-base/8" />
<span className="text-red-700">
This action is irreversible!
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="hover:bg-red-700 transition-all"
onClick={() =>
banUser(user._id)
.then(() => {
setUserDraft((user) => ({ ...user, flags: 4 }));
toast({ title: "Banned user" });
})
.catch((err) =>
toast({
title: "Failed to ban user!",
description: String(err),
variant: "destructive",
})
)
}
>
Ban
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button className="flex-1 bg-yellow-600">Bees</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Release the bees</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to send the bees?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => toast({ title: "🐝" })}>
Deploy
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="flex-1">
More Options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="flex flex-col">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost">Send Alert</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Send Alert</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
This will send a message from the Platform Moderation
account.
</span>
<Input
placeholder="Enter a message..."
name="message"
onChange={(e) =>
(alertMessage.current = e.currentTarget.value)
}
/>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (!alertMessage.current) return;
sendAlert(user._id, alertMessage.current)
.then(() => {
toast({ title: "Sent Alert" });
alertMessage.current = "";
})
.catch((err) =>
toast({
title: "Failed to send alert!",
description: String(err),
variant: "destructive",
})
);
}}
>
Send
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost">Reset profile</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reset user profile</AlertDialogTitle>
<AlertDialogDescription>
<Checkbox
checked={wipeDraft.avatar}
onChange={(e) =>
setWipeDraft({ ...wipeDraft, avatar: e == true })
}
>
Avatar
</Checkbox>
<Checkbox
checked={wipeDraft.banner}
onChange={(e) =>
setWipeDraft({ ...wipeDraft, banner: e == true })
}
>
Profile Banner
</Checkbox>
<Checkbox
checked={wipeDraft.displayName}
onChange={(e) =>
setWipeDraft({ ...wipeDraft, displayName: e == true })
}
>
Display Name
</Checkbox>
<Checkbox
checked={wipeDraft.bio}
onChange={(e) =>
setWipeDraft({ ...wipeDraft, bio: e == true })
}
>
Bio
</Checkbox>
<Checkbox
checked={wipeDraft.status}
onChange={(e) =>
setWipeDraft({ ...wipeDraft, status: e == true })
}
>
Status
</Checkbox>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={!Object.values(wipeDraft).filter((i) => i).length}
onClick={() => {
wipeUserProfile(user._id, wipeDraft)
.then(() => {
toast({ title: "Wiped selected fields" });
window.location.reload();
})
.catch((e) =>
toast({
title: "Failed to wipe profile",
description: String(e),
variant: "destructive",
})
)
.finally(() =>
setWipeDraft({
avatar: false,
banner: false,
bio: false,
displayName: false,
status: false,
})
);
}}
>
Reset
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" disabled={!user.bot?.owner}>Reset bot token</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reset token</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
Re-roll this bot&apos;s authentication token. This will not disconnect active connections.
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => resetBotToken(user._id)
.then(() => toast({
title: "Reset bot token",
}))
.catch((e) => toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
}))
}
>
Reset
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" disabled={!user.bot?.owner}>Transfer bot</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Transfer bot</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
Transfer this bot to a new owner.
</span>
<UserSelector
onChange={setTransferTarget}
/>
<Checkbox
checked={transferResetToken}
onChange={(e) => setTransferResetToken(!!e)}
>
Also reset token
</Checkbox>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={!transferTarget}
onClick={() => transferBot(user._id, transferTarget!._id, transferResetToken)
.then(() => toast({
title: "Reset bot token",
}))
.catch((e) => toast({
title: "Failed to reset token",
description: String(e),
variant: "destructive",
}))
.finally(() => {
setTransferResetToken(true);
setTransferTarget(null);
})
}
>
Transfer
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost">Close Open Reports</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Close Open Reports</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
This will close all reports still open by this user.
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() =>
closeReportsByUser(user._id)
.then((reports) =>
toast({ title: `Closed ${reports} Reports` })
)
.catch((err) =>
toast({
title: "Failed to close reports!",
description: String(err),
variant: "destructive",
})
)
}
>
Continue
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* <DropdownMenuItem>
Clear ({counts.pending}) Friend Requests
</DropdownMenuItem>
<DropdownMenuItem>
Clear All ({counts.all}) Relations
</DropdownMenuItem> */}
</DropdownMenuContent>
</DropdownMenu>
</div>
</>
);
}