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

302 lines
11 KiB
TypeScript

"use client";
import { Server, User } from "revolt-api";
import { Button, buttonVariants } from "../../ui/button";
import {
Command,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { addServerMember, quarantineServer, updateServerDiscoverability, updateServerFlags, updateServerOwner } from "@/lib/actions";
import { useToast } from "../../ui/use-toast";
import Link from "next/link";
import { DropdownMenu, DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { Checkbox } from "@/components/ui/checkbox";
import UserSelector from "@/components/ui/user-selector";
import { Textarea } from "@/components/ui/textarea";
import { SEVRER_REMOVAL_MESSAGE } from "@/lib/constants";
export function ServerActions({ server }: { server: Server }) {
const [selectBadges, setSelectBadges] = useState(false);
const [serverDraft, setDraft] = useState(server);
const [quarantineMessage, setQuarantineMessage] = useState(SEVRER_REMOVAL_MESSAGE(server));
const [newOwner, setNewOwner] = useState<User | null>(null);
const [newMember, setNewMember] = useState<User | null>(null);
const [newMemberEvent, setNewMemberEvent] = useState(true);
const { toast } = useToast();
return (
<div className="flex flex-col md:flex-row gap-2">
{serverDraft.discoverable ? (
<Button
className="flex-1"
onClick={async () => {
try {
await updateServerDiscoverability(server._id, false);
setDraft((server) => ({ ...server, discoverable: false }));
toast({
title: "Removed server from Discover",
});
} catch (err) {
toast({
title: "Failed to remove server from Discover",
description: String(err),
variant: "destructive",
});
}
}}
>
Remove from Discover
</Button>
) : (
<Button
className="flex-1"
onClick={async () => {
try {
await updateServerDiscoverability(server._id, true);
setDraft((server) => ({ ...server, discoverable: true }));
toast({
title: "Added server to Discover",
});
} catch (err) {
toast({
title: "Failed to add server to Discover",
description: String(err),
variant: "destructive",
});
}
}}
>
Add to Discover
</Button>
)}
<Popover open={selectBadges} onOpenChange={setSelectBadges}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={selectBadges}
className="flex-1 justify-between"
>
{serverDraft.flags === 1
? "Official"
: serverDraft.flags === 2
? "Verified"
: "No Badge"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
{[
{ value: 0, label: "No Badge" },
{ value: 1, label: "Official" },
{ value: 2, label: "Verified" },
].map((flag) => (
<CommandItem
key={flag.value}
onSelect={async () => {
setSelectBadges(false);
try {
await updateServerFlags(server._id, flag.value);
setDraft((server) => ({ ...server, flags: flag.value }));
toast({
title: "Updated server flags",
});
} catch (err) {
toast({
title: "Failed to update server flags",
description: String(err),
variant: "destructive",
});
}
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
/*value === framework.value*/ false
? "opacity-100"
: "opacity-0"
)}
/>
{flag.label}
</CommandItem>
))}
</Command>
</PopoverContent>
</Popover>
<Link
className={`flex-1 ${buttonVariants()}`}
href={`/panel/inspect/server/${server._id}/invites`}
>
Invites
</Link>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button className="flex-1" variant="destructive">
Quarantine
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Quarantine server
</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-1">
<span>This will remove all members from this server and revoke all invites.</span>
<span className="text-red-700">This action is irreversible!</span>
<br />
<Textarea
placeholder="Removal message"
value={quarantineMessage}
onChange={(e) => setQuarantineMessage(e.currentTarget.value)}
/>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-red-800 hover:bg-red-700"
disabled={quarantineMessage == SEVRER_REMOVAL_MESSAGE(server) || !quarantineMessage}
onClick={async () => {
if (serverDraft.flags) {
// Intentionally not clearing the quarantine message draft
toast({
title: "Refusing to quarantine",
description: "This server is marked as verified or official",
variant: "destructive",
});
return;
}
try {
await quarantineServer(server._id, quarantineMessage);
toast({
title: "Quarantined server",
});
setQuarantineMessage(SEVRER_REMOVAL_MESSAGE(server));
} catch(e) {
toast({
title: "Failed to quarantine",
description: String(e),
variant: "destructive",
});
}
}}
>
Quarantine
</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">
Change owner
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Change server owner
</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
Enter the ID of the new server owner.
<UserSelector
onChange={(user) => setNewOwner(user)}
/>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={!newOwner}
onClick={async () => {
try {
await updateServerOwner(server._id, newOwner!._id);
setNewOwner(null);
toast({ title: "Server owner changed" });
} catch(e) {
toast({
title: "Owner update failed",
description: String(e),
variant: "destructive",
});
}
}}
>
Update
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost">
Add member
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Add member to server
</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
Enter the ID of the user you want to add.
<UserSelector onChange={(user) => setNewMember(user)} />
<Checkbox checked={newMemberEvent} onChange={(state) => setNewMemberEvent(state === true)}>Publish join event</Checkbox>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={!newMember}
onClick={async () => {
try {
await addServerMember(server._id, newMember!._id, newMemberEvent);
setNewMember(null);
toast({ title: "User added to server" });
} catch(e) {
toast({
title: "Failed to add user",
description: String(e),
variant: "destructive",
});
}
}}
>
Update
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}