diff --git a/frontend/src/component/onboarding/ConnectSDKDialog.tsx b/frontend/src/component/onboarding/ConnectSDKDialog.tsx
index e5d597173a..2979804761 100644
--- a/frontend/src/component/onboarding/ConnectSDKDialog.tsx
+++ b/frontend/src/component/onboarding/ConnectSDKDialog.tsx
@@ -3,22 +3,10 @@ import {
Button,
Dialog,
styled,
- type Theme,
- Typography,
useMediaQuery,
useTheme,
} from '@mui/material';
-import { SingleSelectConfigButton } from '../common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton';
-import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
-import { useEffect, useState } from 'react';
-import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
-import CodeIcon from '@mui/icons-material/Code';
-import { useProjectApiTokens } from 'hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
-import { ArcherContainer, ArcherElement } from 'react-archer';
-import useProjectApiTokensApi from 'hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
-import { formatUnknownError } from 'utils/formatUnknownError';
-import useToast from 'hooks/useToast';
-import { parseToken } from './parseToken';
+import { GenrateApiKeyConcepts, GeneratApiKey } from './GenerateApiKey';
interface IConnectSDKDialogProps {
open: boolean;
@@ -27,57 +15,13 @@ interface IConnectSDKDialogProps {
environments: string[];
}
-const ConceptsDefinitionsWrapper = styled('div')(({ theme }) => ({
- backgroundColor: theme.palette.background.sidebar,
- padding: theme.spacing(6),
- flex: 0,
- minWidth: '400px',
-}));
-
-const IconStyle = ({ theme }: { theme: Theme }) => ({
- color: theme.palette.primary.contrastText,
- fontSize: theme.fontSizes.smallBody,
- marginTop: theme.spacing(0.5),
-});
-
-const StyledProjectIcon = styled(ProjectIcon)(IconStyle);
-const StyledEnvironmentsIcon = styled(EnvironmentsIcon)(IconStyle);
-const StyledCodeIcon = styled(CodeIcon)(IconStyle);
-
-const ConceptItem = styled('div')(({ theme }) => ({
- display: 'flex',
- gap: theme.spacing(1.5),
- alignItems: 'flex-start',
- marginTop: theme.spacing(3),
-}));
-
-const ConceptSummary = styled('div')(({ theme }) => ({
- color: theme.palette.primary.contrastText,
- fontSize: theme.fontSizes.smallBody,
- fontWeight: theme.fontWeight.bold,
- marginBottom: theme.spacing(2),
-}));
-
-const ConceptDetails = styled('p')(({ theme }) => ({
- color: theme.palette.primary.contrastText,
- fontSize: theme.fontSizes.smallerBody,
- marginBottom: theme.spacing(2),
-}));
-
-export const APIKeyGeneration = styled('div')(({ theme }) => ({
+const ConnectSdk = styled('main')(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
flex: 1,
}));
-export const SpacedContainer = styled('div')(({ theme }) => ({
- padding: theme.spacing(5, 8, 3, 8),
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(3),
-}));
-
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: theme.shape.borderRadiusLarge,
@@ -91,327 +35,52 @@ const StyledDialog = styled(Dialog)(({ theme }) => ({
},
}));
-const SectionHeader = styled('div')(({ theme }) => ({
- fontWeight: theme.fontWeight.bold,
- marginBottom: theme.spacing(1),
- fontSize: theme.fontSizes.bodySize,
-}));
-
-const SectionDescription = styled('p')(({ theme }) => ({
- color: theme.palette.text.secondary,
- fontSize: theme.fontSizes.smallBody,
- marginBottom: theme.spacing(2),
-}));
-
-const NextStepSection = styled('div')(({ theme }) => ({
+const Navigation = styled('div')(({ theme }) => ({
marginTop: 'auto',
borderTop: `1px solid ${theme.palette.divider}}`,
-}));
-
-const NextStepSectionSpacedContainer = styled('div')(({ theme }) => ({
display: 'flex',
- justifyContent: 'space-between',
+ justifyContent: 'flex-end',
+ gap: theme.spacing(4),
alignItems: 'center',
padding: theme.spacing(3, 8, 3, 8),
}));
-const SecretExplanation = styled('div')(({ theme }) => ({
- backgroundColor: theme.palette.background.elevation1,
- borderRadius: theme.shape.borderRadius,
- padding: theme.spacing(3),
+const NextStepSectionSpacedContainer = styled('div')(({ theme }) => ({
display: 'flex',
- flexDirection: 'column',
+ justifyContent: 'flex-end',
+ gap: theme.spacing(4),
alignItems: 'center',
+ padding: theme.spacing(3, 8, 3, 8),
}));
-const SecretExplanationDescription = styled('div')(({ theme }) => ({
- backgroundColor: theme.palette.primary.contrastText,
- borderRadius: theme.shape.borderRadius,
- padding: theme.spacing(2),
- flex: 1,
- color: theme.palette.text.secondary,
- fontSize: theme.fontSizes.smallBody,
-}));
-
-const ConceptsDefinitions = () => (
-
-
-
-
- Flags live in projects
-
- Projects are containers for feature flags. When you create a
- feature flag it will belong to the project you create it in.
-
-
-
-
-
-
-
- Flags have configuration in environments
-
-
- In Unleash you can have multiple environments. Each feature
- flag will have different configuration in every environment.
-
-
-
-
-
-
-
- SDKs connect to Unleash to retrieve configuration
-
-
- When you connect an SDK to Unleash it will use the API key
- to deduce which feature flags to retrieve and from which
- environment to retrieve configuration.
-
-
-
-
-);
-
-const TokenExplanationBox = styled(Box)(({ theme }) => ({
- display: 'flex',
- gap: theme.spacing(2),
- alignItems: 'flex-start',
- marginTop: theme.spacing(8),
- flexWrap: 'wrap',
-}));
-
-const TokenExplanation = ({
- project,
- environment,
- secret,
-}: { project: string; environment: string; secret: string }) => {
- const theme = useTheme();
- const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
-
- return (
-
-
-
-
- {project}
-
- :
-
- {environment}
-
- .
-
- {secret}
-
-
-
- {isLargeScreen ? (
-
-
-
- The project this API key will retrieve feature
- flags from
-
-
-
-
- The environment the API key will retrieve
- feature flag configuration from
-
-
-
-
- The API key secret
-
-
-
- ) : null}
-
-
- );
-};
-
-const ChooseEnvironment = ({
- environments,
- onSelect,
- currentEnvironment,
-}: {
- environments: string[];
- currentEnvironment: string;
- onSelect: (env: string) => void;
-}) => {
- const longestEnv = Math.max(
- ...environments.map((environment) => environment.length),
- );
-
- return (
- ({
- label: environment,
- value: environment,
- }))}
- onChange={(value: any) => {
- onSelect(value);
- }}
- button={{
- label: currentEnvironment,
- icon: ,
- labelWidth: `${longestEnv + 5}ch`,
- }}
- search={{
- label: 'Filter project mode options',
- placeholder: 'Select project mode',
- }}
- />
- );
-};
-
export const ConnectSDKDialog = ({
open,
onClose,
environments,
project,
}: IConnectSDKDialogProps) => {
- const [environment, setEnvironment] = useState('');
-
- useEffect(() => {
- if (environments.length > 0) {
- setEnvironment(environments[0]);
- }
- }, [JSON.stringify(environments)]);
-
- const { tokens, refetch: refreshTokens } = useProjectApiTokens(project);
- const { createToken, loading: creatingToken } = useProjectApiTokensApi();
- const currentEnvironmentToken = tokens.find(
- (token) => token.environment === environment,
- );
- const parsedToken = parseToken(currentEnvironmentToken?.secret);
-
const theme = useTheme();
const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
- const { setToastApiError } = useToast();
-
- const generateAPIKey = async () => {
- try {
- await createToken(
- {
- environment,
- type: 'CLIENT',
- projects: [project],
- username: 'onboarding-api-key',
- },
- project,
- );
- refreshTokens();
- } catch (error: unknown) {
- setToastApiError(formatUnknownError(error));
- }
- };
return (
-
-
-
- Connect an SDK to Unleash
-
-
- Environment
-
- The environment SDK will connect to in order to
- retrieve configuration.
-
- {environments.length > 0 ? (
-
- ) : null}
-
-
-
- API Key
- {parsedToken ? (
-
- Here is your generated API key. We will use
- it to connect to the{' '}
- {parsedToken.project} project in the{' '}
- {parsedToken.environment}{' '}
- environment.
-
- ) : (
-
- You currently have no active API keys for
- this project/environment combination. You'll
- need to generate and API key in order to
- proceed with connecting your SDK.
-
- )}
- {parsedToken ? (
-
- ) : (
-
- )}
-
-
-
-
+
+
+
-
- Next: Choose SDK and connect
-
+
-
-
+
+
- {isLargeScreen ? : null}
+ {isLargeScreen ? : null}
);
diff --git a/frontend/src/component/onboarding/GenerateApiKey.tsx b/frontend/src/component/onboarding/GenerateApiKey.tsx
new file mode 100644
index 0000000000..cc7b1e1330
--- /dev/null
+++ b/frontend/src/component/onboarding/GenerateApiKey.tsx
@@ -0,0 +1,358 @@
+import { useEffect, useState } from 'react';
+import { useProjectApiTokens } from '../../hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
+import useProjectApiTokensApi from '../../hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
+import { parseToken } from './parseToken';
+import useToast from '../../hooks/useToast';
+import { formatUnknownError } from '../../utils/formatUnknownError';
+import {
+ Box,
+ Button,
+ styled,
+ type Theme,
+ Typography,
+ useMediaQuery,
+ useTheme,
+} from '@mui/material';
+import { SingleSelectConfigButton } from '../common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton';
+import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
+import { ArcherContainer, ArcherElement } from 'react-archer';
+import { ProjectIcon } from '../common/ProjectIcon/ProjectIcon';
+import CodeIcon from '@mui/icons-material/Code';
+
+const ChooseEnvironment = ({
+ environments,
+ onSelect,
+ currentEnvironment,
+}: {
+ environments: string[];
+ currentEnvironment: string;
+ onSelect: (env: string) => void;
+}) => {
+ const longestEnv = Math.max(
+ ...environments.map((environment) => environment.length),
+ );
+
+ return (
+ ({
+ label: environment,
+ value: environment,
+ }))}
+ onChange={(value: any) => {
+ onSelect(value);
+ }}
+ button={{
+ label: currentEnvironment,
+ icon: ,
+ labelWidth: `${longestEnv + 5}ch`,
+ }}
+ search={{
+ label: 'Filter project mode options',
+ placeholder: 'Select project mode',
+ }}
+ />
+ );
+};
+
+const SecretExplanation = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.background.elevation1,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(3),
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+}));
+
+const SecretExplanationDescription = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.primary.contrastText,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(2),
+ flex: 1,
+ color: theme.palette.text.secondary,
+ fontSize: theme.fontSizes.smallBody,
+}));
+
+const TokenExplanationBox = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ gap: theme.spacing(2),
+ alignItems: 'flex-start',
+ marginTop: theme.spacing(8),
+ flexWrap: 'wrap',
+}));
+
+const SectionHeader = styled('div')(({ theme }) => ({
+ fontWeight: theme.fontWeight.bold,
+ marginBottom: theme.spacing(1),
+ fontSize: theme.fontSizes.bodySize,
+}));
+
+const SectionDescription = styled('p')(({ theme }) => ({
+ color: theme.palette.text.secondary,
+ fontSize: theme.fontSizes.smallBody,
+ marginBottom: theme.spacing(2),
+}));
+
+const SpacedContainer = styled('div')(({ theme }) => ({
+ padding: theme.spacing(5, 8, 3, 8),
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(3),
+}));
+
+const ConceptsDefinitionsWrapper = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.background.sidebar,
+ padding: theme.spacing(6),
+ flex: 0,
+ minWidth: '400px',
+}));
+
+const ConceptDetails = styled('p')(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ fontSize: theme.fontSizes.smallerBody,
+ marginBottom: theme.spacing(2),
+}));
+
+const IconStyle = ({ theme }: { theme: Theme }) => ({
+ color: theme.palette.primary.contrastText,
+ fontSize: theme.fontSizes.smallBody,
+ marginTop: theme.spacing(0.5),
+});
+
+const StyledProjectIcon = styled(ProjectIcon)(IconStyle);
+const StyledEnvironmentsIcon = styled(EnvironmentsIcon)(IconStyle);
+const StyledCodeIcon = styled(CodeIcon)(IconStyle);
+
+const ConceptItem = styled('div')(({ theme }) => ({
+ display: 'flex',
+ gap: theme.spacing(1.5),
+ alignItems: 'flex-start',
+ marginTop: theme.spacing(3),
+}));
+
+const ConceptSummary = styled('div')(({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ fontSize: theme.fontSizes.smallBody,
+ fontWeight: theme.fontWeight.bold,
+ marginBottom: theme.spacing(2),
+}));
+
+const TokenExplanation = ({
+ project,
+ environment,
+ secret,
+}: { project: string; environment: string; secret: string }) => {
+ const theme = useTheme();
+ const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
+
+ return (
+
+
+
+
+ {project}
+
+ :
+
+ {environment}
+
+ .
+
+ {secret}
+
+
+
+ {isLargeScreen ? (
+
+
+
+ The project this API key will retrieve feature
+ flags from
+
+
+
+
+ The environment the API key will retrieve
+ feature flag configuration from
+
+
+
+
+ The API key secret
+
+
+
+ ) : null}
+
+
+ );
+};
+
+export const GenrateApiKeyConcepts = () => (
+
+
+
+
+ Flags live in projects
+
+ Projects are containers for feature flags. When you create a
+ feature flag it will belong to the project you create it in.
+
+
+
+
+
+
+
+ Flags have configuration in environments
+
+
+ In Unleash you can have multiple environments. Each feature
+ flag will have different configuration in every environment.
+
+
+
+
+
+
+
+ SDKs connect to Unleash to retrieve configuration
+
+
+ When you connect an SDK to Unleash it will use the API key
+ to deduce which feature flags to retrieve and from which
+ environment to retrieve configuration.
+
+
+
+
+);
+
+interface GeneratApiKeyProps {
+ project: string;
+ environments: string[];
+}
+
+export const GeneratApiKey = ({
+ environments,
+ project,
+}: GeneratApiKeyProps) => {
+ const [environment, setEnvironment] = useState('');
+
+ useEffect(() => {
+ if (environments.length > 0) {
+ setEnvironment(environments[0]);
+ }
+ }, [JSON.stringify(environments)]);
+
+ const { tokens, refetch: refreshTokens } = useProjectApiTokens(project);
+ const { createToken, loading: creatingToken } = useProjectApiTokensApi();
+ const currentEnvironmentToken = tokens.find(
+ (token) => token.environment === environment,
+ );
+ const parsedToken = parseToken(currentEnvironmentToken?.secret);
+
+ const { setToastApiError } = useToast();
+
+ const generateAPIKey = async () => {
+ try {
+ await createToken(
+ {
+ environment,
+ type: 'CLIENT',
+ projects: [project],
+ username: `api-key-${project}-${environment}`,
+ },
+ project,
+ );
+ refreshTokens();
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ };
+
+ return (
+
+ Connect an SDK to Unleash
+
+ Environment
+
+ The environment SDK will connect to in order to retrieve
+ configuration.
+
+ {environments.length > 0 ? (
+
+ ) : null}
+
+
+
+ API Key
+ {parsedToken ? (
+
+ Here is your generated API key. We will use it to
+ connect to the {parsedToken.project} project in
+ the {parsedToken.environment} environment.
+
+ ) : (
+
+ You currently have no active API keys for this
+ project/environment combination. You'll need to generate
+ and API key in order to proceed with connecting your
+ SDK.
+
+ )}
+ {parsedToken ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};