diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx index 34e3caa4b841..ae3408e9c049 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -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 = ({ } /> diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.test.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.test.tsx similarity index 87% rename from frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.test.tsx rename to frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.test.tsx index b088fb6864f9..7cb2a6317913 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.test.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.test.tsx @@ -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 () => { {}} + setOnboardingFlow={() => {}} /> } /> @@ -45,9 +46,10 @@ test('Project can connect SDK', async () => { {}} + setOnboardingFlow={() => {}} /> } /> diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.tsx index d00324eedf2a..d708ad410756 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/ProjectOnboarding.tsx @@ -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 ( - - + + + + Welcome to your project + + + + + + + + + Complete the steps below to start working with this project + + + + + {project.onboardingStatus.status === + 'first-flag-created' ? ( + + ) : ( + + )} + + + + 2 + Connect an SDK + + + Your project is not yet connected to any SDK. To start + using your feature flag, connect an SDK to the project. + + { + setConnectSdkOpen(true); + }} + maxWidth='200px' + projectId={projectId} + Icon={Add} + disabled={!isFirstFlagCreated} + permission={CREATE_FEATURE} + > + Connect SDK + + + + + + ); }; + +const CreateFlag = ({ projectId }: ICreateFlagProps) => { + const { refetch } = useProjectOverview(projectId); + return ( + <> + + 1 + Create a feature flag + + + The project currently holds no feature flags. + Create one to get started. + + + > + ); +}; + +const ExistingFlag = () => { + return ( + + + ✓ + Create a feature flag + + + + Congratulations! You have created your first flag + + + Click into the flag below to customize the flag further + + + + ); +}; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/SdkExample.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/SdkExample.tsx index da68efd621bd..5a46884f655a 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/SdkExample.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/SdkExample.tsx @@ -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,27 +30,22 @@ export const SdkExample = () => { setSelectedSdk(event.target.value); }; return ( - - - View SDK Example - - - - - See an example implementation of your preferred SDK. - - - Go to example - - + <> + View SDK Example + + Choose your preferred SDK to view an example + + + Go to example + > ); }; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx deleted file mode 100644 index 6a1e98410340..000000000000 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOnboarding/WelcomeToProject.tsx +++ /dev/null @@ -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 ( - - - - Welcome to your project - - - Complete the steps below to start working with this project - - - - - {project.onboardingStatus.status === - 'first-flag-created' ? ( - - ) : ( - - )} - - - - 2 - Connect an SDK - - - Your project is not yet connected to any SDK. To start - using your feature flag, connect an SDK to the project. - - { - setConnectSdkOpen(true); - }} - maxWidth='200px' - projectId={projectId} - Icon={Add} - disabled={!isFirstFlagCreated} - permission={CREATE_FEATURE} - > - Connect SDK - - - - - ); -}; - -const CreateFlag = ({ projectId }: ICreateFlagProps) => { - const { refetch } = useProjectOverview(projectId); - return ( - <> - - 1 - Create a feature flag - - - The project currently holds no feature flags. - Create one to get started. - - - > - ); -}; - -const ExistingFlag = () => { - return ( - - - ✓ - Create a feature flag - - - - Congratulations! You have created your first flag - - - Click into the flag below to customize the flag further - - - - ); -};