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 
This commit is contained in:
parent
87b997698b
commit
ebcdd67db0
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user