1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-03 01:18:43 +02:00
unleash.unleash/frontend/src/component/onboarding/GenerateApiKey.tsx
2024-09-18 14:08:31 +02:00

285 lines
9.8 KiB
TypeScript

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,
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 { useEffect } from 'react';
import { SectionHeader, StepperBox } from './SharedComponents';
import { Stepper } from './Stepper';
import { Badge } from '../common/Badge/Badge';
const ChooseEnvironment = ({
environments,
onSelect,
currentEnvironment,
}: {
environments: string[];
currentEnvironment: string;
onSelect: (env: string) => void;
}) => {
const longestEnv = Math.max(
...environments.map((environment) => environment.length),
);
return (
<SingleSelectConfigButton
tooltip={{ header: '' }}
description='Select the environment where API key will be created'
options={environments.map((environment) => ({
label: environment,
value: environment,
}))}
onChange={(value: any) => {
onSelect(value);
}}
button={{
label: currentEnvironment,
icon: <EnvironmentsIcon />,
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.background.paper,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2),
flex: 1,
color: theme.palette.text.secondary,
fontSize: theme.typography.body2.fontSize,
}));
const TokenExplanationBox = styled(Box)(({ theme }) => ({
display: 'flex',
gap: theme.spacing(2),
alignItems: 'flex-start',
marginTop: theme.spacing(8),
flexWrap: 'wrap',
}));
const SectionDescription = styled('p')(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.typography.body2.fontSize,
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 TokenExplanation = ({
project,
environment,
secret,
}: { project: string; environment: string; secret: string }) => {
const theme = useTheme();
const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
return (
<ArcherContainer
strokeColor={theme.palette.secondary.border}
endMarker={false}
lineStyle='curve'
>
<SecretExplanation>
<Box sx={{ wordBreak: 'break-all' }}>
<ArcherElement id='project'>
<span>{project}</span>
</ArcherElement>
:
<ArcherElement id='environment'>
<span>{environment}</span>
</ArcherElement>
.
<ArcherElement id='secret'>
<span>{secret}</span>
</ArcherElement>
</Box>
{isLargeScreen ? (
<TokenExplanationBox>
<ArcherElement
id='project-description'
relations={[
{
targetId: 'project',
targetAnchor: 'bottom',
sourceAnchor: 'top',
},
]}
>
<SecretExplanationDescription>
The project this API key will retrieve feature
flags from
</SecretExplanationDescription>
</ArcherElement>
<ArcherElement
id='environment-description'
relations={[
{
targetId: 'environment',
targetAnchor: 'bottom',
sourceAnchor: 'top',
},
]}
>
<SecretExplanationDescription>
The environment the API key will retrieve
feature flag configuration from
</SecretExplanationDescription>
</ArcherElement>
<ArcherElement
id='secret-description'
relations={[
{
targetId: 'secret',
targetAnchor: 'right',
sourceAnchor: 'top',
},
]}
>
<SecretExplanationDescription>
The API key secret
</SecretExplanationDescription>
</ArcherElement>
</TokenExplanationBox>
) : null}
</SecretExplanation>
</ArcherContainer>
);
};
interface GenerateApiKeyProps {
project: string;
environments: string[];
environment: string;
sdkType: 'client' | 'frontend';
onEnvSelect: (env: string) => void;
onApiKey: (apiKey: string | null) => void;
}
export const GenerateApiKey = ({
environments,
environment,
project,
sdkType,
onEnvSelect,
onApiKey,
}: GenerateApiKeyProps) => {
const { tokens, refetch: refreshTokens } = useProjectApiTokens(project);
const { createToken, loading: creatingToken } = useProjectApiTokensApi();
const currentEnvironmentToken = tokens.find(
(token) => token.environment === environment && token.type === sdkType,
);
useEffect(() => {
onApiKey(currentEnvironmentToken?.secret || null);
}, [currentEnvironmentToken]);
const parsedToken = parseToken(currentEnvironmentToken?.secret);
const { setToastApiError } = useToast();
const generateAPIKey = async () => {
try {
await createToken(
{
environment,
type: sdkType,
projects: [project],
username: `api-key-${project}-${environment}`,
},
project,
);
refreshTokens();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
return (
<SpacedContainer>
<Typography variant='h2'>Connect an SDK to Unleash</Typography>
<StepperBox>
<Stepper active={1} steps={3} />
<Badge color='secondary'>2/3 - Generate API Key</Badge>
</StepperBox>
<Box sx={{ mt: 2 }}>
<SectionHeader>Environment</SectionHeader>
<SectionDescription>
The environment SDK will connect to in order to retrieve
configuration.
</SectionDescription>
{environments.length > 0 ? (
<ChooseEnvironment
environments={environments}
currentEnvironment={environment}
onSelect={onEnvSelect}
/>
) : null}
</Box>
<Box sx={{ mt: 3 }}>
<SectionHeader>API Key</SectionHeader>
{parsedToken ? (
<SectionDescription>
Here is your generated API key. We will use it to
connect to the <b>{parsedToken.project}</b> project in
the <b>{parsedToken.environment}</b> environment.
</SectionDescription>
) : (
<SectionDescription>
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.
</SectionDescription>
)}
{parsedToken ? (
<TokenExplanation
project={parsedToken.project}
environment={parsedToken.environment}
secret={parsedToken.secret}
/>
) : (
<Button
variant='contained'
disabled={creatingToken}
onClick={generateAPIKey}
>
Generate API Key
</Button>
)}
</Box>
</SpacedContainer>
);
};