Organize/components/admin/user-role-menu.tsx

81 lines
2.3 KiB
TypeScript

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { updateUserRole } from "@/lib/actions/admin";
import { Role } from "@/lib/generated/prisma/enums";
const ROLE_BADGE_VARIANT: Record<Role, "default" | "secondary" | "outline"> = {
ADMIN: "default",
USER: "secondary",
PENDING: "outline",
};
const ROLE_LABEL: Record<Role, string> = {
ADMIN: "Administrator",
USER: "User",
PENDING: "Pending",
};
/** The role badge doubles as the trigger -- clicking it opens a menu to
* change the user's role in place. */
export function UserRoleMenu({ userId, role }: { userId: string; role: Role }) {
const [current, setCurrent] = useState(role);
const [pending, setPending] = useState(false);
async function handleChange(next: string) {
if (next === current || pending) return;
const prev = current;
setCurrent(next as Role);
setPending(true);
const result = await updateUserRole(userId, next);
setPending(false);
if (result?.error) {
setCurrent(prev);
toast.error(result.error);
}
}
return (
<DropdownMenu>
<DropdownMenuTrigger
render={
// Base UI requires a native <button> trigger -- so the Badge
// (a <span>) sits inside a bare reset button instead of being
// the trigger itself.
<button
type="button"
className="inline-flex appearance-none border-0 bg-transparent p-0"
aria-label={`Change role (currently ${ROLE_LABEL[current]})`}
>
<Badge
variant={ROLE_BADGE_VARIANT[current]}
className="cursor-pointer select-none"
>
{ROLE_LABEL[current]}
</Badge>
</button>
}
/>
<DropdownMenuContent align="start">
<DropdownMenuRadioGroup value={current} onValueChange={handleChange}>
{Object.values(Role).map((value) => (
<DropdownMenuRadioItem key={value} value={value}>
{ROLE_LABEL[value]}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}