From 9fbb041bfd506be8795bd5b4c09bc56a01b945ad Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Fri, 10 May 2024 11:01:49 +0300 Subject: [PATCH] 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) Screenshot 2024-05-09 at 14 06 50 --------- Signed-off-by: andreas-unleash --- frontend/src/component/menu/routes.ts | 2 +- .../Project/CreateProject/CreateProject.tsx | 58 +------ .../CreateProjectDialog.tsx | 164 ++++++++++++++++++ .../Project/CreateProject/NewProjectForm.tsx | 6 +- .../project/ProjectList/ProjectList.tsx | 23 ++- 5 files changed, 196 insertions(+), 57 deletions(-) create mode 100644 frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 3a809b7367..0d74fd9562 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -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 diff --git a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx index 757736c39a..62a2bbfc6b 100644 --- a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx +++ b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx @@ -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 ; + } + 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 ( - - - Cancel - - - - ); - } - return ( 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 ( + + + + Cancel + + + + + ); +}; diff --git a/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx b/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx index dd32de27d4..5227ef8175 100644 --- a/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx +++ b/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx @@ -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 }) => ({ diff --git a/frontend/src/component/project/ProjectList/ProjectList.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx index 15c92445ff..f0d1cad876 100644 --- a/frontend/src/component/project/ProjectList/ProjectList.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -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 ( { navigate('/projects/create')} + onClick={handleClick} maxWidth='700px' permission={CREATE_PROJECT} disabled={createButtonData.disabled} @@ -250,6 +262,15 @@ export const ProjectListNew = () => { } /> + setOpenCreateDialog(false)} + /> + } + /> ); };