1
0
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.


![image](https://github.com/user-attachments/assets/45ec680f-64df-4877-92cb-33fef506f0ad)
This commit is contained in:
Jaanus Sellin 2024-09-23 13:52:00 +03:00 committed by GitHub
parent f45b7a07b4
commit 9f5e909436
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 223 additions and 236 deletions

View File

@ -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}
/>
}
/>

View File

@ -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={() => {}}
/>
}
/>

View File

@ -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>
);
};

View File

@ -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>
</>
);
};

View File

@ -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>
);
};