diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx
new file mode 100644
index 0000000000..e377c7b33e
--- /dev/null
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_components/rbac-dialog-content.tsx
@@ -0,0 +1,155 @@
+"use client";
+
+import { Badge } from "@/components/ui/badge";
+import { formatNumber } from "@/lib/fmt";
+import { trpc } from "@/lib/trpc/client";
+import { AnimatedLoadingSpinner, Button } from "@unkey/ui";
+import dynamic from "next/dynamic";
+
+const PermissionList = dynamic(
+ () =>
+ import("../keys/[keyAuthId]/[keyId]/components/rbac/permissions").then((mod) => ({
+ default: mod.PermissionList,
+ })),
+ { ssr: false },
+);
+
+const RBACButtons = dynamic(
+ () =>
+ import("../keys/[keyAuthId]/[keyId]/components/rbac/rbac-buttons").then((mod) => ({
+ default: mod.RBACButtons,
+ })),
+ { ssr: false },
+);
+
+type Props = {
+ keyId: string;
+ keyspaceId: string;
+};
+
+export default function RBACDialogContent({ keyId, keyspaceId }: Props) {
+ const trpcUtils = trpc.useUtils();
+
+ const {
+ data: permissionsData,
+ isLoading,
+ isRefetching,
+ error,
+ } = trpc.key.fetchPermissions.useQuery({
+ keyId,
+ keyspaceId,
+ });
+
+ const { transientPermissionIds, rolesList } = calculatePermissionData(permissionsData);
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (error || !permissionsData) {
+ return (
+
+
Could not retrieve permission data
+
+ There was an error loading the permissions for this key. Please try again or contact
+ support if the issue persists.
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {formatNumber(permissionsData.roles.length)} Roles{" "}
+
+
+ {formatNumber(transientPermissionIds.size)} Permissions
+
+
+
+
+
+ {keyId ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+type WorkspaceRole = {
+ id: string;
+ name: string;
+ permissions: { permissionId: string }[];
+};
+
+type PermissionsResponse = {
+ roles: { roleId: string }[];
+ workspace: { roles: WorkspaceRole[]; permissions: { roles: unknown } };
+};
+
+function calculatePermissionData(permissionsData?: PermissionsResponse) {
+ const transientPermissionIds = new Set();
+ const rolesList: { id: string; name: string; isActive: boolean }[] = [];
+
+ if (!permissionsData) {
+ return { transientPermissionIds, rolesList };
+ }
+
+ // Mimic the original implementation logic
+ const connectedRoleIds = new Set();
+
+ for (const role of permissionsData.roles) {
+ connectedRoleIds.add(role.roleId);
+ }
+
+ for (const role of permissionsData.workspace.roles) {
+ if (connectedRoleIds.has(role.id)) {
+ for (const p of role.permissions) {
+ transientPermissionIds.add(p.permissionId);
+ }
+ }
+ }
+
+ // Build roles list matching the original format
+ const roles = permissionsData.workspace.roles.map((role: any) => {
+ return {
+ id: role.id,
+ name: role.name,
+ isActive: permissionsData.roles.some((keyRole: any) => keyRole.roleId === role.id),
+ };
+ });
+
+ return {
+ transientPermissionIds,
+ rolesList: roles,
+ };
+}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx b/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx
index eb943aa2e4..524982cc17 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx
@@ -4,17 +4,11 @@ import { QuickNavPopover } from "@/components/navbar-popover";
import { NavbarActionButton } from "@/components/navigation/action-button";
import { CopyableIDButton } from "@/components/navigation/copyable-id-button";
import { Navbar } from "@/components/navigation/navbar";
-import { Badge } from "@/components/ui/badge";
import { useIsMobile } from "@/hooks/use-mobile";
-import { formatNumber } from "@/lib/fmt";
-import { trpc } from "@/lib/trpc/client";
import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema";
import { ChevronExpandY, Gauge, Gear, Plus, ShieldKey } from "@unkey/icons";
-import { AnimatedLoadingSpinner, Button } from "@unkey/ui";
import dynamic from "next/dynamic";
import { useState } from "react";
-import { PermissionList } from "./keys/[keyAuthId]/[keyId]/components/rbac/permissions";
-import { RBACButtons } from "./keys/[keyAuthId]/[keyId]/components/rbac/rbac-buttons";
import { getKeysTableActionItems } from "./keys/[keyAuthId]/_components/components/table/components/actions/keys-table-action.popover.constants";
const CreateKeyDialog = dynamic(
@@ -56,6 +50,16 @@ const KeysTableActionPopover = dynamic(
},
);
+const RBACDialogContent = dynamic(() => import("./_components/rbac-dialog-content"), {
+ ssr: false,
+ loading: () => (
+
+
+ Permissions
+
+ ),
+});
+
export const ApisNavbar = ({
api,
apis,
@@ -82,29 +86,10 @@ export const ApisNavbar = ({
keyData?: KeyDetails | null;
}) => {
const isMobile = useIsMobile();
- const trpcUtils = trpc.useUtils();
const [showRBAC, setShowRBAC] = useState(false);
const keyId = keyData?.id || "";
const keyspaceId = api.keyAuthId || "";
- const shouldFetchPermissions = Boolean(keyId) && Boolean(keyspaceId);
-
- const {
- data: permissionsData,
- isLoading,
- isRefetching,
- error,
- } = trpc.key.fetchPermissions.useQuery(
- {
- keyId,
- keyspaceId,
- },
- {
- enabled: shouldFetchPermissions,
- },
- );
-
- const { transientPermissionIds, rolesList } = calculatePermissionData(permissionsData);
return (
<>
@@ -181,7 +166,7 @@ export const ApisNavbar = ({
setShowRBAC(true)}
- disabled={!shouldFetchPermissions}
+ disabled={!(keyId && keyspaceId)}
>
Permissions
@@ -208,117 +193,17 @@ export const ApisNavbar = ({
)}
- setShowRBAC(false)}
- title="Key Permissions & Roles"
- subTitle="Manage access control for this API key with role-based permissions"
- className="max-w-[800px] max-h-[90vh] overflow-y-auto"
- >
- {isLoading ? (
-
- ) : error || !permissionsData ? (
-
-
Could not retrieve permission data
-
- There was an error loading the permissions for this key. Please try again or contact
- support if the issue persists.
-
-
-
- ) : (
-
-
-
-
- {formatNumber(permissionsData.roles.length)} Roles{" "}
-
-
- {formatNumber(transientPermissionIds.size)} Permissions
-
-
-
-
-
- {/* Only render PermissionList if we have a valid keyId */}
- {keyId ? (
-
- ) : (
-
- )}
-
-
- )}
-
+ {showRBAC && (
+ setShowRBAC(false)}
+ title="Key Permissions & Roles"
+ subTitle="Manage access control for this API key with role-based permissions"
+ className="max-w-[800px] max-h-[90vh] overflow-y-auto"
+ >
+
+
+ )}
>
);
};
-
-type WorkspaceRole = {
- id: string;
- name: string;
- permissions: { permissionId: string }[];
-};
-
-type PermissionsResponse = {
- roles: { roleId: string }[];
- workspace: { roles: WorkspaceRole[]; permissions: { roles: unknown } };
-};
-
-function calculatePermissionData(permissionsData?: PermissionsResponse) {
- const transientPermissionIds = new Set();
- const rolesList: { id: string; name: string; isActive: boolean }[] = [];
-
- if (!permissionsData) {
- return { transientPermissionIds, rolesList };
- }
-
- // Mimic the original implementation logic
- const connectedRoleIds = new Set();
-
- for (const role of permissionsData.roles) {
- connectedRoleIds.add(role.roleId);
- }
-
- for (const role of permissionsData.workspace.roles) {
- if (connectedRoleIds.has(role.id)) {
- for (const p of role.permissions) {
- transientPermissionIds.add(p.permissionId);
- }
- }
- }
-
- // Build roles list matching the original format
- const roles = permissionsData.workspace.roles.map((role: any) => {
- return {
- id: role.id,
- name: role.name,
- isActive: permissionsData.roles.some((keyRole: any) => keyRole.roleId === role.id),
- };
- });
-
- return {
- transientPermissionIds,
- rolesList: roles,
- };
-}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
index 939399687f..28e03ebe78 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
@@ -2,7 +2,7 @@
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema";
-import { BookBookmark, Focus, Key } from "@unkey/icons";
+import { BookBookmark, Dots, Focus, Key } from "@unkey/icons";
import {
AnimatedLoadingSpinner,
Button,
@@ -14,9 +14,9 @@ import {
TooltipTrigger,
} from "@unkey/ui";
import { cn } from "@unkey/ui/src/lib/utils";
+import dynamic from "next/dynamic";
import Link from "next/link";
import React, { useCallback, useMemo, useState } from "react";
-import { KeysTableActionPopover } from "./components/actions/keys-table-action.popover";
import { getKeysTableActionItems } from "./components/actions/keys-table-action.popover.constants";
import { VerificationBarChart } from "./components/bar-chart";
import { HiddenValueCell } from "./components/hidden-value";
@@ -34,6 +34,27 @@ import { StatusDisplay } from "./components/status-cell";
import { useKeysListQuery } from "./hooks/use-keys-list-query";
import { getRowClassName } from "./utils/get-row-class";
+const KeysTableActionPopover = dynamic(
+ () =>
+ import("./components/actions/keys-table-action.popover").then((mod) => ({
+ default: mod.KeysTableActionPopover,
+ })),
+ {
+ ssr: false,
+ loading: () => (
+
+ ),
+ },
+);
+
export const KeysList = ({
keyspaceId,
apiId,