72 lines
1.9 KiB
TypeScript
72 lines
1.9 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={
|
|
<Badge
|
|
variant={ROLE_BADGE_VARIANT[current]}
|
|
className="cursor-pointer select-none"
|
|
>
|
|
{ROLE_LABEL[current]}
|
|
</Badge>
|
|
}
|
|
/>
|
|
<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>
|
|
);
|
|
}
|