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

View File

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

View File

@ -45,6 +45,8 @@ interface IFlagCreationButtonProps {
'text' | 'outlined' | 'contained', 'text' | 'outlined' | 'contained',
ButtonPropsVariantOverrides ButtonPropsVariantOverrides
>; >;
skipNavigationOnComplete?: boolean;
onSuccess?: () => void;
} }
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
@ -54,6 +56,8 @@ const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
export const FlagCreationButton = ({ export const FlagCreationButton = ({
variant, variant,
text = 'New feature flag', text = 'New feature flag',
skipNavigationOnComplete,
onSuccess,
}: IFlagCreationButtonProps) => { }: IFlagCreationButtonProps) => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
@ -78,6 +82,8 @@ export const FlagCreationButton = ({
<CreateFeatureDialog <CreateFeatureDialog
open={openCreateDialog} open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)} 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 { FlagCreationButton } from '../ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; 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 { interface IWelcomeToProjectProps {
projectId: string; projectId: string;
setConnectSdkOpen: (open: boolean) => void; setConnectSdkOpen: (open: boolean) => void;
} }
interface IExistingFlagsProps { interface ICreateFlagProps {
featureId: string;
projectId: string; projectId: string;
} }
@ -72,17 +66,6 @@ const MainCircleContainer = styled(NeutralCircleContainer)(({ theme }) => ({
color: theme.palette.background.paper, 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 }) => ({ const ExistingFlagContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@ -90,15 +73,22 @@ const ExistingFlagContainer = styled('div')(({ theme }) => ({
height: '100%', height: '100%',
})); }));
const FlagCreationContainer = styled('div')(({ theme }) => ({ const SuccessContainer = styled('div')(({ theme }) => ({
marginTop: 'auto', 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 = ({ export const WelcomeToProject = ({
projectId, projectId,
setConnectSdkOpen, setConnectSdkOpen,
}: IWelcomeToProjectProps) => { }: IWelcomeToProjectProps) => {
const { project } = useProjectOverview(projectId); const { project, refetch } = useProjectOverview(projectId);
const isFirstFlagCreated = const isFirstFlagCreated =
project.onboardingStatus.status === 'first-flag-created'; project.onboardingStatus.status === 'first-flag-created';
@ -116,12 +106,9 @@ export const WelcomeToProject = ({
<ActionBox> <ActionBox>
{project.onboardingStatus.status === {project.onboardingStatus.status ===
'first-flag-created' ? ( 'first-flag-created' ? (
<ExistingFlag <ExistingFlag />
projectId={projectId}
featureId={project.onboardingStatus.feature}
/>
) : ( ) : (
<CreateFlag /> <CreateFlag projectId={projectId} />
)} )}
</ActionBox> </ActionBox>
<ActionBox> <ActionBox>
@ -151,7 +138,8 @@ export const WelcomeToProject = ({
); );
}; };
const CreateFlag = () => { const CreateFlag = ({ projectId }: ICreateFlagProps) => {
const { refetch } = useProjectOverview(projectId);
return ( return (
<> <>
<TitleContainer> <TitleContainer>
@ -162,47 +150,30 @@ const CreateFlag = () => {
<div>The project currently holds no feature flags.</div> <div>The project currently holds no feature flags.</div>
<div>Create one to get started.</div> <div>Create one to get started.</div>
</Typography> </Typography>
<FlagCreationButton text='Create flag' /> <FlagCreationButton
text='Create flag'
skipNavigationOnComplete={true}
onSuccess={refetch}
/>
</> </>
); );
}; };
const ExistingFlag = ({ featureId, projectId }: IExistingFlagsProps) => { const ExistingFlag = () => {
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`;
return ( return (
<ExistingFlagContainer> <ExistingFlagContainer>
<TitleContainer> <TitleContainer>
<MainCircleContainer></MainCircleContainer> <MainCircleContainer></MainCircleContainer>
Create a feature flag Create a feature flag
</TitleContainer> </TitleContainer>
<TitleContainer> <SuccessContainer>
<HtmlTooltip arrow title={typeTitle} describeChild> <Typography fontWeight='bold' variant='body2'>
<TypeCircleContainer> Congratulations! You have created your first flag
<IconComponent /> </Typography>
</TypeCircleContainer> <Typography variant='body2'>
</HtmlTooltip> Click into the flag below to customize the flag further
<FlagLink </Typography>
to={`/projects/${projectId}/features/${feature.name}`} </SuccessContainer>
>
{feature.name}
</FlagLink>
<Link to={`/projects/${projectId}/features/${feature.name}`}>
view flag
</Link>
</TitleContainer>
<FlagCreationContainer>
<FlagCreationButton
variant='outlined'
text='Create a new flag'
/>
</FlagCreationContainer>
</ExistingFlagContainer> </ExistingFlagContainer>
); );
}; };