diff --git a/frontend/common/services/useServersideEnvironmentKey.ts b/frontend/common/services/useServersideEnvironmentKey.ts index 88c697b0e872..623d6c32052e 100644 --- a/frontend/common/services/useServersideEnvironmentKey.ts +++ b/frontend/common/services/useServersideEnvironmentKey.ts @@ -32,9 +32,7 @@ export const serversideEnvironmentKeyService = service Res['serversideEnvironmentKeys'], Req['getServersideEnvironmentKeys'] >({ - providesTags: (res) => [ - { id: res?.id, type: 'ServersideEnvironmentKey' }, - ], + providesTags: [{ id: 'LIST', type: 'ServersideEnvironmentKey' }], query: (query: Req['getServersideEnvironmentKeys']) => ({ url: `environments/${query.environmentId}/api-keys/`, }), diff --git a/frontend/web/components/SDKKeysPage.tsx b/frontend/web/components/SDKKeysPage.tsx index abfe9feed72c..bea2fa57fafc 100644 --- a/frontend/web/components/SDKKeysPage.tsx +++ b/frontend/web/components/SDKKeysPage.tsx @@ -1,11 +1,13 @@ -import React, { FC } from 'react' +import { FC } from 'react' import Button from './base/forms/Button' import Input from './base/forms/Input' import Icon from './Icon' -import ServerSideSDKKeys from './ServerSideSDKKeys' +import ServerSideSDKKeysLegacy from './ServerSideSDKKeys' +import { ServerSideSDKKeys } from './pages/sdk-keys/components' import PageTitle from './PageTitle' import Utils from 'common/utils/utils' import { useRouteMatch } from 'react-router-dom' +import { useGetEnvironmentsQuery } from 'common/services/useEnvironment' interface RouteParams { environmentId: string @@ -15,11 +17,23 @@ interface RouteParams { const SDKKeysPage: FC = () => { const match = useRouteMatch() const environmentId = match?.params?.environmentId + const projectId = match?.params?.projectId + + const { data: environments } = useGetEnvironmentsQuery( + { projectId: parseInt(projectId, 10) }, + { skip: !projectId }, + ) + + const environmentName = + environments?.results?.find((env) => env.api_key === environmentId)?.name ?? + '' + + const handleCopy = () => Utils.copyToClipboard(environmentId) return (
@@ -27,7 +41,7 @@ const SDKKeysPage: FC = () => { {' '} @@ -44,18 +58,20 @@ const SDKKeysPage: FC = () => { placeholder='Client-side Environment Key' /> -

- + {Utils.getFlagsmithHasFeature('rtk_server_side_sdk_keys') ? ( + + ) : ( + + )} ) } diff --git a/frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx b/frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx new file mode 100644 index 000000000000..30f42406746f --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx @@ -0,0 +1,69 @@ +import React, { FC, useActionState, useEffect, useRef } from 'react' +import Button from 'components/base/forms/Button' +import ModalHR from 'components/modals/ModalHR' + +type CreateServerSideKeyModalProps = { + environmentName: string + onSubmit: (name: string) => Promise +} + +const CreateServerSideKeyModal: FC = ({ + environmentName, + onSubmit, +}) => { + const inputRef = useRef(null) + + useEffect(() => { + const timer = setTimeout(() => inputRef.current?.focus(), 500) + return () => clearTimeout(timer) + }, []) + + const [error, submitAction, isPending] = useActionState( + async (_prev: string | null, formData: FormData) => { + const name = (formData.get('name') as string)?.trim() + if (!name) return 'Name is required' + try { + await onSubmit(name) + return null + } catch { + return 'Failed to create key. Please try again.' + } + }, + null, + ) + + return ( +
+
+
+
+ This will create a Server-side Environment Key for the environment{' '} + {environmentName}. +
+ + {error &&
{error}
} +
+ +
+ + +
+ +
+ ) +} + +export default CreateServerSideKeyModal diff --git a/frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx b/frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx new file mode 100644 index 000000000000..275ce56d06d6 --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx @@ -0,0 +1,54 @@ +import React, { FC } from 'react' +import cn from 'classnames' +import Button from 'components/base/forms/Button' +import Icon from 'components/Icon' +import Token from 'components/Token' +import Utils from 'common/utils/utils' + +type ServerSideKeyRowProps = { + id: string + keyValue: string + name: string + isDeleting: boolean + onRemove: (id: string, name: string) => void +} + +const ServerSideKeyRow: FC = ({ + id, + isDeleting, + keyValue, + name, + onRemove, +}) => { + return ( + + {name} +
+ +
+ +
+ +
+
+ ) +} + +export default ServerSideKeyRow diff --git a/frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx b/frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx new file mode 100644 index 000000000000..6a21a8585ad0 --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx @@ -0,0 +1,115 @@ +import React, { FC } from 'react' +import Button from 'components/base/forms/Button' +import Tooltip from 'components/Tooltip' +import Constants from 'common/constants' +import { useHasPermission } from 'common/providers/Permission' +import { EnvironmentPermission } from 'common/types/permissions.types' +import CreateServerSideKeyModal from './CreateServerSideKeyModal' +import ServerSideKeyRow from './ServerSideKeyRow' +import { useServerSideKeys } from 'components/pages/sdk-keys/hooks/useServerSideKeys' + +type ServerSideKey = { + id: string + key: string + name: string +} + +type ServerSideSDKKeysProps = { + environmentId: string + environmentName: string +} + +const ServerSideSDKKeys: FC = ({ + environmentId, + environmentName, +}) => { + const { permission: isAdmin } = useHasPermission({ + id: environmentId, + level: 'environment', + permission: EnvironmentPermission.ADMIN, + }) + + const { handleCreateKey, handleRemove, isDeletingKey, isLoading, keys } = + useServerSideKeys({ environmentId }) + + const handleCreate = () => { + openModal( + 'Create Server-side Environment Keys', + , + 'p-0', + ) + } + + const filterByName = (item: ServerSideKey, search: string) => + item.name.toLowerCase().includes(search.toLowerCase()) + + const renderKeyRow = ({ id, key, name }: ServerSideKey) => ( + + ) + + return ( + +
+
Server-side Environment Keys
+

+ Flags can be evaluated locally within your own Server environments + using our{' '} + + . +

+

+ Server-side SDKs should be initialised with a Server-side Environment + Key. +

+ {isAdmin ? ( + + ) : ( + + Create Server-side Environment Key + + } + place='right' + > + {Constants.environmentPermissions(EnvironmentPermission.ADMIN)} + + )} +
+ {isLoading && ( +
+ +
+ )} + {keys && !!keys.length && ( + + )} +
+ ) +} + +export default ServerSideSDKKeys diff --git a/frontend/web/components/pages/sdk-keys/components/index.ts b/frontend/web/components/pages/sdk-keys/components/index.ts new file mode 100644 index 000000000000..5293b760dfda --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/index.ts @@ -0,0 +1,3 @@ +export { default as CreateServerSideKeyModal } from './CreateServerSideKeyModal' +export { default as ServerSideKeyRow } from './ServerSideKeyRow' +export { default as ServerSideSDKKeys } from './ServerSideSDKKeys' diff --git a/frontend/web/components/pages/sdk-keys/hooks/useServerSideKeys.tsx b/frontend/web/components/pages/sdk-keys/hooks/useServerSideKeys.tsx new file mode 100644 index 000000000000..2599d818e202 --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/hooks/useServerSideKeys.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import { + useCreateServersideEnvironmentKeysMutation, + useDeleteServersideEnvironmentKeysMutation, + useGetServersideEnvironmentKeysQuery, +} from 'common/services/useServersideEnvironmentKey' + +type UseServerSideKeysParams = { + environmentId: string +} + +export const useServerSideKeys = ({ + environmentId, +}: UseServerSideKeysParams) => { + const { data: keys, isLoading } = useGetServersideEnvironmentKeysQuery( + { environmentId }, + { skip: !environmentId }, + ) + + const [createKey] = useCreateServersideEnvironmentKeysMutation() + const [deleteKey, { isLoading: isDeleting, originalArgs: deleteArgs }] = + useDeleteServersideEnvironmentKeysMutation() + + const handleCreateKey = async (name: string) => { + await createKey({ data: { name }, environmentId }).unwrap() + closeModal() + } + + const handleRemove = (id: string, name: string) => { + openConfirm({ + body: ( +
+ Are you sure you want to remove the SDK key {name}? + This action cannot be undone. +
+ ), + destructive: true, + onYes: () => { + deleteKey({ environmentId, id }) + }, + title: 'Delete Server-side Environment Keys', + yesText: 'Confirm', + }) + } + + const isDeletingKey = (id: string) => isDeleting && deleteArgs?.id === id + + return { + handleCreateKey, + handleRemove, + isDeletingKey, + isLoading, + keys, + } +} diff --git a/frontend/web/styles/project/_utils.scss b/frontend/web/styles/project/_utils.scss index 2eb95fa53cf6..64dfbb4ee3aa 100644 --- a/frontend/web/styles/project/_utils.scss +++ b/frontend/web/styles/project/_utils.scss @@ -238,6 +238,9 @@ .pointer-events-none { pointer-events: none; } +.opacity-50 { + opacity: 0.5; +} .bg-primary-opacity-5 { background-color: transparentize($primary, 0.95);