1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: create project dialog (#7012)

Adds a redirect to `/projects?create=true` to keep the `projects/create`
url still usable.
Loads the form in a modal

Closes
[1-2296](https://linear.app/unleash/issue/1-2296/project-creation-move-to-modal)

<img width="1537" alt="Screenshot 2024-05-09 at 14 06 50"
src="https://github.com/Unleash/unleash/assets/104830839/c108c7ee-5751-4380-9766-11368281a11a">

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
andreas-unleash 2024-05-10 11:01:49 +03:00 committed by GitHub
parent 1cc8ca901c
commit 9fbb041bfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 196 additions and 57 deletions

View File

@ -35,7 +35,6 @@ import { SegmentTable } from '../segments/SegmentTable/SegmentTable';
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
import { LazyPlayground } from 'component/playground/Playground/LazyPlayground';
import { Profile } from 'component/user/Profile/Profile';
import { LazyCreateProject } from 'component/project/Project/CreateProject/LazyCreateProject';
import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
import { LazyAdmin } from 'component/admin/LazyAdmin';
import { LazyProject } from 'component/project/Project/LazyProject';
@ -48,6 +47,7 @@ import { Insights } from '../insights/Insights';
import { FeedbackList } from '../feedbackNew/FeedbackList';
import { Application } from 'component/application/Application';
import { Signals } from 'component/signals/Signals';
import { LazyCreateProject } from '../project/Project/CreateProject/LazyCreateProject';
export const routes: IRoute[] = [
// Splash

View File

@ -1,6 +1,5 @@
import { useNavigate } from 'react-router-dom';
import { Navigate, useNavigate } from 'react-router-dom';
import ProjectForm from '../ProjectForm/ProjectForm';
import { NewProjectForm } from './NewProjectForm';
import useProjectForm, {
DEFAULT_PROJECT_STICKINESS,
} from '../hooks/useProjectForm';
@ -28,6 +27,7 @@ const CreateProject = () => {
const { setToastData, setToastApiError } = useToast();
const { refetchUser } = useAuthUser();
const { uiConfig } = useUiConfig();
const useNewProjectForm = useUiFlag('newCreateProjectUI');
const navigate = useNavigate();
const { trackEvent } = usePlausibleTracker();
const {
@ -52,6 +52,10 @@ const CreateProject = () => {
errors,
} = useProjectForm();
if (useNewProjectForm) {
return <Navigate to={`/projects?create=true`} replace />;
}
const generalDocumentation =
'Projects allows you to group feature toggles together in the management UI.';
@ -60,8 +64,6 @@ const CreateProject = () => {
const clearDocumentationOverride = () =>
setDocumentation(generalDocumentation);
const useNewProjectForm = useUiFlag('newCreateProjectUI');
const { createProject, loading } = useProjectApi();
const handleSubmit = async (e: Event) => {
@ -112,54 +114,6 @@ const CreateProject = () => {
navigate(GO_BACK);
};
if (useNewProjectForm) {
return (
<FormTemplate
disablePadding
loading={loading}
description={documentation}
documentationLink='https://docs.getunleash.io/reference/projects'
documentationLinkLabel='Projects documentation'
formatApiCode={formatApiCode}
>
<NewProjectForm
errors={errors}
handleSubmit={handleSubmit}
projectId={projectId}
projectEnvironments={projectEnvironments}
setProjectEnvironments={setProjectEnvironments}
setProjectId={setProjectId}
projectName={projectName}
projectStickiness={projectStickiness}
projectChangeRequestConfiguration={
projectChangeRequestConfiguration
}
updateProjectChangeRequestConfig={
updateProjectChangeRequestConfig
}
projectMode={projectMode}
setProjectMode={setProjectMode}
setProjectStickiness={setProjectStickiness}
setProjectName={setProjectName}
projectDesc={projectDesc}
setProjectDesc={setProjectDesc}
mode='Create'
clearErrors={clearErrors}
validateProjectId={validateProjectId}
overrideDocumentation={setDocumentation}
clearDocumentationOverride={clearDocumentationOverride}
>
<StyledButton onClick={handleCancel}>Cancel</StyledButton>
<CreateButton
name='project'
permission={CREATE_PROJECT}
data-testid={CREATE_PROJECT_BTN}
/>
</NewProjectForm>
</FormTemplate>
);
}
return (
<FormTemplate
loading={loading}

View File

@ -0,0 +1,164 @@
import type React from 'react';
import { formatUnknownError } from 'utils/formatUnknownError';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useToast from 'hooks/useToast';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { NewProjectForm } from '../NewProjectForm';
import { CreateButton } from 'component/common/CreateButton/CreateButton';
import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import useProjectForm, {
DEFAULT_PROJECT_STICKINESS,
} from '../../hooks/useProjectForm';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useState } from 'react';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useNavigate } from 'react-router-dom';
import { Button, Dialog, styled } from '@mui/material';
interface ICreateProjectDialogueProps {
open: boolean;
onClose: (e: React.SyntheticEvent) => void;
}
const StyledDialog = styled(Dialog)(({ theme, maxWidth }) => ({
'& .MuiDialog-paper': {
borderRadius: theme.shape.borderRadiusLarge,
maxWidth: theme.spacing(170),
backgroundColor: 'transparent',
},
padding: 0,
}));
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
const StyledButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(3),
}));
export const CreateProjectDialogue = ({
open,
onClose,
}: ICreateProjectDialogueProps) => {
const { createProject, loading } = useProjectApi();
const { refetchUser } = useAuthUser();
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const navigate = useNavigate();
const { trackEvent } = usePlausibleTracker();
const {
projectId,
projectName,
projectDesc,
projectMode,
projectEnvironments,
projectChangeRequestConfiguration,
setProjectMode,
setProjectId,
setProjectName,
setProjectDesc,
setProjectEnvironments,
updateProjectChangeRequestConfig,
getCreateProjectPayload,
clearErrors,
validateProjectId,
validateName,
setProjectStickiness,
projectStickiness,
errors,
} = useProjectForm();
const generalDocumentation =
'Projects allows you to group feature toggles together in the management UI.';
const [documentation, setDocumentation] = useState(generalDocumentation);
const clearDocumentationOverride = () =>
setDocumentation(generalDocumentation);
const formatApiCode = () => {
return `curl --location --request POST '${uiConfig.unleashUrl}/api/admin/projects' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getCreateProjectPayload(), undefined, 2)}'`;
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const validName = validateName();
const validId = await validateProjectId();
if (validName && validId) {
const payload = getCreateProjectPayload();
try {
await createProject(payload);
refetchUser();
navigate(`/projects/${projectId}`, { replace: true });
setToastData({
title: 'Project created',
text: 'Now you can add toggles to this project',
confetti: true,
type: 'success',
});
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
trackEvent('project_stickiness_set');
}
trackEvent('project-mode', {
props: { mode: projectMode, action: 'added' },
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
}
};
return (
<StyledDialog open={open} onClose={onClose}>
<FormTemplate
disablePadding
loading={loading}
description={documentation}
documentationLink='https://docs.getunleash.io/reference/projects'
documentationLinkLabel='Projects documentation'
formatApiCode={formatApiCode}
>
<NewProjectForm
errors={errors}
handleSubmit={handleSubmit}
projectId={projectId}
projectEnvironments={projectEnvironments}
setProjectEnvironments={setProjectEnvironments}
setProjectId={setProjectId}
projectName={projectName}
projectStickiness={projectStickiness}
projectChangeRequestConfiguration={
projectChangeRequestConfiguration
}
updateProjectChangeRequestConfig={
updateProjectChangeRequestConfig
}
projectMode={projectMode}
setProjectMode={setProjectMode}
setProjectStickiness={setProjectStickiness}
setProjectName={setProjectName}
projectDesc={projectDesc}
setProjectDesc={setProjectDesc}
mode='Create'
clearErrors={clearErrors}
validateProjectId={validateProjectId}
overrideDocumentation={setDocumentation}
clearDocumentationOverride={clearDocumentationOverride}
>
<StyledButton onClick={onClose}>Cancel</StyledButton>
<CreateButton
name='project'
permission={CREATE_PROJECT}
data-testid={CREATE_PROJECT_BTN}
/>
</NewProjectForm>
</FormTemplate>
</StyledDialog>
);
};

View File

@ -25,7 +25,7 @@ const StyledFormSection = styled('div')(({ theme }) => ({
borderBlockStart: `1px solid ${theme.palette.divider}`,
},
padding: theme.spacing(7),
padding: theme.spacing(6),
}));
const TopGrid = styled(StyledFormSection)(({ theme }) => ({
@ -60,11 +60,11 @@ const StyledInput = styled(Input)(({ theme }) => ({
}));
const StyledProjectName = styled(StyledInput)(({ theme }) => ({
'*': { fontSize: theme.typography.h1.fontSize },
'*': { fontSize: theme.typography.h2.fontSize },
}));
const StyledProjectDescription = styled(StyledInput)(({ theme }) => ({
'*': { fontSize: theme.typography.h2.fontSize },
'*': { fontSize: theme.typography.h3.fontSize },
}));
const OptionButtons = styled(StyledFormSection)(({ theme }) => ({

View File

@ -26,6 +26,7 @@ import { useUiFlag } from 'hooks/useUiFlag';
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import { groupProjects } from './group-projects';
import { ProjectGroup } from './ProjectGroup';
import { CreateProjectDialogue } from '../Project/CreateProject/CreateProjectDialog/CreateProjectDialog';
const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px',
@ -104,6 +105,10 @@ export const ProjectListNew = () => {
const splitProjectList = useUiFlag('projectListFilterMyProjects');
const myProjects = new Set(useProfile().profile?.projects || []);
const showCreateDialog = Boolean(searchParams.get('create'));
const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog);
const useNewProjectForm = useUiFlag('newCreateProjectUI');
useEffect(() => {
const tableState: PageQueryType = {};
if (searchValue) {
@ -173,6 +178,13 @@ export const ProjectListNew = () => {
);
};
function handleClick() {
if (useNewProjectForm) {
return setOpenCreateDialog(true);
}
navigate('/projects/create');
}
return (
<PageContent
isLoading={loading}
@ -196,7 +208,7 @@ export const ProjectListNew = () => {
<ResponsiveButton
Icon={Add}
endIcon={createButtonData.endIcon}
onClick={() => navigate('/projects/create')}
onClick={handleClick}
maxWidth='700px'
permission={CREATE_PROJECT}
disabled={createButtonData.disabled}
@ -250,6 +262,15 @@ export const ProjectListNew = () => {
}
/>
</StyledContainer>
<ConditionallyRender
condition={useNewProjectForm}
show={
<CreateProjectDialogue
open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)}
/>
}
/>
</PageContent>
);
};