diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx
index 25bdabec38..b63896ac7c 100644
--- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx
+++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx
@@ -26,8 +26,6 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli
import { Search } from 'component/common/Search/Search';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
-import { Route, Routes } from 'react-router-dom';
-import { ProjectApiTokenCreate } from './ProjectApiTokenCreate';
const hiddenColumnsSmall = ['Icon', 'createdAt'];
const hiddenColumnsCompact = ['Icon', 'project', 'seenAt'];
@@ -67,11 +65,11 @@ export const ApiTokenTable = ({
Cell: ({
value,
}: {
- value: keyof typeof tokenDescriptions;
+ value: 'client' | 'admin' | 'frontend';
}) => (
),
minWidth: 280,
@@ -246,44 +244,21 @@ export const ApiTokenTable = ({
/>
}
/>
-
- }
- />
-
- }
- />
);
};
-//TODO fix me - remove duplicate keys
-const tokenDescriptions = {
- client: {
- label: 'CLIENT',
- title: 'Connect server-side SDK or Unleash Proxy',
- },
- frontend: {
- label: 'FRONTEND',
- title: 'Connect web and mobile SDK',
- },
- admin: {
- label: 'ADMIN',
- title: 'Full access for managing Unleash',
- },
- CLiENT: {
- label: 'CLIENT',
- title: 'Connect server-side SDK or Unleash Proxy',
- },
- FRONTEND: {
- label: 'FRONTEND',
- title: 'Connect web and mobile SDK',
- },
- ADMIN: {
- label: 'ADMIN',
- title: 'Full access for managing Unleash',
- },
-};
+const tokenDescriptions: { [index: string]: { label: string; title: string } } =
+ {
+ client: {
+ label: 'CLIENT',
+ title: 'Connect server-side SDK or Unleash Proxy',
+ },
+ frontend: {
+ label: 'FRONTEND',
+ title: 'Connect web and mobile SDK',
+ },
+ admin: {
+ label: 'ADMIN',
+ title: 'Full access for managing Unleash',
+ },
+ };
diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx
deleted file mode 100644
index 26eaec1398..0000000000
--- a/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import useProjectAccess from 'hooks/api/getters/useProjectAccess/useProjectAccess';
-import { useAccess } from 'hooks/api/getters/useAccess/useAccess';
-import { GO_BACK } from 'constants/navigate';
-import { CreateApiToken } from '../CreateApiToken/CreateApiToken';
-import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
-import { useNavigate } from 'react-router-dom';
-
-export const ProjectApiTokenCreate = () => {
- const projectId = useRequiredPathParam('projectId');
- const navigate = useNavigate();
-
- const { access } = useProjectAccess(projectId);
- const { users, serviceAccounts, groups } = useAccess();
-
- if (!access || !users || !serviceAccounts || !groups) {
- return null;
- }
-
- return (
- navigate(GO_BACK)}
- label={`Create API token`}
- >
-
-
- );
-};
diff --git a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
index 9a63d346cd..e4f13fd8d4 100644
--- a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
+++ b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
@@ -7,32 +7,23 @@ import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { useApiTokenForm } from 'component/admin/apiToken/ApiTokenForm/useApiTokenForm';
-import {
- CREATE_API_TOKEN,
- CREATE_PROJECT_API_TOKEN,
-} from 'component/providers/AccessProvider/permissions';
+import { CREATE_API_TOKEN } from 'component/providers/AccessProvider/permissions';
import { ConfirmToken } from '../ConfirmToken/ConfirmToken';
import { scrollToTop } from 'component/common/util';
import { formatUnknownError } from 'utils/formatUnknownError';
import { usePageTitle } from 'hooks/usePageTitle';
import { GO_BACK } from 'constants/navigate';
import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens';
-import useProjectApiTokensApi from 'hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
import { TokenInfo } from '../ApiTokenForm/TokenInfo/TokenInfo';
import { TokenTypeSelector } from '../ApiTokenForm/TokenTypeSelector/TokenTypeSelector';
import { ProjectSelector } from '../ApiTokenForm/ProjectSelector/ProjectSelector';
import { EnvironmentSelector } from '../ApiTokenForm/EnvironmentSelector/EnvironmentSelector';
const pageTitle = 'Create API token';
-
interface ICreateApiTokenProps {
modal?: boolean;
- project?: string;
}
-export const CreateApiToken = ({
- modal = false,
- project,
-}: ICreateApiTokenProps) => {
+export const CreateApiToken = ({ modal = false }: ICreateApiTokenProps) => {
const { setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
@@ -52,22 +43,16 @@ export const CreateApiToken = ({
isValid,
errors,
clearErrors,
- } = useApiTokenForm(project);
+ } = useApiTokenForm();
- const { createToken, loading: globalLoading } = useApiTokensApi();
- const { createToken: createProjectToken, loading: projectLoading } =
- useProjectApiTokensApi();
+ const { createToken, loading } = useApiTokensApi();
const { refetch } = useApiTokens();
usePageTitle(pageTitle);
- const PATH = Boolean(project)
- ? `api/admin/project/${project}/api-tokens`
- : 'api/admin/api-tokens';
- const permission = Boolean(project)
- ? CREATE_PROJECT_API_TOKEN
- : CREATE_API_TOKEN;
- const loading = globalLoading || projectLoading;
+ const PATH = `api/admin/api-tokens`;
+
+ const permission = CREATE_API_TOKEN;
const handleSubmit = async (e: Event) => {
e.preventDefault();
@@ -76,23 +61,15 @@ export const CreateApiToken = ({
}
try {
const payload = getApiTokenPayload();
- if (project) {
- await createProjectToken(payload, project)
- .then(res => res.json())
- .then(api => {
- scrollToTop();
- setToken(api.secret);
- setShowConfirm(true);
- });
- } else {
- await createToken(payload)
- .then(res => res.json())
- .then(api => {
- scrollToTop();
- setToken(api.secret);
- setShowConfirm(true);
- });
- }
+
+ await createToken(payload)
+ .then(res => res.json())
+ .then(api => {
+ scrollToTop();
+ setToken(api.secret);
+ setShowConfirm(true);
+ refetch();
+ });
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
@@ -100,7 +77,6 @@ export const CreateApiToken = ({
const closeConfirm = () => {
setShowConfirm(false);
- refetch();
navigate(GO_BACK);
};
@@ -131,13 +107,7 @@ export const CreateApiToken = ({
handleSubmit={handleSubmit}
handleCancel={handleCancel}
mode="Create"
- actions={
-
- }
+ actions={}
>
{
const permission = Boolean(project)
? CREATE_PROJECT_API_TOKEN
: CREATE_API_TOKEN;
+
return (
{
+ const navigate = useNavigate();
+
+ return (
+ navigate(GO_BACK)}
+ label={`Create API token`}
+ >
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/CreateProjectApiTokenForm.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/CreateProjectApiTokenForm.tsx
new file mode 100644
index 0000000000..f40a9e3c55
--- /dev/null
+++ b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/CreateProjectApiTokenForm.tsx
@@ -0,0 +1,144 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import FormTemplate from 'component/common/FormTemplate/FormTemplate';
+
+import { CreateButton } from 'component/common/CreateButton/CreateButton';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import useToast from 'hooks/useToast';
+import { useApiTokenForm } from 'component/admin/apiToken/ApiTokenForm/useApiTokenForm';
+import { CREATE_PROJECT_API_TOKEN } from 'component/providers/AccessProvider/permissions';
+import { scrollToTop } from 'component/common/util';
+import { formatUnknownError } from 'utils/formatUnknownError';
+import { usePageTitle } from 'hooks/usePageTitle';
+import { GO_BACK } from 'constants/navigate';
+import useProjectApiTokensApi from 'hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
+
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
+import ApiTokenForm from 'component/admin/apiToken/ApiTokenForm/ApiTokenForm';
+import { EnvironmentSelector } from 'component/admin/apiToken/ApiTokenForm/EnvironmentSelector/EnvironmentSelector';
+import { TokenInfo } from 'component/admin/apiToken/ApiTokenForm/TokenInfo/TokenInfo';
+import { TokenTypeSelector } from 'component/admin/apiToken/ApiTokenForm/TokenTypeSelector/TokenTypeSelector';
+import { ConfirmToken } from 'component/admin/apiToken/ConfirmToken/ConfirmToken';
+import { useProjectApiTokens } from 'hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
+import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
+
+const pageTitle = 'Create project API token';
+
+export const CreateProjectApiTokenForm = () => {
+ const project = useRequiredPathParam('projectId');
+ const { setToastApiError } = useToast();
+ const { uiConfig } = useUiConfig();
+ const navigate = useNavigate();
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [token, setToken] = useState('');
+
+ const {
+ getApiTokenPayload,
+ username,
+ type,
+ environment,
+ setUsername,
+ setTokenType,
+ setEnvironment,
+ isValid,
+ errors,
+ clearErrors,
+ } = useApiTokenForm(project);
+
+ const { createToken: createProjectToken, loading } =
+ useProjectApiTokensApi();
+ const { refetch: refetchProjectTokens } = useProjectApiTokens(project);
+ const { trackEvent } = usePlausibleTracker();
+
+ usePageTitle(pageTitle);
+
+ const PATH = `api/admin/project/${project}/api-tokens`;
+ const permission = CREATE_PROJECT_API_TOKEN;
+
+ const handleSubmit = async (e: Event) => {
+ e.preventDefault();
+ if (!isValid()) {
+ return;
+ }
+ try {
+ const payload = getApiTokenPayload();
+
+ await createProjectToken(payload, project)
+ .then(res => res.json())
+ .then(api => {
+ scrollToTop();
+ setToken(api.secret);
+ setShowConfirm(true);
+ trackEvent('project_api_tokens', {
+ props: { eventType: 'api_key_created' },
+ });
+
+ refetchProjectTokens();
+ });
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ };
+
+ const closeConfirm = () => {
+ setShowConfirm(false);
+ navigate(GO_BACK);
+ };
+
+ const formatApiCode = () => {
+ return `curl --location --request POST '${
+ uiConfig.unleashUrl
+ }/${PATH}' \\
+--header 'Authorization: INSERT_API_KEY' \\
+--header 'Content-Type: application/json' \\
+--data-raw '${JSON.stringify(getApiTokenPayload(), undefined, 2)}'`;
+ };
+
+ const handleCancel = () => {
+ navigate(GO_BACK);
+ };
+
+ return (
+
+
+ }
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess.tsx
similarity index 76%
rename from frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx
rename to frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess.tsx
index 729dbd4a38..11e476761c 100644
--- a/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx
+++ b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess.tsx
@@ -7,8 +7,10 @@ import { READ_PROJECT_API_TOKEN } from 'component/providers/AccessProvider/permi
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { usePageTitle } from 'hooks/usePageTitle';
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
-import { ApiTokenTable } from '../../../admin/apiToken/ApiTokenTable/ApiTokenTable';
-import { useProjectApiTokens } from '../../../../hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
+import { CreateProjectApiToken } from 'component/project/Project/ProjectSettings/ProjectApiAccess/CreateProjectApiToken';
+import { Routes, Route } from 'react-router-dom';
+import { ApiTokenTable } from 'component/admin/apiToken/ApiTokenTable/ApiTokenTable';
+import { useProjectApiTokens } from 'hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
export const ProjectApiAccess = () => {
const projectId = useRequiredPathParam('projectId');
@@ -37,6 +39,10 @@ export const ProjectApiAccess = () => {
compact
filterForProject={projectId}
/>
+
+
+ } />
+
);
};
diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
index 972f48370c..7cef6a9f19 100644
--- a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
+++ b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
@@ -9,7 +9,7 @@ import { ITab, VerticalTabs } from 'component/common/VerticalTabs/VerticalTabs';
import { ProjectAccess } from 'component/project/ProjectAccess/ProjectAccess';
import ProjectEnvironmentList from 'component/project/ProjectEnvironment/ProjectEnvironment';
import { ChangeRequestConfiguration } from './ChangeRequestConfiguration/ChangeRequestConfiguration';
-import { ProjectApiAccess } from './ProjectApiAccess';
+import { ProjectApiAccess } from './ProjectApiAccess/ProjectApiAccess';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
export const ProjectSettings = () => {
diff --git a/frontend/src/hooks/usePlausibleTracker.ts b/frontend/src/hooks/usePlausibleTracker.ts
index 563f2d0aea..5106048404 100644
--- a/frontend/src/hooks/usePlausibleTracker.ts
+++ b/frontend/src/hooks/usePlausibleTracker.ts
@@ -19,7 +19,8 @@ type CustomEvents =
| 'project_overview'
| 'suggest_tags'
| 'unknown_ui_error'
- | 'export_import';
+ | 'export_import'
+ | 'project_api_tokens';
export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext);
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 106462b64d..f203514d45 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -42,6 +42,7 @@ process.nextTick(async () => {
featuresExportImport: true,
newProjectOverview: true,
projectStatusApi: true,
+ showProjectApiAccess: true,
},
},
authentication: {