1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-03 01:18:43 +02:00

feat: onboarding flow will not break (#8198)

1. Now onboarding flow will not break when feature is created
2. Now the bottom table will appear as soon as first feature appears
3. ExistingFlag component was reworked to match the new UX


![image](https://github.com/user-attachments/assets/2022f4ad-246c-47f9-927f-726f72da5e97)
This commit is contained in:
Jaanus Sellin 2024-09-20 14:31:11 +03:00 committed by GitHub
parent 87b997698b
commit ebcdd67db0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 65 deletions

View File

@ -114,10 +114,12 @@ export const ProjectFeatureToggles = ({
const isPlaceholder = Boolean(initialLoad || (loading && total));
const onboardingStarted =
const isOnboarded =
onboardingUIEnabled && project.onboardingStatus.status === 'onboarded';
const isNotOnboarded =
onboardingUIEnabled && project.onboardingStatus.status !== 'onboarded';
const hasMultipleFeaturesOrNotOnboarding =
(total !== undefined && total > 1) || !onboardingStarted;
const hasFeaturesOrOnboarded =
(total !== undefined && total > 0) || isOnboarded;
const columns = useMemo(
() => [
@ -404,7 +406,7 @@ export const ProjectFeatureToggles = ({
return (
<Container>
<ConditionallyRender
condition={onboardingStarted}
condition={isNotOnboarded}
show={
<ProjectOnboarding
projectId={projectId}
@ -413,7 +415,7 @@ export const ProjectFeatureToggles = ({
}
/>
<ConditionallyRender
condition={hasMultipleFeaturesOrNotOnboarding}
condition={hasFeaturesOrOnboarded}
show={
<PageContent
disableLoading

View File

@ -40,6 +40,8 @@ import { useFlagLimits } from './useFlagLimits';
interface ICreateFeatureDialogProps {
open: boolean;
onClose: () => void;
skipNavigationOnComplete?: boolean;
onSuccess?: () => void;
}
const StyledDialog = styled(Dialog)(({ theme }) => ({
@ -78,17 +80,28 @@ const configButtonData = {
export const CreateFeatureDialog = ({
open,
onClose,
onSuccess,
skipNavigationOnComplete,
}: ICreateFeatureDialogProps) => {
if (open) {
// wrap the inner component so that we only fetch data etc
// when the dialog is actually open.
return <CreateFeatureDialogContent open={open} onClose={onClose} />;
return (
<CreateFeatureDialogContent
open={open}
onClose={onClose}
skipNavigationOnComplete={skipNavigationOnComplete}
onSuccess={onSuccess}
/>
);
}
};
const CreateFeatureDialogContent = ({
open,
onClose,
skipNavigationOnComplete,
onSuccess,
}: ICreateFeatureDialogProps) => {
const { setToastData, setToastApiError } = useToast();
const { setShowFeedback } = useContext(UIContext);
@ -153,13 +166,17 @@ const CreateFeatureDialogContent = ({
const payload = getTogglePayload();
try {
await createFeatureToggle(project, payload);
if (!skipNavigationOnComplete) {
navigate(`/projects/${project}/features/${name}`);
}
setToastData({
title: 'Flag created successfully',
text: 'Now you can start using your flag.',
confetti: true,
type: 'success',
});
onClose();
onSuccess?.();
setShowFeedback(true);
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));

View File

@ -45,6 +45,8 @@ interface IFlagCreationButtonProps {
'text' | 'outlined' | 'contained',
ButtonPropsVariantOverrides
>;
skipNavigationOnComplete?: boolean;
onSuccess?: () => void;
}
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
@ -54,6 +56,8 @@ const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
export const FlagCreationButton = ({
variant,
text = 'New feature flag',
skipNavigationOnComplete,
onSuccess,
}: IFlagCreationButtonProps) => {
const [searchParams] = useSearchParams();
const projectId = useRequiredPathParam('projectId');
@ -78,6 +82,8 @@ export const FlagCreationButton = ({
<CreateFeatureDialog
open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)}
skipNavigationOnComplete={skipNavigationOnComplete}
onSuccess={onSuccess}
/>
</>
);

View File

@ -4,19 +4,13 @@ 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 { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import { Link } from 'react-router-dom';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
interface IWelcomeToProjectProps {
projectId: string;
setConnectSdkOpen: (open: boolean) => void;
}
interface IExistingFlagsProps {
featureId: string;
interface ICreateFlagProps {
projectId: string;
}
@ -72,17 +66,6 @@ const MainCircleContainer = styled(NeutralCircleContainer)(({ theme }) => ({
color: theme.palette.background.paper,
}));
const TypeCircleContainer = styled(MainCircleContainer)(({ theme }) => ({
borderRadius: '20%',
}));
const FlagLink = styled(Link)({
fontWeight: 'bold',
textDecoration: 'none',
display: 'flex',
justifyContent: 'center',
});
const ExistingFlagContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
@ -90,15 +73,22 @@ const ExistingFlagContainer = styled('div')(({ theme }) => ({
height: '100%',
}));
const FlagCreationContainer = styled('div')(({ theme }) => ({
marginTop: 'auto',
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 } = useProjectOverview(projectId);
const { project, refetch } = useProjectOverview(projectId);
const isFirstFlagCreated =
project.onboardingStatus.status === 'first-flag-created';
@ -116,12 +106,9 @@ export const WelcomeToProject = ({
<ActionBox>
{project.onboardingStatus.status ===
'first-flag-created' ? (
<ExistingFlag
projectId={projectId}
featureId={project.onboardingStatus.feature}
/>
<ExistingFlag />
) : (
<CreateFlag />
<CreateFlag projectId={projectId} />
)}
</ActionBox>
<ActionBox>
@ -151,7 +138,8 @@ export const WelcomeToProject = ({
);
};
const CreateFlag = () => {
const CreateFlag = ({ projectId }: ICreateFlagProps) => {
const { refetch } = useProjectOverview(projectId);
return (
<>
<TitleContainer>
@ -162,47 +150,30 @@ const CreateFlag = () => {
<div>The project currently holds no feature flags.</div>
<div>Create one to get started.</div>
</Typography>
<FlagCreationButton text='Create flag' />
<FlagCreationButton
text='Create flag'
skipNavigationOnComplete={true}
onSuccess={refetch}
/>
</>
);
};
const ExistingFlag = ({ featureId, projectId }: IExistingFlagsProps) => {
const { feature } = useFeature(projectId, featureId);
const { featureTypes } = useFeatureTypes();
const IconComponent = getFeatureTypeIcons(feature.type);
const typeName = featureTypes.find(
(featureType) => featureType.id === feature.type,
)?.name;
const typeTitle = `${typeName || feature.type} flag`;
const ExistingFlag = () => {
return (
<ExistingFlagContainer>
<TitleContainer>
<MainCircleContainer></MainCircleContainer>
Create a feature flag
</TitleContainer>
<TitleContainer>
<HtmlTooltip arrow title={typeTitle} describeChild>
<TypeCircleContainer>
<IconComponent />
</TypeCircleContainer>
</HtmlTooltip>
<FlagLink
to={`/projects/${projectId}/features/${feature.name}`}
>
{feature.name}
</FlagLink>
<Link to={`/projects/${projectId}/features/${feature.name}`}>
view flag
</Link>
</TitleContainer>
<FlagCreationContainer>
<FlagCreationButton
variant='outlined'
text='Create a new flag'
/>
</FlagCreationContainer>
<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>
);
};