From 2c74f03c9bb096a23f5fee824aeaded799e0dede Mon Sep 17 00:00:00 2001 From: poswalsameer Date: Sun, 9 Feb 2025 14:14:48 +0530 Subject: [PATCH 1/6] changed the query params to tab=secret from tab=Secret --- .../src/components/dashboard/project/projectCard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/platform/src/components/dashboard/project/projectCard/index.tsx b/apps/platform/src/components/dashboard/project/projectCard/index.tsx index 1a847f8b5..af642e648 100644 --- a/apps/platform/src/components/dashboard/project/projectCard/index.tsx +++ b/apps/platform/src/components/dashboard/project/projectCard/index.tsx @@ -107,7 +107,7 @@ export default function ProjectCard({
From fe320d418d8b2931c2b934ba287985ccb7487a36 Mon Sep 17 00:00:00 2001 From: poswalsameer Date: Mon, 10 Feb 2025 00:48:48 +0530 Subject: [PATCH 2/6] feat: create, show, and delete API Keys --- .../(settings)/settings/@profile/page.tsx | 67 +++++- apps/platform/src/components/common/slug.tsx | 2 +- .../apiKeys/addApiKeyDialog/index.tsx | 192 ++++++++++++++++++ .../userProfile/apiKeys/apiKeyCard/index.tsx | 80 ++++++++ .../apiKeys/confirmDeleteApiKey/index.tsx | 140 +++++++++++++ apps/platform/src/lib/controller-instance.ts | 11 +- apps/platform/src/store/index.ts | 7 + packages/api-client/src/index.ts | 4 +- 8 files changed, 495 insertions(+), 8 deletions(-) create mode 100644 apps/platform/src/components/userProfile/apiKeys/addApiKeyDialog/index.tsx create mode 100644 apps/platform/src/components/userProfile/apiKeys/apiKeyCard/index.tsx create mode 100644 apps/platform/src/components/userProfile/apiKeys/confirmDeleteApiKey/index.tsx diff --git a/apps/platform/src/app/(main)/(settings)/settings/@profile/page.tsx b/apps/platform/src/app/(main)/(settings)/settings/@profile/page.tsx index ffcf3af84..133c29564 100644 --- a/apps/platform/src/app/(main)/(settings)/settings/@profile/page.tsx +++ b/apps/platform/src/app/(main)/(settings)/settings/@profile/page.tsx @@ -1,16 +1,28 @@ 'use client' -import React, { useCallback, useState } from 'react' +import React, { useCallback, useState, useEffect } from 'react' import { toast } from 'sonner' -import { useAtom } from 'jotai' +import { useAtom, useAtomValue } from 'jotai' import InputLoading from './loading' import { Input } from '@/components/ui/input' import { Separator } from '@/components/ui/separator' import ControllerInstance from '@/lib/controller-instance' import { Button } from '@/components/ui/button' -import { userAtom } from '@/store' +import { + userAtom, + apiKeysOfProjectAtom, + deleteApiKeyOpenAtom, + selectedApiKeyAtom +} from '@/store' +import { useSearchParams } from 'next/navigation' +import AddApiKeyDialog from '@/components/userProfile/apiKeys/addApiKeyDialog' +import ApiKeyCard from '@/components/userProfile/apiKeys/apiKeyCard' +import ConfirmDeleteApiKey from '@/components/userProfile/apiKeys/confirmDeleteApiKey' function ProfilePage(): React.JSX.Element { const [user, setUser] = useAtom(userAtom) + const [apiKeys, setApiKeys] = useAtom(apiKeysOfProjectAtom) + const isDeleteApiKeyOpen = useAtomValue(deleteApiKeyOpenAtom) + const selectedApiKey = useAtomValue(selectedApiKeyAtom) const [isLoading, setIsLoading] = useState(true) const [userData, setUserData] = useState({ @@ -20,6 +32,9 @@ function ProfilePage(): React.JSX.Element { }) const [isModified, setIsModified] = useState(false) + const searchParams = useSearchParams() + const tab = searchParams.get('profile') ?? 'profile' + const updateSelf = useCallback(async () => { toast.loading('Updating profile...') setIsLoading(true) @@ -63,6 +78,33 @@ function ProfilePage(): React.JSX.Element { setIsLoading(false) }, [userData.name, userData.email, user?.name, user?.email, setUser]) + useEffect(() => { + const getAllApiKeys = async () => { + const { success, error, data } = await ControllerInstance.getInstance().apiKeyController.getApiKeysOfUser( + {}, + {} + ) + + if (success && data) { + setApiKeys(data.items) + } + if (error) { + toast.error('Something went wrong!', { + description: ( +

+ Something went wrong while fetching API Keys. Check console for + more info. +

+ ) + }) + // eslint-disable-next-line no-console -- we need to log the error + console.error(error) + } + } + + getAllApiKeys() + }, [setApiKeys]) + return (
{/* Avatar */} @@ -124,15 +166,30 @@ function ProfilePage(): React.JSX.Element { Save Changes
- -
+ +
API Keys
Generate new API keys to use with the Keyshade CLI.
+
+ {tab === 'profile' && } +
+ {apiKeys.length !== 0 && +
+ {apiKeys.map((apiKey) => ( + + ))} + + {/* Delete API Key alert dialog */} + {isDeleteApiKeyOpen && selectedApiKey ? ( + + ) : null} +
+ } diff --git a/apps/platform/src/components/common/slug.tsx b/apps/platform/src/components/common/slug.tsx index db2b080f1..a63b0b481 100644 --- a/apps/platform/src/components/common/slug.tsx +++ b/apps/platform/src/components/common/slug.tsx @@ -23,7 +23,7 @@ export default function Slug({ text }: SlugProps): React.JSX.Element { return ( + + + + + Add a new API Key + + + Add a new API key to the project + + + +
+
+
+ + + setNewApiKeyData({ + ...newApiKeyData, + apiKeyName: e.target.value + }) + } + placeholder="Enter the API key" + value={newApiKeyData.apiKeyName} + /> +
+ +
+ + +
+ +
+ +
+
+
+
+ + ) +} diff --git a/apps/platform/src/components/userProfile/apiKeys/apiKeyCard/index.tsx b/apps/platform/src/components/userProfile/apiKeys/apiKeyCard/index.tsx new file mode 100644 index 000000000..d9a62f00c --- /dev/null +++ b/apps/platform/src/components/userProfile/apiKeys/apiKeyCard/index.tsx @@ -0,0 +1,80 @@ +'use client' + +import type { ApiKey } from '@keyshade/schema' +import dayjs from 'dayjs' +import { useSetAtom } from 'jotai' +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger +} from '@/components/ui/context-menu' +import { + selectedApiKeyAtom, + editApiKeyOpenAtom, + deleteApiKeyOpenAtom +} from '@/store' +import Slug from '@/components/common/slug' + +const formatDate = (date: string): string => { + return dayjs(date).format('D MMMM, YYYY') +} + +export default function ApiKeyCard({ + apiKey +}: { + apiKey: ApiKey +}): React.JSX.Element { + const setSelectedApiKey = useSetAtom(selectedApiKeyAtom) + const setIsEditApiKeyOpen = useSetAtom(editApiKeyOpenAtom) + const setIsDeleteApiKeyOpen = useSetAtom(deleteApiKeyOpenAtom) + + const handleDeleteClick = () => { + setSelectedApiKey(apiKey) + setIsDeleteApiKeyOpen(true) + } + + return ( + + +
+
+
+
{apiKey.name}
+
+
+
+
+ +
+
+
Expiring in
+
+ {apiKey.expiresAt ? ( + dayjs(apiKey.expiresAt).diff(dayjs(), "day") >= 1 ? ( + `${dayjs(apiKey.expiresAt).diff(dayjs(), "day")} days` + ) : ( + `${dayjs(apiKey.expiresAt).diff(dayjs(), "hour")} hours` + ) + ) : "Never"} +
+
+
+
+
+ + + Edit + + + Delete + + +
+ ) +} diff --git a/apps/platform/src/components/userProfile/apiKeys/confirmDeleteApiKey/index.tsx b/apps/platform/src/components/userProfile/apiKeys/confirmDeleteApiKey/index.tsx new file mode 100644 index 000000000..2fc7e1f68 --- /dev/null +++ b/apps/platform/src/components/userProfile/apiKeys/confirmDeleteApiKey/index.tsx @@ -0,0 +1,140 @@ +'use client' + +import React, { useState, useCallback, useEffect } from 'react' +import { TrashSVG } from '@public/svg/shared' +import { toast } from 'sonner' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle +} from '@/components/ui/alert-dialog' +import ControllerInstance from '@/lib/controller-instance' +import { + deleteApiKeyOpenAtom, + apiKeysOfProjectAtom, + selectedApiKeyAtom +} from '@/store' + +export default function ConfirmDeleteApiKey(): React.JSX.Element { + const [isLoading, setIsLoading] = useState(false) + const selectedApiKey = useAtomValue(selectedApiKeyAtom) + const [isDeleteApiKeyOpen, setIsDeleteApiKeyOpen] = useAtom(deleteApiKeyOpenAtom) + const setApiKeys = useSetAtom(apiKeysOfProjectAtom) + + const handleClose = useCallback(() => { + setIsDeleteApiKeyOpen(false) + }, [setIsDeleteApiKeyOpen]) + + const deleteApiKey = useCallback(async () => { + setIsLoading(true) + + if (selectedApiKey === null) { + toast.error('No API Key selected', { + description: ( +

+ No API Key selected. Please select an API Key. +

+ ) + }) + return + } + + const apiKeySlug = selectedApiKey.slug + + toast.loading("Deleting your API Key...") + const { success, error } = + await ControllerInstance.getInstance().apiKeyController.deleteApiKey( + { apiKeySlug: apiKeySlug }, + {} + ) + + toast.dismiss() + if (success) { + toast.success('API Key deleted successfully', { + description: ( +

+ The API Key has been deleted. +

+ ) + }) + + // Remove the API Key from the store + setApiKeys((prevApiKeys) => + prevApiKeys.filter( + (apiKey) => apiKey.slug !== apiKeySlug + ) + ) + } + if (error) { + toast.dismiss() + toast.error('Something went wrong!', { + description: ( +

+ Something went wrong while deleting the API Key. Check console for more info. +

+ ) + }) + // eslint-disable-next-line no-console -- we need to log the error + console.error(error) + } + + handleClose() + setIsLoading(false) + }, [setApiKeys, selectedApiKey, handleClose]) + + //Cleaning the pointer events for the context menu after closing the alert dialog + const cleanup = useCallback(() => { + document.body.style.pointerEvents = '' + document.documentElement.style.pointerEvents = '' + }, []) + + useEffect(() => { + if (!isDeleteApiKeyOpen) { + cleanup() + } + return () => cleanup() + }, [isDeleteApiKeyOpen, cleanup]) + + return ( + + + +
+ + + Do you want to delete this API? + +
+ + This action cannot be undone. This will permanently delete your API and remove your API key data from our servers. + +
+ + + Cancel + + + Yes, delete {selectedApiKey ? selectedApiKey.name : "this API Key" } + + +
+
+ ) +} diff --git a/apps/platform/src/lib/controller-instance.ts b/apps/platform/src/lib/controller-instance.ts index a766038fe..640eab0db 100644 --- a/apps/platform/src/lib/controller-instance.ts +++ b/apps/platform/src/lib/controller-instance.ts @@ -7,7 +7,8 @@ import { VariableController, WorkspaceController, WorkspaceMembershipController, - WorkspaceRoleController + WorkspaceRoleController, + ApiKeyController } from '@keyshade/api-client' export default class ControllerInstance { @@ -22,6 +23,7 @@ export default class ControllerInstance { private _environmentController: EnvironmentController private _secretController: SecretController private _variableController: VariableController + private _apiKeyController: ApiKeyController get authController(): AuthController { return this._authController @@ -59,6 +61,10 @@ export default class ControllerInstance { return this._userController } + get apiKeyController(): ApiKeyController { + return this._apiKeyController + } + static getInstance(): ControllerInstance { if (!ControllerInstance.instance) { ControllerInstance.instance = new ControllerInstance() @@ -85,6 +91,9 @@ export default class ControllerInstance { ControllerInstance.instance._variableController = new VariableController( process.env.NEXT_PUBLIC_BACKEND_URL ) + ControllerInstance.instance._apiKeyController = new ApiKeyController( + process.env.NEXT_PUBLIC_BACKEND_URL + ) } return ControllerInstance.instance } diff --git a/apps/platform/src/store/index.ts b/apps/platform/src/store/index.ts index e4d6c0c29..0ee01ef95 100644 --- a/apps/platform/src/store/index.ts +++ b/apps/platform/src/store/index.ts @@ -1,6 +1,7 @@ import { atom } from 'jotai' import { atomWithStorage } from 'jotai/utils' import type { + ApiKey, GetAllEnvironmentsOfProjectResponse, Project, ProjectWithCount, @@ -21,12 +22,14 @@ export const selectedSecretAtom = atom(null) export const selectedEnvironmentAtom = atom< GetAllEnvironmentsOfProjectResponse['items'][number] | null >(null) +export const selectedApiKeyAtom = atom(null) export const projectsOfWorkspaceAtom = atom([]) export const environmentsOfProjectAtom = atom< GetAllEnvironmentsOfProjectResponse['items'] >([]) export const variablesOfProjectAtom = atom([]) export const secretsOfProjectAtom = atom([]) +export const apiKeysOfProjectAtom = atom([]) export const createProjectOpenAtom = atom(false) export const editProjectOpenAtom = atom(false) @@ -43,3 +46,7 @@ export const deleteSecretOpenAtom = atom(false) export const createEnvironmentOpenAtom = atom(false) export const editEnvironmentOpenAtom = atom(false) export const deleteEnvironmentOpenAtom = atom(false) + +export const createApiKeyOpenAtom = atom(false) +export const editApiKeyOpenAtom = atom(false) +export const deleteApiKeyOpenAtom = atom(false) diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts index 1390c3f62..2bfd74635 100644 --- a/packages/api-client/src/index.ts +++ b/packages/api-client/src/index.ts @@ -10,6 +10,7 @@ import WorkspaceRoleController from '@api-client/controllers/workspace-role' import WorkspaceMembershipController from '@api-client/controllers/workspace-membership' import AuthController from '@api-client/controllers/auth' import UserController from '@api-client/controllers/user' +import ApiKeyController from './controllers/api-key' export { AppController, EnvironmentController, @@ -22,5 +23,6 @@ export { WorkspaceRoleController, WorkspaceMembershipController, AuthController, - UserController + UserController, + ApiKeyController } From c8ff770d620119a80c568960f62d08f5854a112a Mon Sep 17 00:00:00 2001 From: poswalsameer Date: Sun, 16 Feb 2025 19:09:37 +0530 Subject: [PATCH 3/6] updated the API Key card with new design, also updated the Create API Key Card dialog --- apps/platform/public/svg/shared/crown.svg | 5 + apps/platform/public/svg/shared/index.ts | 4 +- .../apiKeys/addApiKeyDialog/index.tsx | 252 ++++++++++++++++-- .../userProfile/apiKeys/apiKeyCard/index.tsx | 41 ++- 4 files changed, 275 insertions(+), 27 deletions(-) create mode 100644 apps/platform/public/svg/shared/crown.svg diff --git a/apps/platform/public/svg/shared/crown.svg b/apps/platform/public/svg/shared/crown.svg new file mode 100644 index 000000000..c40541bcf --- /dev/null +++ b/apps/platform/public/svg/shared/crown.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/platform/public/svg/shared/index.ts b/apps/platform/public/svg/shared/index.ts index 438a6638b..5f11aeaa6 100644 --- a/apps/platform/public/svg/shared/index.ts +++ b/apps/platform/public/svg/shared/index.ts @@ -12,6 +12,7 @@ import VectorSVG from './vector.svg' import ErrorSVG from './Error.svg' import TrashSVG from './trash.svg' import CopySVG from './copy.svg' +import CrownSVG from './crown.svg' export { DropdownSVG, @@ -27,5 +28,6 @@ export { VectorSVG, ErrorSVG, TrashSVG, - CopySVG + CopySVG, + CrownSVG } diff --git a/apps/platform/src/components/userProfile/apiKeys/addApiKeyDialog/index.tsx b/apps/platform/src/components/userProfile/apiKeys/addApiKeyDialog/index.tsx index b93dfebe9..818f157d4 100644 --- a/apps/platform/src/components/userProfile/apiKeys/addApiKeyDialog/index.tsx +++ b/apps/platform/src/components/userProfile/apiKeys/addApiKeyDialog/index.tsx @@ -25,16 +25,171 @@ import { createApiKeyOpenAtom, apiKeysOfProjectAtom } from '@/store' +import { Checkbox } from '@/components/ui/checkbox' + +const authorityGroups = [ + { + name: "USERS", + description: "Full access to all user actions", + permissions: [ + { id: "ADD_USER", label: "Add", description: "Access to add users" }, + { id: "READ_USERS", label: "Read", description: "Access to read users" }, + { id: "REMOVE_USER", label: "Remove", description: "Access to remove users" }, + { id: "UPDATE_USER_ROLE", label: "Update", description: "Access to update users" }, + ], + }, + { + name: "PROJECT", + description: "Full access to all project actions", + permissions: [ + { id: "CREATE_PROJECT", label: "Create", description: "Access to create projects" }, + { id: "READ_PROJECT", label: "Read", description: "Access to read projects" }, + { id: "UPDATE_PROJECT", label: "Update", description: "Access to update projects" }, + { id: "DELETE_PROJECT", label: "Delete", description: "Access to delete projects" }, + ], + }, + { + name: "WORKSPACE", + description: "Full access to all workspace actions", + permissions: [ + { id: "CREATE_WORKSPACE", label: "Create", description: "Access to create workspaces" }, + { id: "READ_WORKSPACE", label: "Read", description: "Access to read workspaces" }, + { id: "UPDATE_WORKSPACE", label: "Update", description: "Access to update workspaces" }, + { id: "DELETE_WORKSPACE", label: "Delete", description: "Access to delete workspaces" }, + { id: "WORKSPACE_ADMIN", label: "Admin", description: "Access to admin workspace" }, + { id: "CREATE_WORKSPACE_ROLE", label: "Create_Role", description: "Access to create_role workspace" }, + { id: "READ_WORKSPACE_ROLE", label: "Read_Role", description: "Access to read_role workspace" }, + { id: "UPDATE_WORKSPACE_ROLE", label: "Update_Role", description: "Access to update_role workspace" }, + ], + }, + { + name: "SECRET", + description: "Full access to all secret actions", + permissions: [ + { id: "CREATE_SECRET", label: "Create", description: "Access to create secrets" }, + { id: "READ_SECRET", label: "Read", description: "Access to read secrets" }, + { id: "UPDATE_SECRET", label: "Update", description: "Access to update secrets" }, + { id: "DELETE_SECRET", label: "Delete", description: "Access to delete secrets" }, + ], + }, + { + name: "ENVIRONMENT", + description: "Full access to all environment actions", + permissions: [ + { id: "CREATE_ENVIRONMENT", label: "Create", description: "Access to create environments" }, + { id: "READ_ENVIRONMENT", label: "Read", description: "Access to read environments" }, + { id: "UPDATE_ENVIRONMENT", label: "Update", description: "Access to update environments" }, + { id: "DELETE_ENVIRONMENT", label: "Delete", description: "Access to delete environments" }, + ], + }, + { + name: "VARIABLE", + description: "Full access to all variable actions", + permissions: [ + { id: "CREATE_VARIABLE", label: "Create", description: "Access to create variables" }, + { id: "READ_VARIABLE", label: "Read", description: "Access to read variables" }, + { id: "UPDATE_VARIABLE", label: "Update", description: "Access to update variables" }, + { id: "DELETE_VARIABLE", label: "Delete", description: "Access to delete variables" }, + ], + }, + { + name: "INTEGRATIONS", + description: "Full access to all integration actions", + permissions: [ + { id: "CREATE_INTEGRATION", label: "Create", description: "Access to create integrations" }, + { id: "READ_INTEGRATION", label: "Read", description: "Access to read integrations" }, + { id: "UPDATE_INTEGRATION", label: "Update", description: "Access to update integrations" }, + { id: "DELETE_INTEGRATION", label: "Delete", description: "Access to delete integrations" }, + ], + }, + { + name: "API-KEY", + description: "Full access to all API-Key actions", + permissions: [ + { id: "CREATE_API_KEY", label: "Create", description: "Access to create API-Key" }, + { id: "READ_API_KEY", label: "Read", description: "Access to read API-Key" }, + { id: "UPDATE_API_KEY", label: "Update", description: "Access to update API-Key" }, + { id: "DELETE_API_KEY", label: "Delete", description: "Access to delete API-Key" }, + ], + }, + { + name: "UPDATE_PROFILE", + description: "Full access to all update_profile actions", + id: "UPDATE_PROFILE" + }, + { + name: "READ_SELF", + description: "Full access to all read_self actions", + id: "READ_SELF" + }, + { + name: "UPDATE_SELF_READ_EVENT", + description: "Full access to all update_self_read_event actions", + id: "UPDATE_SELF" + }, + { + name: "ADMIN", + description: "Full access to all admin actions", + id: "ADMIN" + }, +] export default function AddApiKeyDialog() { const [isLoading, setIsLoading] = useState(false) const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useAtom(createApiKeyOpenAtom) const setApiKeys = useSetAtom(apiKeysOfProjectAtom) - const [newApiKeyData, setNewApiKeyData] = useState({ apiKeyName: '', expiryDate: '24' }) + const [selectedPermissions, setSelectedPermissions] = React.useState>(new Set()) + + const togglePermission = useCallback((permissionId: string) => { + setSelectedPermissions((current) => { + const newPermissions = new Set(current) + if (newPermissions.has(permissionId)) { + newPermissions.delete(permissionId) + } else { + newPermissions.add(permissionId) + } + return newPermissions + }) + }, []) + + const getGroupState = useCallback((group: any) => { + if (!group.permissions) { + return selectedPermissions.has(group.id) + } + const groupPermissions = group.permissions.map((p) => p.id) + const selectedGroupPermissions = groupPermissions.filter((p) => selectedPermissions.has(p)) + + if (selectedGroupPermissions.length === 0) return false + if (selectedGroupPermissions.length === groupPermissions.length) return true + return "indeterminate" + }, [selectedPermissions]) + + const toggleGroup = useCallback((group: any) => { + setSelectedPermissions((current) => { + const newPermissions = new Set(current) + if (group.permissions) { + const groupState = getGroupState(group) + group.permissions.forEach((permission) => { + if (groupState === true) { + newPermissions.delete(permission.id) + } else { + newPermissions.add(permission.id) + } + }) + } else { + if (newPermissions.has(group.id)) { + newPermissions.delete(group.id) + } else { + newPermissions.add(group.id) + } + } + return newPermissions + }) + }, [getGroupState]) const handleAddApiKey = useCallback(async () => { setIsLoading(true) @@ -43,14 +198,20 @@ export default function AddApiKeyDialog() { toast.error('API Key name is required') return } - if (!newApiKeyData.expiryDate) { + + const expiryDate = newApiKeyData.expiryDate || '24'; + if (!expiryDate) { toast.error('Expiry Date is required') return } + // Create a new array from selectedPermissions to ensure we have the latest state + const authoritiesArray = Array.from(selectedPermissions).filter(Boolean) + const request: CreateApiKeyRequest = { name: newApiKeyData.apiKeyName, - expiresAfter: newApiKeyData.expiryDate as "never" | "24" | "168" | "720" | "8760" | undefined + expiresAfter: expiryDate as "never" | "24" | "168" | "720" | "8760", + authorities: authoritiesArray } toast.loading("Creating your API Key...") @@ -66,7 +227,6 @@ export default function AddApiKeyDialog() {

You created a new API Key

) }) - // Add the new API Key to the list of keys setApiKeys((prev) => [...prev, data]) } if (error) { @@ -87,18 +247,19 @@ export default function AddApiKeyDialog() {

) }) - // eslint-disable-next-line no-console -- we need to log the error that are not in the if condition + // eslint-disable-next-line no-console -- we need to log the error console.error(error) } } setNewApiKeyData({ apiKeyName: '', - expiryDate: '' + expiryDate: '24' }) setIsLoading(false) setIsCreateApiKeyOpen(false) - }, [newApiKeyData, setIsCreateApiKeyOpen, setApiKeys]) + setSelectedPermissions(new Set()) + }, [newApiKeyData, selectedPermissions, setIsCreateApiKeyOpen, setApiKeys]) return ( Add API Key - + Add a new API Key @@ -123,9 +284,9 @@ export default function AddApiKeyDialog() { -
+
-
+
-
+
+
+ +
+ {authorityGroups.map((group) => ( +
+
+ toggleGroup(group)} + className="text-black border border-[#18181B] bg-[#71717A] rounded-[4px] data-[state=checked]:border-[#18181B] data-[state=checked]:bg-[#71717A] data-[state=checked]:text-black" + data-state={getGroupState(group)} + /> +
+ +

+ {group.description} +

+
+
+
+ {group.permissions?.map((permission) => ( +
+ togglePermission(permission.id)} + className="border border-[#18181B] bg-[#71717A] rounded-[4px] data-[state=checked]:border-[#18181B] data-[state=checked]:bg-[#71717A] data-[state=checked]:text-black" + /> +
+ +

+ {permission.description} +

+
+
+ ))} +
+
+ ))} +
+
+