mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
feat: display new completed dialog (#8255)
1. Now the dialog will not close when SDK got connected 2. It will start to show the suggested production code. ( this will be attached in next PR) 3. Also, it has connected indicator on the right 4. Back button is removed in this stage.  --------- Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
parent
c7427f4b91
commit
aca0de414e
@ -7,22 +7,21 @@ import {
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { GenerateApiKey } from './GenerateApiKey';
|
||||
import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SelectSdk } from './SelectSdk';
|
||||
import { GenerateApiKeyConcepts, SelectSdkConcepts } from './UnleashConcepts';
|
||||
|
||||
const TestSdkConnection = lazy(() => import('./TestSdkConnection'));
|
||||
|
||||
import type { Sdk } from './sharedTypes';
|
||||
import { ConnectionInformation } from './ConnectionInformation';
|
||||
import Loader from 'component/common/Loader/Loader';
|
||||
import { SdkConnection } from './SdkConnection';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
|
||||
interface IConnectSDKDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
project: string;
|
||||
environments: string[];
|
||||
feature: string;
|
||||
feature?: string;
|
||||
}
|
||||
|
||||
const ConnectSdk = styled('main')(({ theme }) => ({
|
||||
@ -68,7 +67,7 @@ export const ConnectSdkDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
environments,
|
||||
project,
|
||||
project: projectId,
|
||||
feature,
|
||||
}: IConnectSDKDialogProps) => {
|
||||
const theme = useTheme();
|
||||
@ -77,6 +76,9 @@ export const ConnectSdkDialog = ({
|
||||
const [environment, setEnvironment] = useState<string | null>(null);
|
||||
const [apiKey, setApiKey] = useState<string | null>(null);
|
||||
const [stage, setStage] = useState<OnboardingStage>('select-sdk');
|
||||
const { project } = useProjectOverview(projectId, {
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
const isSelectSdkStage = stage === 'select-sdk';
|
||||
const isGenerateApiKeyStage =
|
||||
@ -84,6 +86,8 @@ export const ConnectSdkDialog = ({
|
||||
const isTestConnectionStage =
|
||||
stage === 'test-connection' && sdk && environment && apiKey;
|
||||
|
||||
const onboarded = project.onboardingStatus.status === 'onboarded';
|
||||
|
||||
useEffect(() => {
|
||||
if (environments.length > 0) {
|
||||
setEnvironment(environments[0]);
|
||||
@ -106,23 +110,21 @@ export const ConnectSdkDialog = ({
|
||||
<GenerateApiKey
|
||||
environments={environments}
|
||||
environment={environment}
|
||||
project={project}
|
||||
project={projectId}
|
||||
sdkType={sdk.type}
|
||||
onEnvSelect={setEnvironment}
|
||||
onApiKey={setApiKey}
|
||||
/>
|
||||
) : null}
|
||||
{isTestConnectionStage ? (
|
||||
<Suspense fallback={<Loader />}>
|
||||
<TestSdkConnection
|
||||
sdk={sdk}
|
||||
apiKey={apiKey}
|
||||
feature={feature}
|
||||
onSdkChange={() => {
|
||||
setStage('select-sdk');
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
<SdkConnection
|
||||
apiKey={apiKey}
|
||||
sdk={sdk}
|
||||
feature={feature}
|
||||
onSdkChange={() => {
|
||||
setStage('select-sdk');
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{stage === 'generate-api-key' ? (
|
||||
@ -152,22 +154,25 @@ export const ConnectSdkDialog = ({
|
||||
{isTestConnectionStage ? (
|
||||
<Navigation>
|
||||
<NextStepSectionSpacedContainer>
|
||||
<Button
|
||||
variant='text'
|
||||
color='inherit'
|
||||
onClick={() => {
|
||||
setStage('generate-api-key');
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
{!onboarded ? (
|
||||
<Button
|
||||
variant='text'
|
||||
color='inherit'
|
||||
onClick={() => {
|
||||
setStage('generate-api-key');
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
variant='contained'
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Finish
|
||||
Next
|
||||
</Button>
|
||||
</NextStepSectionSpacedContainer>
|
||||
</Navigation>
|
||||
@ -182,10 +187,9 @@ export const ConnectSdkDialog = ({
|
||||
) : null}
|
||||
{isLargeScreen && isTestConnectionStage ? (
|
||||
<ConnectionInformation
|
||||
projectId={project}
|
||||
projectId={projectId}
|
||||
sdk={sdk.name}
|
||||
environment={environment}
|
||||
onConnection={onClose}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { styled, Typography, useTheme } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { WhitePulsingAvatar } from 'component/common/PulsingAvatar/PulsingAvatar';
|
||||
import Pending from '@mui/icons-material/Pending';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { useEffect } from 'react';
|
||||
import Check from '@mui/icons-material/Check';
|
||||
|
||||
interface IConnectionInformationProps {
|
||||
onConnection: () => void;
|
||||
projectId: string;
|
||||
sdk: string;
|
||||
environment: string;
|
||||
@ -50,8 +48,16 @@ export const ConnectionStatus = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
export const StyledCheck = styled(Check)(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: '50%',
|
||||
padding: theme.spacing(1),
|
||||
width: '80px',
|
||||
height: '80px',
|
||||
}));
|
||||
|
||||
export const ConnectionInformation = ({
|
||||
onConnection,
|
||||
projectId,
|
||||
sdk,
|
||||
environment,
|
||||
@ -63,12 +69,6 @@ export const ConnectionInformation = ({
|
||||
|
||||
const onboarded = project.onboardingStatus.status === 'onboarded';
|
||||
|
||||
useEffect(() => {
|
||||
if (onboarded) {
|
||||
onConnection();
|
||||
}
|
||||
}, [onboarded]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Title>Connection information</Title>
|
||||
@ -86,28 +86,38 @@ export const ConnectionInformation = ({
|
||||
<Typography variant='body2'>{sdk}</Typography>
|
||||
</Info>
|
||||
</SdkInfo>
|
||||
<ConnectionStatus>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
Connection status
|
||||
</Typography>
|
||||
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
|
||||
Waiting for SDK data...
|
||||
</Typography>
|
||||
<ConditionallyRender
|
||||
condition={true}
|
||||
show={
|
||||
<WhitePulsingAvatar
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
}}
|
||||
active={true}
|
||||
>
|
||||
<Pending fontSize='large' />
|
||||
</WhitePulsingAvatar>
|
||||
}
|
||||
/>
|
||||
</ConnectionStatus>
|
||||
{onboarded ? (
|
||||
<ConnectionStatus>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
Connection status
|
||||
</Typography>
|
||||
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
|
||||
Connected
|
||||
</Typography>
|
||||
<StyledCheck />
|
||||
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
|
||||
We received metrics from your application!
|
||||
</Typography>
|
||||
</ConnectionStatus>
|
||||
) : (
|
||||
<ConnectionStatus>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
Connection status
|
||||
</Typography>
|
||||
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
|
||||
Waiting for SDK data...
|
||||
</Typography>
|
||||
<WhitePulsingAvatar
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
}}
|
||||
active={true}
|
||||
>
|
||||
<Pending fontSize='large' />
|
||||
</WhitePulsingAvatar>
|
||||
</ConnectionStatus>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
150
frontend/src/component/onboarding/SdkConnected.tsx
Normal file
150
frontend/src/component/onboarding/SdkConnected.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
import type { FC } from 'react';
|
||||
import { Box, IconButton, styled, Tooltip, Typography } from '@mui/material';
|
||||
import { SectionHeader, StepperBox } from './SharedComponents';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import type { SdkName, Sdk } from './sharedTypes';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import useToast from 'hooks/useToast';
|
||||
import CopyIcon from '@mui/icons-material/FileCopy';
|
||||
import { Stepper } from './Stepper';
|
||||
import { Badge } from '../common/Badge/Badge';
|
||||
import { Markdown } from 'component/common/Markdown/Markdown';
|
||||
import type { CodeComponent } from 'react-markdown/lib/ast-to-react';
|
||||
import android from './snippets/android.md?raw';
|
||||
import go from './snippets/go.md?raw';
|
||||
import javascript from './snippets/javascript.md?raw';
|
||||
import nodejs from './snippets/nodejs.md?raw';
|
||||
import python from './snippets/python.md?raw';
|
||||
import ruby from './snippets/ruby.md?raw';
|
||||
import svelte from './snippets/svelte.md?raw';
|
||||
import vue from './snippets/vue.md?raw';
|
||||
import flutter from './snippets/flutter.md?raw';
|
||||
import java from './snippets/java.md?raw';
|
||||
import dotnet from './snippets/dotnet.md?raw';
|
||||
import php from './snippets/php.md?raw';
|
||||
import react from './snippets/react.md?raw';
|
||||
import rust from './snippets/rust.md?raw';
|
||||
import swift from './snippets/swift.md?raw';
|
||||
|
||||
const snippets: Record<SdkName, string> = {
|
||||
Android: android,
|
||||
Go: go,
|
||||
JavaScript: javascript,
|
||||
'Node.js': nodejs,
|
||||
Python: python,
|
||||
Ruby: ruby,
|
||||
Svelte: svelte,
|
||||
Vue: vue,
|
||||
Flutter: flutter,
|
||||
Java: java,
|
||||
'.NET': dotnet,
|
||||
PHP: php,
|
||||
React: react,
|
||||
Rust: rust,
|
||||
Swift: swift,
|
||||
};
|
||||
|
||||
const SpacedContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(5, 8, 2, 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',
|
||||
position: 'relative',
|
||||
maxHeight: theme.spacing(34),
|
||||
}));
|
||||
|
||||
const CopyToClipboard = styled(Tooltip)(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
top: theme.spacing(1),
|
||||
right: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const CopyBlock: FC<{ title: string; code: string }> = ({ title, code }) => {
|
||||
const onCopyToClipboard = (data: string) => () => {
|
||||
copy(data);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
const { setToastData } = useToast();
|
||||
|
||||
return (
|
||||
<StyledCodeBlock>
|
||||
{code}
|
||||
<CopyToClipboard title={title} arrow>
|
||||
<IconButton onClick={onCopyToClipboard(code)} size='small'>
|
||||
<CopyIcon />
|
||||
</IconButton>
|
||||
</CopyToClipboard>
|
||||
</StyledCodeBlock>
|
||||
);
|
||||
};
|
||||
|
||||
const ChangeSdk = styled('div')(({ theme }) => ({
|
||||
display: 'inline-flex',
|
||||
gap: theme.spacing(3),
|
||||
padding: theme.spacing(1, 2),
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
marginBottom: theme.spacing(3),
|
||||
}));
|
||||
|
||||
const CodeRenderer: CodeComponent = ({ inline = false, children }) => {
|
||||
if (!inline && typeof children?.[0] === 'string') {
|
||||
return <CopyBlock code={children[0]} title='Copy code' />;
|
||||
}
|
||||
|
||||
return <code>{children}</code>;
|
||||
};
|
||||
|
||||
interface ISdkConnectedProps {
|
||||
sdk: Sdk;
|
||||
}
|
||||
|
||||
export const SdkConnected: FC<ISdkConnectedProps> = ({ sdk }) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const clientApiUrl = `${uiConfig.unleashUrl}/api/`;
|
||||
const frontendApiUrl = `${uiConfig.unleashUrl}/api/frontend/`;
|
||||
const apiUrl = sdk.type === 'client' ? clientApiUrl : frontendApiUrl;
|
||||
|
||||
const snippet = (snippets[sdk.name] || '').replace(
|
||||
'<YOUR_API_URL>',
|
||||
apiUrl,
|
||||
);
|
||||
|
||||
return (
|
||||
<SpacedContainer>
|
||||
<Typography variant='h2'>Connect an SDK to Unleash</Typography>
|
||||
<StepperBox>
|
||||
<Stepper active={2} steps={3} />
|
||||
<Badge color='secondary'>3/3 - Test connection</Badge>
|
||||
</StepperBox>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<SectionHeader>Production settings</SectionHeader>
|
||||
<Typography variant='body2'>
|
||||
In order to validate the connection, we changed some
|
||||
settings that you might want to revert. We recommend the
|
||||
following default settings.
|
||||
</Typography>
|
||||
<Markdown components={{ code: CodeRenderer }}>
|
||||
{snippet}
|
||||
</Markdown>
|
||||
</Box>
|
||||
</SpacedContainer>
|
||||
);
|
||||
};
|
||||
|
||||
// Use a default export for lazy-loading
|
||||
export default SdkConnected;
|
34
frontend/src/component/onboarding/SdkConnection.tsx
Normal file
34
frontend/src/component/onboarding/SdkConnection.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Suspense } from 'react';
|
||||
import Loader from '../common/Loader/Loader';
|
||||
import TestSdkConnection from './TestSdkConnection';
|
||||
import type { Sdk } from './sharedTypes';
|
||||
import { SdkConnected } from './SdkConnected';
|
||||
|
||||
interface ISdkConnectionProps {
|
||||
sdk: Sdk;
|
||||
apiKey: string;
|
||||
feature?: string;
|
||||
onSdkChange: () => void;
|
||||
}
|
||||
|
||||
export const SdkConnection = ({
|
||||
sdk,
|
||||
apiKey,
|
||||
feature,
|
||||
onSdkChange,
|
||||
}: ISdkConnectionProps) => {
|
||||
return (
|
||||
<Suspense fallback={<Loader />}>
|
||||
{feature ? (
|
||||
<TestSdkConnection
|
||||
sdk={sdk}
|
||||
apiKey={apiKey}
|
||||
feature={feature}
|
||||
onSdkChange={onSdkChange}
|
||||
/>
|
||||
) : (
|
||||
<SdkConnected sdk={sdk} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
@ -534,17 +534,19 @@ export const ProjectFeatureToggles = ({
|
||||
</PageContent>
|
||||
}
|
||||
/>
|
||||
{'feature' in project.onboardingStatus ? (
|
||||
<ConnectSdkDialog
|
||||
open={connectSdkOpen}
|
||||
onClose={() => {
|
||||
setConnectSdkOpen(false);
|
||||
}}
|
||||
project={projectId}
|
||||
environments={environments}
|
||||
feature={project.onboardingStatus.feature}
|
||||
/>
|
||||
) : null}
|
||||
<ConnectSdkDialog
|
||||
open={connectSdkOpen}
|
||||
onClose={() => {
|
||||
setConnectSdkOpen(false);
|
||||
}}
|
||||
project={projectId}
|
||||
environments={environments}
|
||||
feature={
|
||||
'feature' in project.onboardingStatus
|
||||
? project.onboardingStatus.feature
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<BatchSelectionActionsBar count={selectedData.length}>
|
||||
<ProjectFeaturesBatchActions
|
||||
selectedIds={Object.keys(rowSelection)}
|
||||
|
Loading…
Reference in New Issue
Block a user