mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-28 17:55:15 +02:00
feat: project feature limit UI (#4220)
This commit is contained in:
parent
81c005013e
commit
469727bb19
@ -32,6 +32,7 @@ const CreateProject = () => {
|
|||||||
projectName,
|
projectName,
|
||||||
projectMode,
|
projectMode,
|
||||||
projectDesc,
|
projectDesc,
|
||||||
|
featureLimit,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
@ -40,6 +41,7 @@ const CreateProject = () => {
|
|||||||
validateProjectId,
|
validateProjectId,
|
||||||
validateName,
|
validateName,
|
||||||
setProjectStickiness,
|
setProjectStickiness,
|
||||||
|
setFeatureLimit,
|
||||||
setProjectMode,
|
setProjectMode,
|
||||||
projectStickiness,
|
projectStickiness,
|
||||||
errors,
|
errors,
|
||||||
@ -105,7 +107,9 @@ const CreateProject = () => {
|
|||||||
projectName={projectName}
|
projectName={projectName}
|
||||||
projectMode={projectMode}
|
projectMode={projectMode}
|
||||||
projectStickiness={projectStickiness}
|
projectStickiness={projectStickiness}
|
||||||
|
featureLimit={featureLimit}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
|
setFeatureLimit={setFeatureLimit}
|
||||||
setProjectMode={setProjectMode}
|
setProjectMode={setProjectMode}
|
||||||
setProjectName={setProjectName}
|
setProjectName={setProjectName}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
|
@ -126,6 +126,8 @@ const EditProject = () => {
|
|||||||
projectStickiness={projectStickiness}
|
projectStickiness={projectStickiness}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
setProjectMode={setProjectMode}
|
setProjectMode={setProjectMode}
|
||||||
|
setFeatureLimit={() => {}}
|
||||||
|
featureLimit={''}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
mode="Edit"
|
mode="Edit"
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
|
|
||||||
|
export const FeatureTogglesLimitTooltip: FC = () => (
|
||||||
|
<HelpIcon
|
||||||
|
htmlTooltip
|
||||||
|
tooltip={
|
||||||
|
<Box>
|
||||||
|
Enforce an upper limit of the number of feature toggles that may
|
||||||
|
be created for this project. You can create unlimited feature
|
||||||
|
toggle if there is no limit set.
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
@ -7,6 +7,8 @@ import { ProjectMode } from '../hooks/useProjectForm';
|
|||||||
import { Box, styled, TextField } 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';
|
import Input from 'component/common/Input/Input';
|
||||||
|
import { FeatureTogglesLimitTooltip } from './FeatureTogglesLimitTooltip';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
interface IProjectForm {
|
interface IProjectForm {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -14,11 +16,14 @@ interface IProjectForm {
|
|||||||
projectDesc: string;
|
projectDesc: string;
|
||||||
projectStickiness?: string;
|
projectStickiness?: string;
|
||||||
projectMode?: string;
|
projectMode?: string;
|
||||||
|
featureLimit: string;
|
||||||
|
featureCount?: number;
|
||||||
setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
|
setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
||||||
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProjectName: React.Dispatch<React.SetStateAction<string>>;
|
setProjectName: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProjectDesc: React.Dispatch<React.SetStateAction<string>>;
|
setProjectDesc: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setFeatureLimit: React.Dispatch<React.SetStateAction<string>>;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
mode: 'Create' | 'Edit';
|
mode: 'Create' | 'Edit';
|
||||||
@ -47,9 +52,17 @@ const StyledDescription = styled('p')(({ theme }) => ({
|
|||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledSubtitle = styled('div')(({ theme }) => ({
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
lineHeight: 1.25,
|
||||||
|
paddingBottom: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledInput = styled(Input)(({ theme }) => ({
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
|
paddingRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTextField = styled(TextField)(({ theme }) => ({
|
const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
@ -57,12 +70,22 @@ const StyledTextField = styled(TextField)(({ theme }) => ({
|
|||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledSelect = styled(Select)(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
minWidth: '200px',
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledButtonContainer = styled('div')(() => ({
|
const StyledButtonContainer = styled('div')(() => ({
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledInputContainer = styled('div')(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
const ProjectForm: React.FC<IProjectForm> = ({
|
const ProjectForm: React.FC<IProjectForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -71,16 +94,20 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
projectDesc,
|
projectDesc,
|
||||||
projectStickiness,
|
projectStickiness,
|
||||||
projectMode,
|
projectMode,
|
||||||
|
featureLimit,
|
||||||
|
featureCount,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
setProjectStickiness,
|
setProjectStickiness,
|
||||||
setProjectMode,
|
setProjectMode,
|
||||||
|
setFeatureLimit,
|
||||||
errors,
|
errors,
|
||||||
mode,
|
mode,
|
||||||
validateProjectId,
|
validateProjectId,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
return (
|
return (
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -158,7 +185,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
<p>What is your project collaboration mode?</p>
|
<p>What is your project collaboration mode?</p>
|
||||||
<CollaborationModeTooltip />
|
<CollaborationModeTooltip />
|
||||||
</Box>
|
</Box>
|
||||||
<Select
|
<StyledSelect
|
||||||
id="project-mode"
|
id="project-mode"
|
||||||
value={projectMode}
|
value={projectMode}
|
||||||
label="Project collaboration mode"
|
label="Project collaboration mode"
|
||||||
@ -170,11 +197,54 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
{ key: 'open', label: 'open' },
|
{ key: 'open', label: 'open' },
|
||||||
{ key: 'protected', label: 'protected' },
|
{ key: 'protected', label: 'protected' },
|
||||||
]}
|
]}
|
||||||
style={{ minWidth: '200px' }}
|
></StyledSelect>
|
||||||
></Select>
|
|
||||||
</>
|
</>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(uiConfig.flags.newProjectLayout)}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 1,
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Feature toggles limit?</p>
|
||||||
|
<FeatureTogglesLimitTooltip />
|
||||||
|
</Box>
|
||||||
|
<StyledSubtitle>
|
||||||
|
Leave it empty if you don’t want to add a limit
|
||||||
|
</StyledSubtitle>
|
||||||
|
<StyledInputContainer>
|
||||||
|
<StyledInput
|
||||||
|
label={'Limit'}
|
||||||
|
name="value"
|
||||||
|
type={'number'}
|
||||||
|
value={featureLimit}
|
||||||
|
onChange={e =>
|
||||||
|
setFeatureLimit(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
featureCount !== undefined &&
|
||||||
|
featureLimit !== undefined &&
|
||||||
|
featureLimit.length > 0
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<Box>
|
||||||
|
({featureCount} of {featureLimit}{' '}
|
||||||
|
used)
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledInputContainer>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|
||||||
<StyledButtonContainer>{children}</StyledButtonContainer>
|
<StyledButtonContainer>{children}</StyledButtonContainer>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
|
@ -29,7 +29,6 @@ const EditProject = () => {
|
|||||||
const id = useRequiredPathParam('projectId');
|
const id = useRequiredPathParam('projectId');
|
||||||
const { project } = useProject(id);
|
const { project } = useProject(id);
|
||||||
const { defaultStickiness } = useDefaultProjectSettings(id);
|
const { defaultStickiness } = useDefaultProjectSettings(id);
|
||||||
const navigate = useNavigate();
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -38,11 +37,13 @@ const EditProject = () => {
|
|||||||
projectDesc,
|
projectDesc,
|
||||||
projectStickiness,
|
projectStickiness,
|
||||||
projectMode,
|
projectMode,
|
||||||
|
featureLimit,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
setProjectStickiness,
|
setProjectStickiness,
|
||||||
setProjectMode,
|
setProjectMode,
|
||||||
|
setFeatureLimit,
|
||||||
getProjectPayload,
|
getProjectPayload,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
validateProjectId,
|
validateProjectId,
|
||||||
@ -113,6 +114,8 @@ const EditProject = () => {
|
|||||||
setProjectId={setProjectId}
|
setProjectId={setProjectId}
|
||||||
projectName={projectName}
|
projectName={projectName}
|
||||||
projectMode={projectMode}
|
projectMode={projectMode}
|
||||||
|
featureLimit={featureLimit}
|
||||||
|
featureCount={project.features.length}
|
||||||
setProjectName={setProjectName}
|
setProjectName={setProjectName}
|
||||||
projectStickiness={projectStickiness}
|
projectStickiness={projectStickiness}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
@ -120,6 +123,7 @@ const EditProject = () => {
|
|||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
mode="Edit"
|
mode="Edit"
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
|
setFeatureLimit={setFeatureLimit}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
validateProjectId={validateProjectId}
|
validateProjectId={validateProjectId}
|
||||||
>
|
>
|
||||||
|
@ -9,7 +9,8 @@ const useProjectForm = (
|
|||||||
initialProjectName = '',
|
initialProjectName = '',
|
||||||
initialProjectDesc = '',
|
initialProjectDesc = '',
|
||||||
initialProjectStickiness = DEFAULT_PROJECT_STICKINESS,
|
initialProjectStickiness = DEFAULT_PROJECT_STICKINESS,
|
||||||
initialProjectMode: ProjectMode = 'open'
|
initialProjectMode: ProjectMode = 'open',
|
||||||
|
initialFeatureLimit = ''
|
||||||
) => {
|
) => {
|
||||||
const [projectId, setProjectId] = useState(initialProjectId);
|
const [projectId, setProjectId] = useState(initialProjectId);
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ const useProjectForm = (
|
|||||||
);
|
);
|
||||||
const [projectMode, setProjectMode] =
|
const [projectMode, setProjectMode] =
|
||||||
useState<ProjectMode>(initialProjectMode);
|
useState<ProjectMode>(initialProjectMode);
|
||||||
|
const [featureLimit, setFeatureLimit] =
|
||||||
|
useState<string>(initialFeatureLimit);
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const { validateId } = useProjectApi();
|
const { validateId } = useProjectApi();
|
||||||
@ -40,6 +43,10 @@ const useProjectForm = (
|
|||||||
setProjectMode(initialProjectMode);
|
setProjectMode(initialProjectMode);
|
||||||
}, [initialProjectMode]);
|
}, [initialProjectMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFeatureLimit(initialFeatureLimit);
|
||||||
|
}, [initialFeatureLimit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProjectStickiness(initialProjectStickiness);
|
setProjectStickiness(initialProjectStickiness);
|
||||||
}, [initialProjectStickiness]);
|
}, [initialProjectStickiness]);
|
||||||
@ -50,6 +57,7 @@ const useProjectForm = (
|
|||||||
name: projectName,
|
name: projectName,
|
||||||
description: projectDesc,
|
description: projectDesc,
|
||||||
defaultStickiness: projectStickiness,
|
defaultStickiness: projectStickiness,
|
||||||
|
featureLimit: featureLimit,
|
||||||
mode: projectMode,
|
mode: projectMode,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -87,11 +95,13 @@ const useProjectForm = (
|
|||||||
projectDesc,
|
projectDesc,
|
||||||
projectStickiness,
|
projectStickiness,
|
||||||
projectMode,
|
projectMode,
|
||||||
|
featureLimit,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
setProjectStickiness,
|
setProjectStickiness,
|
||||||
setProjectMode,
|
setProjectMode,
|
||||||
|
setFeatureLimit,
|
||||||
getProjectPayload,
|
getProjectPayload,
|
||||||
validateName,
|
validateName,
|
||||||
validateProjectId,
|
validateProjectId,
|
||||||
|
Loading…
Reference in New Issue
Block a user