From 21bc6296699b6f14da4aa162d08cbd0cbf826444 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Wed, 18 Mar 2026 15:45:59 -0300 Subject: [PATCH 1/3] refactor: add RTK Query version of ServerSideSDKKeys components Migrate ServerSideSDKKeys from Flux class components to functional components with RTK Query hooks, split into smaller focused components: - CreateServerSideKeyModal: modal for creating new keys - ServerSideKeyRow: individual key row rendering - ServerSideSDKKeys: main component using RTK Query hooks This fixes FLAGSMITH-FRONTEND-4FX where ProjectStore.getEnvironment() could return undefined and crash the modal render. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/CreateServerSideKeyModal.tsx | 66 ++++++++ .../sdk-keys/components/ServerSideKeyRow.tsx | 50 ++++++ .../sdk-keys/components/ServerSideSDKKeys.tsx | 160 ++++++++++++++++++ .../pages/sdk-keys/components/index.ts | 3 + 4 files changed, 279 insertions(+) create mode 100644 frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx create mode 100644 frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx create mode 100644 frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx create mode 100644 frontend/web/components/pages/sdk-keys/components/index.ts 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..a0c137c90c7f --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx @@ -0,0 +1,66 @@ +import React, { FC, FormEvent, useEffect, useState } from 'react' +import Button from 'components/base/forms/Button' +import ModalHR from 'components/modals/ModalHR' +import Utils from 'common/utils/utils' + +type CreateServerSideKeyModalProps = { + environmentName: string + onSubmit: (name: string) => void +} + +const CreateServerSideKeyModal: FC = ({ + environmentName, + onSubmit, +}) => { + const [name, setName] = useState('') + const [isSaving, setIsSaving] = useState(false) + + useEffect(() => { + const timer = setTimeout(() => { + document.getElementById('jsTokenName')?.focus() + }, 500) + return () => clearTimeout(timer) + }, []) + + const handleSubmit = (e: FormEvent) => { + Utils.preventDefault(e) + if (name) { + setIsSaving(true) + onSubmit(name) + } + } + + return ( +
+
+
+
+ This will create a Server-side Environment Key for the environment{' '} + {environmentName}. +
+ setName(Utils.safeParseEventValue(e))} + /> +
+ +
+ + +
+ +
+ ) +} + +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..dbba57bc7351 --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx @@ -0,0 +1,50 @@ +import React, { FC } from 'react' +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..b6f6416311ff --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx @@ -0,0 +1,160 @@ +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 { + useCreateServersideEnvironmentKeysMutation, + useDeleteServersideEnvironmentKeysMutation, + useGetServersideEnvironmentKeysQuery, +} from 'common/services/useServersideEnvironmentKey' +import { useGetEnvironmentsQuery } from 'common/services/useEnvironment' +import CreateServerSideKeyModal from './CreateServerSideKeyModal' +import ServerSideKeyRow from './ServerSideKeyRow' + +type ServerSideSDKKeysProps = { + environmentId: string + projectId: string +} + +const ServerSideSDKKeys: FC = ({ + environmentId, + projectId, +}) => { + const { permission: isAdmin } = useHasPermission({ + id: environmentId, + level: 'environment', + permission: 'ADMIN', + }) + + const { data: keys, isLoading } = useGetServersideEnvironmentKeysQuery( + { environmentId }, + { skip: !environmentId }, + ) + + const { data: environments } = useGetEnvironmentsQuery( + { projectId: parseInt(projectId, 10) }, + { skip: !projectId }, + ) + + const [createKey] = useCreateServersideEnvironmentKeysMutation() + const [deleteKey, { isLoading: isDeleting }] = + useDeleteServersideEnvironmentKeysMutation() + + const environmentName = + environments?.results?.find((env) => env.api_key === environmentId)?.name ?? + '' + + const handleCreate = () => { + openModal( + 'Create Server-side Environment Keys', + { + createKey({ + data: { name }, + environmentId, + }).then(() => { + closeModal() + }) + }} + />, + 'p-0', + ) + } + + 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', + }) + } + + 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('ADMIN')} + + )} +
+ {isLoading && ( +
+ +
+ )} + {keys && !!keys.length && ( + { + const strToSearch = `${item.name}` + return ( + strToSearch.toLowerCase().indexOf(search.toLowerCase()) !== -1 + ) + }} + renderRow={({ + id, + key, + name, + }: { + id: string + key: string + name: string + }) => ( + + )} + /> + )} +
+ ) +} + +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' From bb3dd759460d8fc97441a78d405b402ae1d2d3f0 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Wed, 18 Mar 2026 15:46:15 -0300 Subject: [PATCH 2/3] refactor: add sdk-keys feature page with barrel exports Create the new SDKKeysPage within the pages/sdk-keys feature folder, following the domain-driven folder pattern used by other features (e.g. pages/features/, pages/organisation-settings/). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/pages/sdk-keys/SDKKeysPage.tsx | 64 +++++++++++++++++++ .../web/components/pages/sdk-keys/index.ts | 1 + 2 files changed, 65 insertions(+) create mode 100644 frontend/web/components/pages/sdk-keys/SDKKeysPage.tsx create mode 100644 frontend/web/components/pages/sdk-keys/index.ts diff --git a/frontend/web/components/pages/sdk-keys/SDKKeysPage.tsx b/frontend/web/components/pages/sdk-keys/SDKKeysPage.tsx new file mode 100644 index 000000000000..6fcae647030d --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/SDKKeysPage.tsx @@ -0,0 +1,64 @@ +import React, { FC } from 'react' +import Button from 'components/base/forms/Button' +import Input from 'components/base/forms/Input' +import Icon from 'components/Icon' +import PageTitle from 'components/PageTitle' +import Utils from 'common/utils/utils' +import { useRouteMatch } from 'react-router-dom' +import { ServerSideSDKKeys } from './components' + +interface RouteParams { + environmentId: string + projectId: string +} + +const SDKKeysPage: FC = () => { + const match = useRouteMatch() + const environmentId = match?.params?.environmentId + const projectId = match?.params?.projectId + + return ( +
+ + Use this key to initialise{' '} + {' '} + SDKs. + +
+ + + Client-side Environment Key} + placeholder='Client-side Environment Key' + /> + + + +
+
+ +
+ ) +} + +export default SDKKeysPage diff --git a/frontend/web/components/pages/sdk-keys/index.ts b/frontend/web/components/pages/sdk-keys/index.ts new file mode 100644 index 000000000000..b9fcace1367e --- /dev/null +++ b/frontend/web/components/pages/sdk-keys/index.ts @@ -0,0 +1 @@ +export { default as SDKKeysPage } from './SDKKeysPage' From ffb4bbaf87c781eed28ce541c536287f4d315712 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Wed, 18 Mar 2026 15:46:32 -0300 Subject: [PATCH 3/3] feat: gate new sdk-keys page behind rtk_server_side_sdk_keys flag Wire up feature flag in the existing SDKKeysPage entry point to switch between the legacy Flux-based implementation and the new RTK Query version. When the flag is off, behaviour is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/web/components/SDKKeysPage.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/web/components/SDKKeysPage.tsx b/frontend/web/components/SDKKeysPage.tsx index abfe9feed72c..c662f44c4025 100644 --- a/frontend/web/components/SDKKeysPage.tsx +++ b/frontend/web/components/SDKKeysPage.tsx @@ -2,7 +2,8 @@ import React, { 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 { SDKKeysPage as SDKKeysPageNew } from './pages/sdk-keys' import PageTitle from './PageTitle' import Utils from 'common/utils/utils' import { useRouteMatch } from 'react-router-dom' @@ -16,6 +17,10 @@ const SDKKeysPage: FC = () => { const match = useRouteMatch() const environmentId = match?.params?.environmentId + if (Utils.getFlagsmithHasFeature('rtk_server_side_sdk_keys')) { + return + } + return (
{

- + ) }