122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import type { UserSummary } from "../types";
|
|
import { fetchAdminJson, getAdminErrorMessage } from "../api";
|
|
|
|
type RoleAction = "make-admin" | "revoke-admin" | "make-super-admin";
|
|
|
|
type RoleActionsProps = {
|
|
target: UserSummary;
|
|
isSelf: boolean;
|
|
isSuperAdmin: boolean;
|
|
onUpdated: () => void;
|
|
};
|
|
|
|
const actionLabels: Record<RoleAction, string> = {
|
|
"make-admin": "Grant Admin",
|
|
"revoke-admin": "Revoke Admin",
|
|
"make-super-admin": "Make Super Admin",
|
|
};
|
|
|
|
const actionDescriptions: Record<RoleAction, string> = {
|
|
"make-admin": "Are you sure you want to promote this user to ADMIN?",
|
|
"revoke-admin": "Are you sure you want to revoke admin privileges for this user?",
|
|
"make-super-admin": "Are you sure you want to promote this user to SUPER_ADMIN?",
|
|
};
|
|
|
|
const actionEndpoint: Record<RoleAction, string> = {
|
|
"make-admin": "make-admin",
|
|
"revoke-admin": "revoke-admin",
|
|
"make-super-admin": "make-super-admin",
|
|
};
|
|
|
|
export default function RoleActions({ target, isSelf, isSuperAdmin, onUpdated }: RoleActionsProps) {
|
|
const [action, setAction] = useState<RoleAction | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isWorking, setIsWorking] = useState(false);
|
|
|
|
if (!isSuperAdmin) {
|
|
return null;
|
|
}
|
|
|
|
const disabledDemote = isSelf && target.role === "SUPER_ADMIN";
|
|
|
|
const handleConfirm = async () => {
|
|
if (!action) {
|
|
return;
|
|
}
|
|
setIsWorking(true);
|
|
setError(null);
|
|
try {
|
|
await fetchAdminJson(
|
|
`admin/users/${target.user_id}/${actionEndpoint[action]}`,
|
|
{ method: "POST" },
|
|
);
|
|
onUpdated();
|
|
setAction(null);
|
|
} catch (err) {
|
|
const message = getAdminErrorMessage(err, "Role update failed.");
|
|
setError(message);
|
|
} finally {
|
|
setIsWorking(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={target.role === "ADMIN" || target.role === "SUPER_ADMIN"}
|
|
onClick={() => setAction("make-admin")}
|
|
>
|
|
{actionLabels["make-admin"]}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={disabledDemote || target.role === "USER"}
|
|
onClick={() => setAction("revoke-admin")}
|
|
>
|
|
{actionLabels["revoke-admin"]}
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
disabled={target.role === "SUPER_ADMIN"}
|
|
onClick={() => setAction("make-super-admin")}
|
|
>
|
|
{actionLabels["make-super-admin"]}
|
|
</Button>
|
|
|
|
<Dialog open={!!action} onOpenChange={(open) => !open && setAction(null)}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{action ? actionLabels[action] : "Confirm role change"}</DialogTitle>
|
|
<DialogDescription>
|
|
{action ? actionDescriptions[action] : ""}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{error && <p className="text-xs text-destructive">{error}</p>}
|
|
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-end">
|
|
<Button variant="outline" onClick={() => setAction(null)} disabled={isWorking}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleConfirm} disabled={isWorking}>
|
|
{isWorking ? "Updating..." : "Confirm"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|