1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00

feat: project UI rework, move edit and delete buttons deeper (#4195)

This commit is contained in:
Jaanus Sellin 2023-07-11 09:47:38 +03:00 committed by GitHub
parent ec2978b133
commit a2b06e4222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 379 additions and 113 deletions

View File

@ -1 +1 @@
export const formTemplateSidebarWidth = '27.5rem'; export const formTemplateSidebarWidth = '36%';

View File

@ -19,12 +19,13 @@ import { formTemplateSidebarWidth } from './FormTemplate.styles';
import { relative } from 'themes/themeStyles'; import { relative } from 'themes/themeStyles';
interface ICreateProps { interface ICreateProps {
title: string; title?: string;
description: string; description: string;
documentationLink: string; documentationLink: string;
documentationLinkLabel: string; documentationLinkLabel: string;
loading?: boolean; loading?: boolean;
modal?: boolean; modal?: boolean;
disablePadding?: boolean;
formatApiCode: () => string; formatApiCode: () => string;
} }
@ -45,20 +46,22 @@ const StyledContainer = styled('section', {
const StyledRelativeDiv = styled('div')(({ theme }) => relative); const StyledRelativeDiv = styled('div')(({ theme }) => relative);
const StyledFormContent = styled('div')(({ theme }) => ({ const StyledFormContent = styled('div', {
shouldForwardProp: prop => prop !== 'disablePadding',
})<{ disablePadding?: boolean }>(({ theme, disablePadding }) => ({
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: theme.spacing(6),
flexGrow: 1, flexGrow: 1,
padding: disablePadding ? 0 : theme.spacing(6),
[theme.breakpoints.down('lg')]: { [theme.breakpoints.down('lg')]: {
padding: theme.spacing(4), padding: disablePadding ? 0 : theme.spacing(4),
}, },
[theme.breakpoints.down(1100)]: { [theme.breakpoints.down(1100)]: {
width: '100%', width: '100%',
}, },
[theme.breakpoints.down(500)]: { [theme.breakpoints.down(500)]: {
padding: theme.spacing(4, 2), padding: disablePadding ? 0 : theme.spacing(4, 2),
}, },
})); }));
@ -157,6 +160,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
loading, loading,
modal, modal,
formatApiCode, formatApiCode,
disablePadding,
}) => { }) => {
const { setToastData } = useToast(); const { setToastData } = useToast();
const smallScreen = useMediaQuery(`(max-width:${1099}px)`); const smallScreen = useMediaQuery(`(max-width:${1099}px)`);
@ -194,13 +198,16 @@ const FormTemplate: React.FC<ICreateProps> = ({
</StyledRelativeDiv> </StyledRelativeDiv>
} }
/> />
<StyledFormContent> <StyledFormContent disablePadding={disablePadding}>
<ConditionallyRender <ConditionallyRender
condition={loading || false} condition={loading || false}
show={<Loader />} show={<Loader />}
elseShow={ elseShow={
<> <>
<StyledTitle>{title}</StyledTitle> <ConditionallyRender
condition={title !== undefined}
show={<StyledTitle>{title}</StyledTitle>}
/>
{children} {children}
</> </>
} }

View File

@ -13,9 +13,14 @@ import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate'; import { GO_BACK } from 'constants/navigate';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Button, styled } from '@mui/material';
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN'; const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
const StyledButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(3),
}));
const CreateProject = () => { const CreateProject = () => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { refetchUser } = useAuthUser(); const { refetchUser } = useAuthUser();
@ -95,7 +100,6 @@ const CreateProject = () => {
<ProjectForm <ProjectForm
errors={errors} errors={errors}
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
handleCancel={handleCancel}
projectId={projectId} projectId={projectId}
setProjectId={setProjectId} setProjectId={setProjectId}
projectName={projectName} projectName={projectName}
@ -115,6 +119,7 @@ const CreateProject = () => {
permission={CREATE_PROJECT} permission={CREATE_PROJECT}
data-testid={CREATE_PROJECT_BTN} data-testid={CREATE_PROJECT_BTN}
/> />
<StyledButton onClick={handleCancel}>Cancel</StyledButton>
</ProjectForm> </ProjectForm>
</FormTemplate> </FormTemplate>
); );

View File

@ -14,13 +14,17 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useContext } from 'react'; import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { Alert } from '@mui/material'; import { Alert, Button, styled } from '@mui/material';
import { GO_BACK } from 'constants/navigate'; import { GO_BACK } from 'constants/navigate';
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings'; import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const EDIT_PROJECT_BTN = 'EDIT_PROJECT_BTN'; const EDIT_PROJECT_BTN = 'EDIT_PROJECT_BTN';
const StyledButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(3),
}));
const EditProject = () => { const EditProject = () => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
@ -114,7 +118,6 @@ const EditProject = () => {
<ProjectForm <ProjectForm
errors={errors} errors={errors}
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
handleCancel={handleCancel}
projectId={projectId} projectId={projectId}
setProjectId={setProjectId} setProjectId={setProjectId}
projectName={projectName} projectName={projectName}
@ -133,7 +136,8 @@ const EditProject = () => {
permission={UPDATE_PROJECT} permission={UPDATE_PROJECT}
projectId={projectId} projectId={projectId}
data-testid={EDIT_PROJECT_BTN} data-testid={EDIT_PROJECT_BTN}
/> />{' '}
<StyledButton onClick={handleCancel}>Cancel</StyledButton>
</ProjectForm> </ProjectForm>
</FormTemplate> </FormTemplate>
); );

View File

@ -165,7 +165,10 @@ export const Project = () => {
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={!isOss()} condition={
!isOss() &&
!Boolean(uiConfig.flags.newProjectLayout)
}
show={ show={
<PermissionIconButton <PermissionIconButton
permission={UPDATE_PROJECT} permission={UPDATE_PROJECT}
@ -184,7 +187,10 @@ export const Project = () => {
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={!isOss()} condition={
!isOss() &&
!Boolean(uiConfig.flags.newProjectLayout)
}
show={ show={
<PermissionIconButton <PermissionIconButton
permission={DELETE_PROJECT} permission={DELETE_PROJECT}

View File

@ -1,37 +0,0 @@
import Input from 'component/common/Input/Input';
import { TextField, Button, styled } from '@mui/material';
export const StyledForm = styled('form')(() => ({
display: 'flex',
flexDirection: 'column',
height: '100%',
}));
export const StyledContainer = styled('div')(() => ({
maxWidth: '400px',
}));
export const StyledDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1),
marginRight: theme.spacing(1),
}));
export const StyledInput = styled(Input)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
export const StyledTextField = styled(TextField)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
export const StyledButtonContainer = styled('div')(() => ({
marginTop: 'auto',
display: 'flex',
justifyContent: 'flex-end',
}));
export const StyledButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(3),
}));

View File

@ -1,20 +1,12 @@
import React from 'react'; import React from 'react';
import { trim } from 'component/common/util'; import { trim } from 'component/common/util';
import {
StyledButton,
StyledButtonContainer,
StyledContainer,
StyledDescription,
StyledForm,
StyledInput,
StyledTextField,
} from './ProjectForm.styles';
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect'; import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import Select from 'component/common/select'; import Select from 'component/common/select';
import { ProjectMode } from '../hooks/useProjectForm'; import { ProjectMode } from '../hooks/useProjectForm';
import { Box } from '@mui/material'; import { Box, styled, TextField } from '@mui/material';
import { CollaborationModeTooltip } from './CollaborationModeTooltip'; import { CollaborationModeTooltip } from './CollaborationModeTooltip';
import Input from 'component/common/Input/Input';
interface IProjectForm { interface IProjectForm {
projectId: string; projectId: string;
@ -28,7 +20,6 @@ interface IProjectForm {
setProjectName: React.Dispatch<React.SetStateAction<string>>; setProjectName: React.Dispatch<React.SetStateAction<string>>;
setProjectDesc: React.Dispatch<React.SetStateAction<string>>; setProjectDesc: React.Dispatch<React.SetStateAction<string>>;
handleSubmit: (e: any) => void; handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string }; errors: { [key: string]: string };
mode: 'Create' | 'Edit'; mode: 'Create' | 'Edit';
clearErrors: () => void; clearErrors: () => void;
@ -40,10 +31,41 @@ const PROJECT_ID_INPUT = 'PROJECT_ID_INPUT';
const PROJECT_NAME_INPUT = 'PROJECT_NAME_INPUT'; const PROJECT_NAME_INPUT = 'PROJECT_NAME_INPUT';
const PROJECT_DESCRIPTION_INPUT = 'PROJECT_DESCRIPTION_INPUT'; const PROJECT_DESCRIPTION_INPUT = 'PROJECT_DESCRIPTION_INPUT';
const StyledForm = styled('form')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
height: '100%',
paddingBottom: theme.spacing(4),
}));
const StyledContainer = styled('div')(() => ({
maxWidth: '400px',
}));
const StyledDescription = styled('p')(({ theme }) => ({
marginBottom: theme.spacing(1),
marginRight: theme.spacing(1),
}));
const StyledInput = styled(Input)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
const StyledTextField = styled(TextField)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
}));
const StyledButtonContainer = styled('div')(() => ({
marginTop: 'auto',
display: 'flex',
justifyContent: 'flex-end',
}));
const ProjectForm: React.FC<IProjectForm> = ({ const ProjectForm: React.FC<IProjectForm> = ({
children, children,
handleSubmit, handleSubmit,
handleCancel,
projectId, projectId,
projectName, projectName,
projectDesc, projectDesc,
@ -153,10 +175,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
</> </>
</StyledContainer> </StyledContainer>
<StyledButtonContainer> <StyledButtonContainer>{children}</StyledButtonContainer>
{children}
<StyledButton onClick={handleCancel}>Cancel</StyledButton>
</StyledButtonContainer>
</StyledForm> </StyledForm>
); );
}; };

View File

@ -12,12 +12,23 @@ import { ChangeRequestConfiguration } from './ChangeRequestConfiguration/ChangeR
import { ProjectApiAccess } from 'component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess'; import { ProjectApiAccess } from 'component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess';
import { ProjectSegments } from './ProjectSegments/ProjectSegments'; import { ProjectSegments } from './ProjectSegments/ProjectSegments';
import { ProjectDefaultStrategySettings } from './ProjectDefaultStrategySettings/ProjectDefaultStrategySettings'; import { ProjectDefaultStrategySettings } from './ProjectDefaultStrategySettings/ProjectDefaultStrategySettings';
import { Settings } from './Settings/Settings';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const ProjectSettings = () => { export const ProjectSettings = () => {
const location = useLocation(); const location = useLocation();
const { uiConfig } = useUiConfig();
const navigate = useNavigate(); const navigate = useNavigate();
const tabs: ITab[] = [ const tabs: ITab[] = [
...(uiConfig.flags.newProjectLayout
? [
{
id: '',
label: 'Settings',
},
]
: []),
{ {
id: 'environments', id: 'environments',
label: 'Environments', label: 'Environments',
@ -59,6 +70,9 @@ export const ProjectSettings = () => {
onChange={onChange} onChange={onChange}
> >
<Routes> <Routes>
{uiConfig.flags.newProjectLayout ? (
<Route path="/*" element={<Settings />} />
) : null}
<Route <Route
path="environments/*" path="environments/*"
element={<ProjectEnvironmentList />} element={<ProjectEnvironmentList />}

View File

@ -0,0 +1,79 @@
import { styled } from '@mui/material';
import { DELETE_PROJECT } from 'component/providers/AccessProvider/permissions';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { DeleteProjectDialogue } from '../../DeleteProject/DeleteProjectDialogue';
import { useState } from 'react';
import { useNavigate } from 'react-router';
const StyledContainer = styled('div')(({ theme }) => ({
borderTop: `1px solid ${theme.palette.divider}`,
}));
const StyledTitle = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(4),
lineHeight: 2,
}));
const StyledCounter = styled('div')(({ theme }) => ({
paddingTop: theme.spacing(3),
}));
const StyledButtonContainer = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
paddingTop: theme.spacing(3),
}));
interface IDeleteProjectProps {
projectId: string;
featureCount: number;
}
export const DeleteProject = ({
projectId,
featureCount,
}: IDeleteProjectProps) => {
const [showDelDialog, setShowDelDialog] = useState(false);
const navigate = useNavigate();
return (
<StyledContainer>
<StyledTitle>Delete project</StyledTitle>
<div>
Before you can delete a project, you must first archive all the
feature toggles associated with it. Keep in mind that deleting a
project will permanently remove all the archived feature
toggles, and they cannot be recovered once deleted.
</div>
<StyledCounter>
Currently there are{' '}
<strong>{featureCount} feature toggles active</strong>
</StyledCounter>
<StyledButtonContainer>
<PermissionButton
permission={DELETE_PROJECT}
disabled={featureCount > 0}
projectId={projectId}
onClick={() => {
setShowDelDialog(true);
}}
tooltipProps={{
title: 'Delete project',
}}
data-loading
>
Delete project
</PermissionButton>
</StyledButtonContainer>
<DeleteProjectDialogue
project={projectId}
open={showDelDialog}
onClose={() => {
setShowDelDialog(false);
}}
onSuccess={() => {
navigate('/projects');
}}
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,143 @@
import { useNavigate } from 'react-router-dom';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useProject from 'hooks/api/getters/useProject/useProject';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { Alert } from '@mui/material';
import { GO_BACK } from 'constants/navigate';
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectForm, {
DEFAULT_PROJECT_STICKINESS,
} from '../../hooks/useProjectForm';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { DeleteProject } from './DeleteProject';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import ProjectForm from '../../ProjectForm/ProjectForm';
const EditProject = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { hasAccess } = useContext(AccessContext);
const id = useRequiredPathParam('projectId');
const { project } = useProject(id);
const { defaultStickiness } = useDefaultProjectSettings(id);
const navigate = useNavigate();
const { trackEvent } = usePlausibleTracker();
const {
projectId,
projectName,
projectDesc,
projectStickiness,
projectMode,
setProjectId,
setProjectName,
setProjectDesc,
setProjectStickiness,
setProjectMode,
getProjectPayload,
clearErrors,
validateProjectId,
validateName,
errors,
} = useProjectForm(
id,
project.name,
project.description,
defaultStickiness,
project.mode
);
const formatApiCode = () => {
return `curl --location --request PUT '${
uiConfig.unleashUrl
}/api/admin/projects/${id}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getProjectPayload(), undefined, 2)}'`;
};
const { editProject, loading } = useProjectApi();
const handleSubmit = async (e: Event) => {
e.preventDefault();
const payload = getProjectPayload();
const validName = validateName();
if (validName) {
try {
await editProject(id, payload);
setToastData({
title: 'Project information updated',
type: 'success',
});
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
trackEvent('project_stickiness_set');
}
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
}
};
const accessDeniedAlert = !hasAccess(UPDATE_PROJECT, projectId) && (
<Alert severity="error" sx={{ mb: 4 }}>
You do not have the required permissions to edit this project.
</Alert>
);
return (
<FormTemplate
loading={loading}
disablePadding={true}
description="Projects allows you to group feature toggles together in the management UI."
documentationLink="https://docs.getunleash.io/reference/projects"
documentationLinkLabel="Projects documentation"
formatApiCode={formatApiCode}
>
{accessDeniedAlert}
<PageContent header={<PageHeader title="Settings" />}>
<ProjectForm
errors={errors}
handleSubmit={handleSubmit}
projectId={projectId}
setProjectId={setProjectId}
projectName={projectName}
projectMode={projectMode}
setProjectName={setProjectName}
projectStickiness={projectStickiness}
setProjectStickiness={setProjectStickiness}
setProjectMode={setProjectMode}
projectDesc={projectDesc}
mode="Edit"
setProjectDesc={setProjectDesc}
clearErrors={clearErrors}
validateProjectId={validateProjectId}
>
<PermissionButton
type="submit"
permission={UPDATE_PROJECT}
projectId={projectId}
>
Save changes
</PermissionButton>
</ProjectForm>
<DeleteProject
projectId={projectId}
featureCount={project.features.length}
/>
</PageContent>
</FormTemplate>
);
};
export default EditProject;

View File

@ -0,0 +1,29 @@
import { useContext } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
import { Alert } from '@mui/material';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import AccessContext from 'contexts/AccessContext';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { usePageTitle } from 'hooks/usePageTitle';
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
import EditProject from './EditProject';
export const Settings = () => {
const projectId = useRequiredPathParam('projectId');
const projectName = useProjectNameOrId(projectId);
const { hasAccess } = useContext(AccessContext);
usePageTitle(`Project configuration ${projectName}`);
if (!hasAccess(UPDATE_PROJECT, projectId)) {
return (
<PageContent header={<PageHeader title="Access" />}>
<Alert severity="error">
You need project owner permissions to access this section.
</Alert>
</PageContent>
);
}
return <EditProject />;
};

View File

@ -1,29 +0,0 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
pageContent: {
minHeight: '200px',
},
divider: {
height: '1px',
position: 'relative',
left: 0,
right: 0,
backgroundColor: theme.palette.divider,
margin: theme.spacing(4, -4, 3),
},
inputLabel: {
backgroundColor: theme.palette.background.paper,
},
roleName: {
fontWeight: 'bold',
padding: '5px 0px',
},
menuItem: {
width: '340px',
whiteSpace: 'normal',
},
projectRoleSelect: {
minWidth: '150px',
},
}));

View File

@ -49,7 +49,7 @@ export const ProjectCard = ({
isFavorite = false, isFavorite = false,
}: IProjectCardProps) => { }: IProjectCardProps) => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
const { isOss } = useUiConfig(); const { isOss, uiConfig } = useUiConfig();
const [anchorEl, setAnchorEl] = useState<Element | null>(null); const [anchorEl, setAnchorEl] = useState<Element | null>(null);
const [showDelDialog, setShowDelDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -117,24 +117,34 @@ export const ProjectCard = ({
<MenuItem <MenuItem
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
navigate(getProjectEditPath(id)); navigate(
getProjectEditPath(
id,
Boolean(uiConfig.flags.newProjectLayout)
)
);
}} }}
> >
<StyledEditIcon /> <StyledEditIcon />
Edit project Edit project
</MenuItem> </MenuItem>
<MenuItem <ConditionallyRender
onClick={e => { condition={!Boolean(uiConfig.flags.newProjectLayout)}
e.preventDefault(); show={
setShowDelDialog(true); <MenuItem
}} onClick={e => {
disabled={!canDeleteProject} e.preventDefault();
> setShowDelDialog(true);
<StyledDeleteIcon /> }}
{id === DEFAULT_PROJECT_ID && !canDeleteProject disabled={!canDeleteProject}
? "You can't delete the default project" >
: 'Delete project'} <StyledDeleteIcon />
</MenuItem> {id === DEFAULT_PROJECT_ID && !canDeleteProject
? "You can't delete the default project"
: 'Delete project'}
</MenuItem>
}
/>
</Menu> </Menu>
</StyledDivHeader> </StyledDivHeader>
<div data-loading> <div data-loading>

View File

@ -53,6 +53,7 @@ export interface IFlags {
advancedPlayground?: boolean; advancedPlayground?: boolean;
customRootRoles?: boolean; customRootRoles?: boolean;
strategySplittedButton?: boolean; strategySplittedButton?: boolean;
newProjectLayout?: boolean;
} }
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -1,3 +1,5 @@
import useUiConfig from '../hooks/api/getters/useUiConfig/useUiConfig';
export const getTogglePath = (projectId: string, featureToggleName: string) => { export const getTogglePath = (projectId: string, featureToggleName: string) => {
return `/projects/${projectId}/features/${featureToggleName}`; return `/projects/${projectId}/features/${featureToggleName}`;
}; };
@ -23,6 +25,11 @@ export const getCreateTogglePath = (
return path; return path;
}; };
export const getProjectEditPath = (projectId: string) => { export const getProjectEditPath = (
return `/projects/${projectId}/edit`; projectId: string,
newProjectPath: boolean
) => {
return newProjectPath
? `/projects/${projectId}/settings`
: `/projects/${projectId}/edit`;
}; };

View File

@ -89,6 +89,7 @@ exports[`should create default config 1`] = `
}, },
}, },
"migrationLock": false, "migrationLock": false,
"newProjectLayout": false,
"personalAccessTokensKillSwitch": false, "personalAccessTokensKillSwitch": false,
"proPlanAutoCharge": false, "proPlanAutoCharge": false,
"responseTimeWithAppNameKillSwitch": false, "responseTimeWithAppNameKillSwitch": false,
@ -121,6 +122,7 @@ exports[`should create default config 1`] = `
}, },
}, },
"migrationLock": false, "migrationLock": false,
"newProjectLayout": false,
"personalAccessTokensKillSwitch": false, "personalAccessTokensKillSwitch": false,
"proPlanAutoCharge": false, "proPlanAutoCharge": false,
"responseTimeWithAppNameKillSwitch": false, "responseTimeWithAppNameKillSwitch": false,

View File

@ -23,7 +23,8 @@ export type IFlagKey =
| 'disableNotifications' | 'disableNotifications'
| 'advancedPlayground' | 'advancedPlayground'
| 'customRootRoles' | 'customRootRoles'
| 'strategySplittedButton'; | 'strategySplittedButton'
| 'newProjectLayout';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -108,6 +109,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES, process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES,
false, false,
), ),
newProjectLayout: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_NEW_PROJECT_LAYOUT,
false,
),
}; };
export const defaultExperimentalOptions: IExperimentalOptions = { export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -40,6 +40,7 @@ process.nextTick(async () => {
segmentContextFieldUsage: true, segmentContextFieldUsage: true,
advancedPlayground: true, advancedPlayground: true,
strategySplittedButton: true, strategySplittedButton: true,
newProjectLayout: true,
}, },
}, },
authentication: { authentication: {