"use client"; import { Crown, EyeOff, Pencil, Plus, Shield, ShieldCheck, Trash2, UserCheck, Users, } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; import { createRank, deleteRank } from "@/actions/permissions"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useServerAction } from "@/hooks/use-server-action"; interface RankSummary { id: number; rank_name: string; badge: string; level: number; prefix: string; prefix_color: string; hidden_rank: string; userCount: number; } interface Props { ranks: RankSummary[]; fromHousekeeping?: boolean; } const RANK_ICONS: Record = { 7: , 6: , 5: , 4: , }; const RANK_COLORS: Record = { 7: "border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]", 6: "border-[var(--admin-error-border)] bg-[var(--admin-error-subtle)]", 5: "border-[var(--admin-info-border)] bg-[var(--admin-info-subtle)]", 4: "border-[var(--admin-success-border)] bg-[var(--admin-success-subtle)]", }; export function PermissionsList({ ranks: initialRanks, fromHousekeeping = false, }: Props) { const { run, isPending } = useServerAction(); const [ranks, setRanks] = useState(initialRanks); const [deleteTarget, setDeleteTarget] = useState(null); const [showCreate, setShowCreate] = useState(false); const [newRankName, setNewRankName] = useState(""); const [newRankLevel, setNewRankLevel] = useState(""); const totalUsers = ranks.reduce((sum, r) => sum + r.userCount, 0); function handleCreate() { const level = parseInt(newRankLevel, 10); if (!newRankName.trim() || !level || level < 1) { toast.error("Name and a valid level are required"); return; } run(() => createRank({ rank_name: newRankName.trim(), level }), { successMessage: "Rank created", onSuccess: () => { setShowCreate(false); setNewRankName(""); setNewRankLevel(""); }, }); } function handleDelete() { if (!deleteTarget) return; run(() => deleteRank({ id: deleteTarget.id }), { successMessage: "Rank deleted", onSuccess: () => { setRanks((prev) => prev.filter((r) => r.id !== deleteTarget?.id)); setDeleteTarget(null); }, }); } return (
{fromHousekeeping ? (
You were sent here from the permission archive. Edit rank access below — this is the live control panel.
) : null}

Live access control

Changes here decide who can open admin and mod pages. The permission archive is only for compare/export.

{/* Header */}

{ranks.length} ranks · {totalUsers.toLocaleString()} users total

{/* Rank cards */}
{ranks.map((rank) => { const prefixColor = rank.prefix_color ? rank.prefix_color.startsWith("#") ? rank.prefix_color : `#${rank.prefix_color}` : undefined; return (
{/* Header */}
{RANK_ICONS[rank.id] || ( )} {rank.rank_name || `Rank ${rank.id}`}
{rank.hidden_rank === "1" && ( )} #{rank.id}
{/* Badges row */}
Level {rank.level} {rank.badge && ( {rank.badge} )} {rank.prefix && ( {rank.prefix} )}
{/* User count */}
{rank.userCount.toLocaleString()} users
{/* Actions */}
); })}
{/* Create rank dialog */} Create New Rank Add a new permission rank. You can configure its permissions after creation.
setNewRankName(e.target.value)} placeholder="e.g. Moderator" />
setNewRankLevel(e.target.value)} placeholder="e.g. 5" min={1} />

Higher level = more base permissions in the emulator

{/* Delete confirmation dialog */} setDeleteTarget(null)}> Delete Rank #{deleteTarget?.id}? This will permanently delete "{deleteTarget?.rank_name}" and its CMS role. This cannot be undone.
); }