From ebcdd67db0750b4d08da7e02c7cbe5e5774f9519 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin <sellinjaanus@gmail.com> Date: Fri, 20 Sep 2024 14:31:11 +0300 Subject: [PATCH] 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) --- .../ProjectFeatureToggles.tsx | 12 +-- .../CreateFeatureDialog.tsx | 21 ++++- .../ProjectFeatureTogglesHeader.tsx | 6 ++ .../ProjectOnboarding/WelcomeToProject.tsx | 87 +++++++------------ 4 files changed, 61 insertions(+), 65 deletions(-) diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx index 606d707504be..d3e2e485c3b2 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -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 diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx index f948d0798586..178f8c3c0e68 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/CreateFeatureDialog.tsx @@ -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); - navigate(`/projects/${project}/features/${name}`); + 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)); diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx index 69762f7eaa0f..af1f3dd0f50f 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx @@ -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} /> </> ); diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx index 88cce787fc2c..6a1e98410340 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx @@ -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> ); };