diff --git a/frontend/src/component/onboarding/ConnectSdkDialog.tsx b/frontend/src/component/onboarding/ConnectSdkDialog.tsx
index d22822edbf65..d57ade6879d4 100644
--- a/frontend/src/component/onboarding/ConnectSdkDialog.tsx
+++ b/frontend/src/component/onboarding/ConnectSdkDialog.tsx
@@ -8,8 +8,15 @@ import {
} from '@mui/material';
import { GenerateApiKey } from './GenerateApiKey';
import { useEffect, useState } from 'react';
-import { type Sdk, SelectSdk } from './SelectSdk';
-import { GenrateApiKeyConcepts, SelectSdkConcepts } from './UnleashConcepts';
+import { SelectSdk } from './SelectSdk';
+import {
+ ConceptsDefinitionsWrapper,
+ GenrateApiKeyConcepts,
+ SelectSdkConcepts,
+} from './UnleashConcepts';
+import { TestSdkConnection } from './TestSdkConnection';
+
+import type { Sdk } from './sharedTypes';
interface IConnectSDKDialogProps {
open: boolean;
@@ -107,7 +114,9 @@ export const ConnectSdkDialog = ({
}}
/>
) : null}
- {isTestConnectionStage ?
Last stage
: null}
+ {isTestConnectionStage ? (
+
+ ) : null}
{stage === 'generate-api-key' ? (
@@ -163,6 +172,9 @@ export const ConnectSdkDialog = ({
{isLargeScreen && isGenerateApiKeyStage ? (
) : null}
+ {isLargeScreen && isTestConnectionStage ? (
+
+ ) : null}
);
diff --git a/frontend/src/component/onboarding/GenerateApiKey.tsx b/frontend/src/component/onboarding/GenerateApiKey.tsx
index f11c87fdb2d6..8fdf0db3f677 100644
--- a/frontend/src/component/onboarding/GenerateApiKey.tsx
+++ b/frontend/src/component/onboarding/GenerateApiKey.tsx
@@ -1,8 +1,8 @@
-import { useProjectApiTokens } from '../../hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
-import useProjectApiTokensApi from '../../hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
+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 useToast from 'hooks/useToast';
+import { formatUnknownError } from 'utils/formatUnknownError';
import {
Box,
Button,
@@ -15,6 +15,7 @@ import { SingleSelectConfigButton } from '../common/DialogFormTemplate/ConfigBut
import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
import { ArcherContainer, ArcherElement } from 'react-archer';
import { useEffect } from 'react';
+import { SectionHeader } from './SharedComponents';
const ChooseEnvironment = ({
environments,
@@ -79,12 +80,6 @@ const TokenExplanationBox = styled(Box)(({ theme }) => ({
flexWrap: 'wrap',
}));
-const SectionHeader = styled('div')(({ theme }) => ({
- fontWeight: theme.typography.fontWeightBold,
- marginBottom: theme.spacing(1),
- fontSize: theme.typography.body1.fontSize,
-}));
-
const SectionDescription = styled('p')(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.typography.body2.fontSize,
diff --git a/frontend/src/component/onboarding/SelectSdk.tsx b/frontend/src/component/onboarding/SelectSdk.tsx
index 84b77cf5517e..00354899d4d8 100644
--- a/frontend/src/component/onboarding/SelectSdk.tsx
+++ b/frontend/src/component/onboarding/SelectSdk.tsx
@@ -16,6 +16,8 @@ import rust from 'assets/icons/sdks/Logo-rust.svg';
import svelte from 'assets/icons/sdks/Logo-svelte.svg';
import vue from 'assets/icons/sdks/Logo-vue.svg';
import { formatAssetPath } from 'utils/formatPath';
+import { SectionHeader } from './SharedComponents';
+import type { ClientSdkName, Sdk, ServerSdkName } from './sharedTypes';
const SpacedContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(5, 8, 8, 8),
@@ -24,12 +26,6 @@ const SpacedContainer = styled('div')(({ theme }) => ({
gap: theme.spacing(3),
}));
-const PrimarySectionHeader = styled('div')(({ theme }) => ({
- fontWeight: theme.typography.fontWeightBold,
- marginBottom: theme.spacing(1),
- fontSize: theme.typography.body1.fontSize,
-}));
-
const SecondarySectionHeader = styled('div')(({ theme }) => ({
marginTop: theme.spacing(4),
marginBottom: theme.spacing(2),
@@ -71,7 +67,7 @@ const StyledAvatar = styled(Avatar)(({ theme }) => ({
boxShadow: theme.shadows[2],
}));
-const serverSdks = [
+const serverSdks: { name: ServerSdkName; icon: string }[] = [
{ name: 'Node', icon: node },
{ name: 'Golang', icon: go },
{ name: 'Ruby', icon: ruby },
@@ -82,7 +78,7 @@ const serverSdks = [
{ name: 'Python', icon: python },
];
-const clientSdks = [
+const clientSdks: { name: ClientSdkName; icon: string }[] = [
{ name: 'Javascript', icon: javascript },
{ name: 'React', icon: react },
{ name: 'Vue', icon: vue },
@@ -92,8 +88,6 @@ const clientSdks = [
{ name: 'Flutter', icon: flutter },
];
-type SdkType = 'client' | 'frontend';
-export type Sdk = { name: string; type: SdkType };
interface ISelectSdkProps {
onSelect: (sdk: Sdk) => void;
}
@@ -102,7 +96,7 @@ export const SelectSdk: FC = ({ onSelect }) => {
Connect an SDK to Unleash
- Select SDK
+ Select SDK
Server side SDKs
@@ -155,5 +149,3 @@ export const SelectSdk: FC = ({ onSelect }) => {
);
};
-
-export const SelectSdkConcepts = () => {};
diff --git a/frontend/src/component/onboarding/SharedComponents.tsx b/frontend/src/component/onboarding/SharedComponents.tsx
new file mode 100644
index 000000000000..181f8ebf6355
--- /dev/null
+++ b/frontend/src/component/onboarding/SharedComponents.tsx
@@ -0,0 +1,7 @@
+import { styled } from '@mui/material';
+
+export const SectionHeader = styled('div')(({ theme }) => ({
+ fontWeight: theme.typography.fontWeightBold,
+ marginBottom: theme.spacing(1),
+ fontSize: theme.typography.body1.fontSize,
+}));
diff --git a/frontend/src/component/onboarding/TestSdkConnection.tsx b/frontend/src/component/onboarding/TestSdkConnection.tsx
new file mode 100644
index 000000000000..3e3f6d00f8d4
--- /dev/null
+++ b/frontend/src/component/onboarding/TestSdkConnection.tsx
@@ -0,0 +1,60 @@
+import type { FC } from 'react';
+import { Box, styled, Typography } from '@mui/material';
+import { SectionHeader } from './SharedComponents';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import type { Sdk } from './sharedTypes';
+import { codeSnippets, installCommands } from './sdkSnippets';
+
+const SpacedContainer = styled('div')(({ theme }) => ({
+ padding: theme.spacing(5, 8, 8, 8),
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(3),
+}));
+
+const StyledCodeBlock = styled('pre')(({ theme }) => ({
+ backgroundColor: theme.palette.background.elevation1,
+ padding: theme.spacing(2),
+ borderRadius: theme.shape.borderRadius,
+ overflow: 'auto',
+ fontSize: theme.typography.body2.fontSize,
+ wordBreak: 'break-all',
+ whiteSpace: 'pre-wrap',
+}));
+
+interface ITestSdkConnectionProps {
+ sdk: Sdk;
+ apiKey: string;
+}
+export const TestSdkConnection: FC = ({
+ sdk,
+ apiKey,
+}) => {
+ const { uiConfig } = useUiConfig();
+
+ const clientApiUrl = `${uiConfig.unleashUrl}/api/`;
+ const frontendApiUrl = `${uiConfig.unleashUrl}/api/frontend/`;
+ const apiUrl = sdk.type === 'client' ? clientApiUrl : frontendApiUrl;
+ const codeSnippet =
+ codeSnippets[sdk.name] || `No snippet found for the ${sdk.name} SDK`;
+ const installCommand =
+ installCommands[sdk.name] ||
+ `No install command found for the ${sdk.name} SDK`;
+
+ return (
+
+ Connect an SDK to Unleash
+
+ Setup the SDK
+ 1. Install the SDK
+ {installCommand}
+ 2. Initialize the SDK
+
+ {codeSnippet
+ .replace('', apiKey)
+ .replace('', apiUrl)}
+
+
+
+ );
+};
diff --git a/frontend/src/component/onboarding/UnleashConcepts.tsx b/frontend/src/component/onboarding/UnleashConcepts.tsx
index c62e1ebe49f6..a91804b1ba6d 100644
--- a/frontend/src/component/onboarding/UnleashConcepts.tsx
+++ b/frontend/src/component/onboarding/UnleashConcepts.tsx
@@ -3,7 +3,7 @@ import { ProjectIcon } from '../common/ProjectIcon/ProjectIcon';
import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
import CodeIcon from '@mui/icons-material/Code';
-const ConceptsDefinitionsWrapper = styled('div')(({ theme }) => ({
+export const ConceptsDefinitionsWrapper = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.sidebar,
padding: theme.spacing(12, 6, 6, 6),
flex: 0,
diff --git a/frontend/src/component/onboarding/sdkSnippets.ts b/frontend/src/component/onboarding/sdkSnippets.ts
new file mode 100644
index 000000000000..46960bfd665c
--- /dev/null
+++ b/frontend/src/component/onboarding/sdkSnippets.ts
@@ -0,0 +1,183 @@
+import type { SdkName } from './sharedTypes';
+
+export const installCommands: Record = {
+ Node: ' npm install unleash-client',
+ Golang: 'go get github.com/Unleash/unleash-client-go/v3',
+ Ruby: 'gem install unleash',
+ PHP: 'composer require unleash/client',
+ Rust: 'cargo add unleash-client',
+ DotNet: `dotnet add package unleash.client
+// If you do not have a json library in your project:
+dotnet add package Newtonsoft.Json`,
+ Java: `
+ io.getunleash
+ unleash-client-java
+ Latest version here
+`,
+ Python: 'pip install UnleashClient',
+ Javascript: 'npm install unleash-proxy-client',
+ React: 'npm install @unleash/proxy-client-react unleash-proxy-client',
+ Vue: 'npm install @unleash/proxy-client-vue',
+ Svelte: 'npm install @unleash/proxy-client-svelte',
+ Swift: 'https://github.com/Unleash/unleash-proxy-client-swift',
+ Android:
+ 'implementation("io.getunleash:unleash-android:${unleash.sdk.version}")',
+ Flutter: 'flutter pub add unleash_proxy_client_flutter',
+};
+
+export const codeSnippets: Record = {
+ Node: `import { initialize } from 'unleash-client';
+
+const unleash = initialize({
+ url: '',
+ appName: 'unleash-onboarding-node',
+ customHeaders: { Authorization: '' },
+});
+`,
+ Golang: `import (
+ "github.com/Unleash/unleash-client-go/v3"
+)
+
+func init() {
+ unleash.Initialize(
+ unleash.WithListener(&unleash.DebugListener{}),
+ unleash.WithAppName("unleash-onboarding-golang"),
+ unleash.WithUrl(""),
+ unleash.WithCustomHeaders(http.Header{"Authorization": {""}}),
+ )
+}`,
+ Ruby: `Unleash.configure do |config|
+ config.app_name = 'unleash-onboarding-ruby'
+ config.url = ''
+ config.custom_http_headers = {'Authorization': ''}
+end`,
+ PHP: `withAppName('unleash-onboarding-php')
+ ->withAppUrl('')
+ ->withHeader('Authorization', '')
+ ->withInstanceId('unleash-onboarding-instance')
+ ->build();`,
+ Rust: `let client = client::ClientBuilder::default()
+ .interval(500)
+ .into_client::(
+ "",
+ "unleash-onboarding-rust",
+ "unleash-onboarding-instance",
+ "",
+ )?;
+client.register().await?;`,
+ DotNet: `using Unleash;
+var settings = new UnleashSettings()
+{
+ AppName = "unleash-onboarding-dotnet",
+ UnleashApi = new Uri(""),
+ CustomHttpHeaders = new Dictionary()
+ {
+ {"Authorization","" }
+ }
+};`,
+ Java: `UnleashConfig config = UnleashConfig.builder()
+ .appName("unleash-onboarding-java")
+ .instanceId("unleash-onboarding-instance")
+ .unleashAPI("")
+ .apiKey("")
+ .build();
+
+Unleash unleash = new DefaultUnleash(config);`,
+ Python: `from UnleashClient import UnleashClient
+
+client = UnleashClient(
+ url="",
+ app_name="unleash-onboarding-python",
+ custom_headers={'Authorization': '"'})
+
+client.initialize_client()`,
+ Javascript: `import { UnleashClient } from 'unleash-proxy-client';
+
+const unleash = new UnleashClient({
+ url: '',
+ clientKey: '',
+ appName: 'unleash-onboarding-javascript',
+});
+
+// Start the background polling
+unleash.start();`,
+ React: `import { createRoot } from 'react-dom/client';
+import { FlagProvider } from '@unleash/proxy-client-react';
+
+const config = {
+ url: '',
+ clientKey: '',
+ refreshInterval: 15,
+ appName: 'unleash-onboarding-react',
+};
+
+const root = createRoot(document.getElementById('root'));
+
+root.render(
+
+
+
+
+
+);`,
+ Vue: `import { createApp } from 'vue'
+import { plugin as unleashPlugin } from '@unleash/proxy-client-vue'
+// import the root component App from a single-file component.
+import App from './App.vue'
+
+const config = {
+ url: ''',
+ clientKey: '',
+ refreshInterval: 15,
+ appName: 'unleash-onboarding-vue',
+}
+
+const app = createApp(App)
+app.use(unleashPlugin, { config })
+app.mount('#app')`,
+ Svelte: `
+
+
+
+`,
+ Swift: `import SwiftUI
+import UnleashProxyClientSwift
+
+var unleash = UnleashProxyClientSwift.UnleashClient(
+ unleashUrl: "",
+ clientKey: "",
+ refreshInterval: 15,
+ appName: "unleash-onboarding-swift",
+ context: [:])
+
+unleash.start()`,
+ Android: `val unleash = DefaultUnleash(
+ androidContext = applicationContext, // likely a reference to your Android application context
+ unleashConfig = UnleashConfig.newBuilder(appName = "unleash-onboarding-android")
+ .proxyUrl("")
+ .clientKey("")
+ .pollingStrategy.interval(3000)
+ .metricsStrategy.interval(3000)
+ .build()
+)`,
+ Flutter: `import 'package:unleash_proxy_client_flutter/unleash_proxy_client_flutter.dart';
+
+final unleash = UnleashClient(
+ url: Uri.parse(''),
+ clientKey: '',
+ appName: 'unleash-onboarding-flutter');`,
+};
diff --git a/frontend/src/component/onboarding/sharedTypes.ts b/frontend/src/component/onboarding/sharedTypes.ts
new file mode 100644
index 000000000000..c1c5117bed68
--- /dev/null
+++ b/frontend/src/component/onboarding/sharedTypes.ts
@@ -0,0 +1,20 @@
+export type SdkType = 'client' | 'frontend';
+export type Sdk = { name: SdkName; type: SdkType };
+export type ServerSdkName =
+ | 'Node'
+ | 'Golang'
+ | 'Ruby'
+ | 'PHP'
+ | 'Rust'
+ | 'DotNet'
+ | 'Java'
+ | 'Python';
+export type ClientSdkName =
+ | 'Javascript'
+ | 'React'
+ | 'Vue'
+ | 'Svelte'
+ | 'Swift'
+ | 'Android'
+ | 'Flutter';
+export type SdkName = ServerSdkName | ClientSdkName;