All components

Team Permissions Matrix

Team Permissions Matrix is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

HextaUI MIT other

What it is

Team Permissions Matrix is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

No packages beyond the project baseline

Licence

This component comes from HextaUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Team Permissions Matrix

registry/new-york/blocks/team/team-permissions-matrix.tsx
"use client";

import { Check, Lock, X } from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { Badge } from "@/registry/new-york/ui/badge";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import { Separator } from "@/registry/new-york/ui/separator";
import { Toggle } from "@/registry/new-york/ui/toggle";

export type PermissionAction =
  | "view"
  | "create"
  | "edit"
  | "delete"
  | "manage_members"
  | "manage_settings"
  | "manage_billing"
  | "view_analytics"
  | "manage_ai_rooms"
  | "manage_files"
  | "manage_projects";

export type PermissionRole = "owner" | "admin" | "member" | "viewer";

export interface Permission {
  action: PermissionAction;
  label: string;
  description?: string;
  category: string;
}

export interface RolePermissions {
  role: PermissionRole;
  permissions: Record<PermissionAction, boolean>;
}

export interface TeamPermissionsMatrixProps {
  permissions?: Permission[];
  rolePermissions?: RolePermissions[];
  onPermissionChange?: (
    role: PermissionRole,
    action: PermissionAction,
    enabled: boolean
  ) => Promise<void>;
  className?: string;
  showPresets?: boolean;
}

const DEFAULT_PERMISSIONS: Permission[] = [
  {
    action: "view",
    label: "View Content",
    description: "View team content and resources",
    category: "General",
  },
  {
    action: "create",
    label: "Create Content",
    description: "Create new content and resources",
    category: "General",
  },
  {
    action: "edit",
    label: "Edit Content",
    description: "Edit existing content",
    category: "General",
  },
  {
    action: "delete",
    label: "Delete Content",
    description: "Delete content and resources",
    category: "General",
  },
  {
    action: "manage_members",
    label: "Manage Members",
    description: "Invite, remove, and change member roles",
    category: "Team Management",
  },
  {
    action: "manage_settings",
    label: "Manage Settings",
    description: "Change team settings and configuration",
    category: "Team Management",
  },
  {
    action: "manage_billing",
    label: "Manage Billing",
    description: "View and manage billing information",
    category: "Team Management",
  },
  {
    action: "view_analytics",
    label: "View Analytics",
    description: "View team usage and analytics",
    category: "Analytics",
  },
  {
    action: "manage_ai_rooms",
    label: "Manage AI Rooms",
    description: "Create and manage shared AI workspaces",
    category: "AI Features",
  },
  {
    action: "manage_files",
    label: "Manage Files",
    description: "Upload, organize, and delete files",
    category: "Files",
  },
  {
    action: "manage_projects",
    label: "Manage Projects",
    description: "Create and manage team projects",
    category: "Projects",
  },
];

const DEFAULT_ROLE_PERMISSIONS: RolePermissions[] = [
  {
    role: "owner",
    permissions: {
      view: true,
      create: true,
      edit: true,
      delete: true,
      manage_members: true,
      manage_settings: true,
      manage_billing: true,
      view_analytics: true,
      manage_ai_rooms: true,
      manage_files: true,
      manage_projects: true,
    },
  },
  {
    role: "admin",
    permissions: {
      view: true,
      create: true,
      edit: true,
      delete: true,
      manage_members: true,
      manage_settings: false,
      manage_billing: false,
      view_analytics: true,
      manage_ai_rooms: true,
      manage_files: true,
      manage_projects: true,
    },
  },
  {
    role: "member",
    permissions: {
      view: true,
      create: true,
      edit: true,
      delete: false,
      manage_members: false,
      manage_settings: false,
      manage_billing: false,
      view_analytics: false,
      manage_ai_rooms: true,
      manage_files: true,
      manage_projects: true,
    },
  },
  {
    role: "viewer",
    permissions: {
      view: true,
      create: false,
      edit: false,
      delete: false,
      manage_members: false,
      manage_settings: false,
      manage_billing: false,
      view_analytics: false,
      manage_ai_rooms: false,
      manage_files: false,
      manage_projects: false,
    },
  },
];

function getRoleLabel(role: PermissionRole): string {
  switch (role) {
    case "owner":
      return "Owner";
    case "admin":
      return "Admin";
    case "member":
      return "Member";
    case "viewer":
      return "Viewer";
  }
}

function getRoleBadgeVariant(role: PermissionRole) {
  switch (role) {
    case "owner":
      return "default";
    case "admin":
      return "secondary";
    default:
      return "outline";
  }
}

export default function TeamPermissionsMatrix({
  permissions = DEFAULT_PERMISSIONS,
  rolePermissions = DEFAULT_ROLE_PERMISSIONS,
  onPermissionChange,
  className,
  showPresets = true,
}: TeamPermissionsMatrixProps) {
  const [localRolePermissions, setLocalRolePermissions] =
    useState<RolePermissions[]>(rolePermissions);

  const groupedPermissions = permissions.reduce(
    (acc, permission) => {
      if (!acc[permission.category]) {
        acc[permission.category] = [];
      }
      acc[permission.category].push(permission);
      return acc;
    },
    {} as Record<string, Permission[]>
  );

  const handlePermissionToggle = async (
    role: PermissionRole,
    action: PermissionAction,
    currentValue: boolean
  ) => {
    // Owner role cannot be modified
    if (role === "owner") return;

    const newValue = !currentValue;
    setLocalRolePermissions((prev) =>
      prev.map((rp) => {
        if (rp.role === role) {
          return {
            ...rp,
            permissions: {
              ...rp.permissions,
              [action]: newValue,
            },
          };
        }
        return rp;
      })
    );

    try {
      await onPermissionChange?.(role, action, newValue);
    } catch {
      // Revert on error
      setLocalRolePermissions((prev) =>
        prev.map((rp) => {
          if (rp.role === role) {
            return {
              ...rp,
              permissions: {
                ...rp.permissions,
                [action]: currentValue,
              },
            };
          }
          return rp;
        })
      );
    }
  };

  return (
    <Card className={cn("w-full shadow-xs", className)}>
      <CardHeader>
        <div className="flex flex-col gap-1">
          <CardTitle>Permissions Matrix</CardTitle>
          <CardDescription>
            Manage what each role can do in your team
          </CardDescription>
        </div>
      </CardHeader>
      <CardContent>
        <div className="flex flex-col gap-6">
          {Object.entries(groupedPermissions).map(([category, perms]) => (
            <div className="flex flex-col gap-3" key={category}>
              <div className="flex items-center gap-2">
                <h3 className="font-semibold text-muted-foreground text-sm">
                  {category}
                </h3>
                <Separator className="flex-1" />
              </div>
              <div className="flex flex-col gap-2">
                {perms.map((permission) => (
                  <div
                    className="flex flex-col gap-3 rounded-lg border bg-card p-4 transition-colors hover:bg-muted/50"
                    key={permission.action}
                  >
                    <div className="flex min-w-0 flex-1 flex-col gap-2">
                      <span className="wrap-break-word font-medium text-sm">
                        {permission.label}
                      </span>
                      {permission.description && (
                        <span className="wrap-break-word text-muted-foreground text-xs">
                          {permission.description}
                        </span>
                      )}
                    </div>
                    <div className="flex flex-wrap items-center gap-3">
                      {localRolePermissions.map((rp) => {
                        const hasPermission = rp.permissions[permission.action];
                        const isOwner = rp.role === "owner";

                        return (
                          <div
                            className="flex items-center gap-2"
                            key={rp.role}
                          >
                            <Badge variant={getRoleBadgeVariant(rp.role)}>
                              {getRoleLabel(rp.role)}
                            </Badge>
                            {isOwner ? (
                              <div className="flex items-center justify-center">
                                <Lock className="size-4 text-muted-foreground" />
                              </div>
                            ) : (
                              <Toggle
                                aria-label={`${permission.label} for ${getRoleLabel(rp.role)}`}
                                aria-pressed={hasPermission}
                                onPressedChange={() =>
                                  handlePermissionToggle(
                                    rp.role,
                                    permission.action,
                                    hasPermission
                                  )
                                }
                                pressed={hasPermission}
                                size="sm"
                              >
                                {hasPermission ? (
                                  <Check className="size-4" />
                                ) : (
                                  <X className="size-4" />
                                )}
                              </Toggle>
                            )}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </CardContent>
    </Card>
  );
}

Similar components