mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
feat: onboarding can be now closed (#8215)
The boxes were merged and now the whole thing can be closed. 
This commit is contained in:
parent
f45b7a07b4
commit
9f5e909436
@ -33,17 +33,18 @@ import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
|
||||
import { TableEmptyState } from './TableEmptyState/TableEmptyState';
|
||||
import { useRowActions } from './hooks/useRowActions';
|
||||
import { useSelectedData } from './hooks/useSelectedData';
|
||||
import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
|
||||
import { FeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
|
||||
import {
|
||||
useProjectFeatureSearch,
|
||||
useProjectFeatureSearchActions,
|
||||
} from './useProjectFeatureSearch';
|
||||
import { AvatarCell } from './AvatarCell';
|
||||
import { ProjectOnboarding } from './ProjectOnboarding/ProjectOnboarding';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { styled } from '@mui/material';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { ConnectSdkDialog } from '../../../onboarding/ConnectSdkDialog';
|
||||
import { ProjectOnboarding } from './ProjectOnboarding/ProjectOnboarding';
|
||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||
|
||||
interface IPaginatedProjectFeatureTogglesProps {
|
||||
environments: string[];
|
||||
@ -114,12 +115,19 @@ export const ProjectFeatureToggles = ({
|
||||
|
||||
const isPlaceholder = Boolean(initialLoad || (loading && total));
|
||||
|
||||
const [onboardingFlow, setOnboardingFlow] = useLocalStorageState<
|
||||
'visible' | 'closed'
|
||||
>(`onboarding-flow:v1-${projectId}`, 'visible');
|
||||
|
||||
const notOnboarding =
|
||||
!onboardingUIEnabled ||
|
||||
(onboardingUIEnabled &&
|
||||
project.onboardingStatus.status === 'onboarded');
|
||||
project.onboardingStatus.status === 'onboarded') ||
|
||||
onboardingFlow === 'closed';
|
||||
const isOnboarding =
|
||||
onboardingUIEnabled && project.onboardingStatus.status !== 'onboarded';
|
||||
onboardingUIEnabled &&
|
||||
project.onboardingStatus.status !== 'onboarded' &&
|
||||
onboardingFlow === 'visible';
|
||||
const showFeaturesTable =
|
||||
(total !== undefined && total > 0) || notOnboarding;
|
||||
|
||||
@ -413,6 +421,7 @@ export const ProjectFeatureToggles = ({
|
||||
<ProjectOnboarding
|
||||
projectId={projectId}
|
||||
setConnectSdkOpen={setConnectSdkOpen}
|
||||
setOnboardingFlow={setOnboardingFlow}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import { WelcomeToProject } from './WelcomeToProject';
|
||||
import { ProjectOnboarding } from './ProjectOnboarding';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
const server = testServerSetup();
|
||||
@ -18,9 +18,10 @@ test('Project can start onboarding', async () => {
|
||||
<Route
|
||||
path={'/projects/:projectId'}
|
||||
element={
|
||||
<WelcomeToProject
|
||||
<ProjectOnboarding
|
||||
projectId={projectId}
|
||||
setConnectSdkOpen={() => {}}
|
||||
setOnboardingFlow={() => {}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@ -45,9 +46,10 @@ test('Project can connect SDK', async () => {
|
||||
<Route
|
||||
path={'/projects/:projectId'}
|
||||
element={
|
||||
<WelcomeToProject
|
||||
<ProjectOnboarding
|
||||
projectId={projectId}
|
||||
setConnectSdkOpen={() => {}}
|
||||
setOnboardingFlow={() => {}}
|
||||
/>
|
||||
}
|
||||
/>
|
@ -1,29 +1,204 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { WelcomeToProject } from './WelcomeToProject';
|
||||
import { IconButton, styled, Tooltip, Typography } from '@mui/material';
|
||||
import Add from '@mui/icons-material/Add';
|
||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { FlagCreationButton } from '../ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
import { SdkExample } from './SdkExample';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
interface IProjectOnboardingProps {
|
||||
projectId: string;
|
||||
setConnectSdkOpen: (open: boolean) => void;
|
||||
setOnboardingFlow: (status: 'visible' | 'closed') => void;
|
||||
}
|
||||
|
||||
interface ICreateFlagProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
const Container = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
flexBasis: '70%',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
}));
|
||||
|
||||
const TitleBox = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 7, 2, 7),
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
minHeight: '80px',
|
||||
}));
|
||||
|
||||
const Actions = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
}));
|
||||
|
||||
const ActionBox = styled('div')(({ theme }) => ({
|
||||
flexBasis: '50%',
|
||||
padding: theme.spacing(3, 2, 6, 8),
|
||||
display: 'flex',
|
||||
gap: theme.spacing(3),
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
const TitleContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(2),
|
||||
alignItems: 'center',
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
|
||||
const NeutralCircleContainer = styled('span')(({ theme }) => ({
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.palette.neutral.border,
|
||||
borderRadius: '50%',
|
||||
}));
|
||||
|
||||
const MainCircleContainer = styled(NeutralCircleContainer)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.background.paper,
|
||||
}));
|
||||
|
||||
const ExistingFlagContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(3),
|
||||
height: '100%',
|
||||
}));
|
||||
|
||||
const SuccessContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: theme.palette.success.light,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
padding: theme.spacing(2, 2, 2, 2),
|
||||
}));
|
||||
|
||||
const TitleRow = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const ProjectOnboarding = ({
|
||||
projectId,
|
||||
setConnectSdkOpen,
|
||||
setOnboardingFlow,
|
||||
}: IProjectOnboardingProps) => {
|
||||
const { project } = useProjectOverview(projectId);
|
||||
const isFirstFlagCreated =
|
||||
project.onboardingStatus.status === 'first-flag-created';
|
||||
|
||||
const closeOnboardingFlow = () => {
|
||||
setOnboardingFlow('closed');
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<WelcomeToProject
|
||||
<TitleBox>
|
||||
<TitleRow>
|
||||
<Typography fontWeight='bold'>
|
||||
Welcome to your project
|
||||
</Typography>
|
||||
<Tooltip title='Close' arrow>
|
||||
<IconButton onClick={closeOnboardingFlow} size='small'>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TitleRow>
|
||||
<Typography variant='body2'>
|
||||
Complete the steps below to start working with this project
|
||||
</Typography>
|
||||
</TitleBox>
|
||||
<Actions>
|
||||
<ActionBox>
|
||||
{project.onboardingStatus.status ===
|
||||
'first-flag-created' ? (
|
||||
<ExistingFlag />
|
||||
) : (
|
||||
<CreateFlag projectId={projectId} />
|
||||
)}
|
||||
</ActionBox>
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
<NeutralCircleContainer>2</NeutralCircleContainer>
|
||||
Connect an SDK
|
||||
</TitleContainer>
|
||||
<Typography>
|
||||
Your project is not yet connected to any SDK. To start
|
||||
using your feature flag, connect an SDK to the project.
|
||||
</Typography>
|
||||
<ResponsiveButton
|
||||
onClick={() => {
|
||||
setConnectSdkOpen(true);
|
||||
}}
|
||||
maxWidth='200px'
|
||||
projectId={projectId}
|
||||
setConnectSdkOpen={setConnectSdkOpen}
|
||||
/>
|
||||
Icon={Add}
|
||||
disabled={!isFirstFlagCreated}
|
||||
permission={CREATE_FEATURE}
|
||||
>
|
||||
Connect SDK
|
||||
</ResponsiveButton>
|
||||
</ActionBox>
|
||||
<ActionBox>
|
||||
<SdkExample />
|
||||
</ActionBox>
|
||||
</Actions>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateFlag = ({ projectId }: ICreateFlagProps) => {
|
||||
const { refetch } = useProjectOverview(projectId);
|
||||
return (
|
||||
<>
|
||||
<TitleContainer>
|
||||
<NeutralCircleContainer>1</NeutralCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
<Typography>
|
||||
<div>The project currently holds no feature flags.</div>
|
||||
<div>Create one to get started.</div>
|
||||
</Typography>
|
||||
<FlagCreationButton
|
||||
text='Create flag'
|
||||
skipNavigationOnComplete={true}
|
||||
onSuccess={refetch}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ExistingFlag = () => {
|
||||
return (
|
||||
<ExistingFlagContainer>
|
||||
<TitleContainer>
|
||||
<MainCircleContainer>✓</MainCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
<SuccessContainer>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
Congratulations! You have created your first flag
|
||||
</Typography>
|
||||
<Typography variant='body2'>
|
||||
Click into the flag below to customize the flag further
|
||||
</Typography>
|
||||
</SuccessContainer>
|
||||
</ExistingFlagContainer>
|
||||
);
|
||||
};
|
||||
|
@ -4,28 +4,13 @@ import Select from 'component/common/select';
|
||||
import { useState } from 'react';
|
||||
import { allSdks } from '../../../../onboarding/sharedTypes';
|
||||
|
||||
const Container = styled('div')(({ theme }) => ({
|
||||
const TitleContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
flexBasis: '30%',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
}));
|
||||
|
||||
const TitleBox = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 7, 2, 7),
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
minHeight: '80px',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(2),
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
}));
|
||||
|
||||
const ContentBox = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(3, 2, 6, 8),
|
||||
display: 'flex',
|
||||
gap: theme.spacing(3),
|
||||
flexDirection: 'column',
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
|
||||
const StyledLink = styled(Link)({
|
||||
@ -45,14 +30,10 @@ export const SdkExample = () => {
|
||||
setSelectedSdk(event.target.value);
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
<TitleBox>
|
||||
<Typography fontWeight='bold'>View SDK Example</Typography>
|
||||
</TitleBox>
|
||||
|
||||
<ContentBox>
|
||||
<>
|
||||
<TitleContainer>View SDK Example</TitleContainer>
|
||||
<Typography>
|
||||
See an example implementation of your preferred SDK.
|
||||
Choose your preferred SDK to view an example
|
||||
</Typography>
|
||||
<Select
|
||||
id='sdk-select'
|
||||
@ -65,7 +46,6 @@ export const SdkExample = () => {
|
||||
}}
|
||||
/>
|
||||
<StyledLink to={``}>Go to example</StyledLink>
|
||||
</ContentBox>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,179 +0,0 @@
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import Add from '@mui/icons-material/Add';
|
||||
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||
import { FlagCreationButton } from '../ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||
|
||||
interface IWelcomeToProjectProps {
|
||||
projectId: string;
|
||||
setConnectSdkOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
interface ICreateFlagProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
const Container = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
flexBasis: '70%',
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
}));
|
||||
|
||||
const TitleBox = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2, 7, 2, 7),
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.divider,
|
||||
minHeight: '80px',
|
||||
}));
|
||||
|
||||
const Actions = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
}));
|
||||
|
||||
const ActionBox = styled('div')(({ theme }) => ({
|
||||
flexBasis: '50%',
|
||||
padding: theme.spacing(3, 2, 6, 8),
|
||||
display: 'flex',
|
||||
gap: theme.spacing(3),
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
const TitleContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(2),
|
||||
alignItems: 'center',
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
|
||||
const NeutralCircleContainer = styled('span')(({ theme }) => ({
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.palette.neutral.border,
|
||||
borderRadius: '50%',
|
||||
}));
|
||||
|
||||
const MainCircleContainer = styled(NeutralCircleContainer)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.background.paper,
|
||||
}));
|
||||
|
||||
const ExistingFlagContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(3),
|
||||
height: '100%',
|
||||
}));
|
||||
|
||||
const SuccessContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: theme.palette.success.light,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
padding: theme.spacing(2, 2, 2, 2),
|
||||
}));
|
||||
|
||||
export const WelcomeToProject = ({
|
||||
projectId,
|
||||
setConnectSdkOpen,
|
||||
}: IWelcomeToProjectProps) => {
|
||||
const { project, refetch } = useProjectOverview(projectId);
|
||||
const isFirstFlagCreated =
|
||||
project.onboardingStatus.status === 'first-flag-created';
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TitleBox>
|
||||
<Typography fontWeight='bold'>
|
||||
Welcome to your project
|
||||
</Typography>
|
||||
<Typography variant='body2'>
|
||||
Complete the steps below to start working with this project
|
||||
</Typography>
|
||||
</TitleBox>
|
||||
<Actions>
|
||||
<ActionBox>
|
||||
{project.onboardingStatus.status ===
|
||||
'first-flag-created' ? (
|
||||
<ExistingFlag />
|
||||
) : (
|
||||
<CreateFlag projectId={projectId} />
|
||||
)}
|
||||
</ActionBox>
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
<NeutralCircleContainer>2</NeutralCircleContainer>
|
||||
Connect an SDK
|
||||
</TitleContainer>
|
||||
<Typography>
|
||||
Your project is not yet connected to any SDK. To start
|
||||
using your feature flag, connect an SDK to the project.
|
||||
</Typography>
|
||||
<ResponsiveButton
|
||||
onClick={() => {
|
||||
setConnectSdkOpen(true);
|
||||
}}
|
||||
maxWidth='200px'
|
||||
projectId={projectId}
|
||||
Icon={Add}
|
||||
disabled={!isFirstFlagCreated}
|
||||
permission={CREATE_FEATURE}
|
||||
>
|
||||
Connect SDK
|
||||
</ResponsiveButton>
|
||||
</ActionBox>
|
||||
</Actions>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateFlag = ({ projectId }: ICreateFlagProps) => {
|
||||
const { refetch } = useProjectOverview(projectId);
|
||||
return (
|
||||
<>
|
||||
<TitleContainer>
|
||||
<NeutralCircleContainer>1</NeutralCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
<Typography>
|
||||
<div>The project currently holds no feature flags.</div>
|
||||
<div>Create one to get started.</div>
|
||||
</Typography>
|
||||
<FlagCreationButton
|
||||
text='Create flag'
|
||||
skipNavigationOnComplete={true}
|
||||
onSuccess={refetch}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ExistingFlag = () => {
|
||||
return (
|
||||
<ExistingFlagContainer>
|
||||
<TitleContainer>
|
||||
<MainCircleContainer>✓</MainCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
<SuccessContainer>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
Congratulations! You have created your first flag
|
||||
</Typography>
|
||||
<Typography variant='body2'>
|
||||
Click into the flag below to customize the flag further
|
||||
</Typography>
|
||||
</SuccessContainer>
|
||||
</ExistingFlagContainer>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user