From 9141a9377f55144e700f0866bee65c3685efe963 Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:32:48 +0530 Subject: [PATCH] [WEB-5472] refactor: components of project creation flow (#8462) --- .../components/projects/create/attributes.tsx | 3 +- .../ce/components/projects/create/root.tsx | 21 +++++----- .../projects/create/template-select.tsx | 7 +--- .../project/create-project-modal.tsx | 2 +- .../project/create/common-attributes.tsx | 30 +++++++------- .../core/components/project/create/header.tsx | 39 ++++++++++++------- packages/types/src/index.ts | 1 + packages/types/src/pagination.ts | 15 +++++++ 8 files changed, 70 insertions(+), 48 deletions(-) create mode 100644 packages/types/src/pagination.ts diff --git a/apps/web/ce/components/projects/create/attributes.tsx b/apps/web/ce/components/projects/create/attributes.tsx index d86a38109..f19537bdc 100644 --- a/apps/web/ce/components/projects/create/attributes.tsx +++ b/apps/web/ce/components/projects/create/attributes.tsx @@ -1,4 +1,3 @@ -import type { FC } from "react"; import { Controller, useFormContext } from "react-hook-form"; // plane imports import { NETWORK_CHOICES, ETabIndices } from "@plane/constants"; @@ -79,7 +78,7 @@ function ProjectAttributes(props: Props) { placeholder={t("lead")} multiple={false} buttonVariant="border-with-text" - tabIndex={5} + tabIndex={getIndex("lead")} /> ); diff --git a/apps/web/ce/components/projects/create/root.tsx b/apps/web/ce/components/projects/create/root.tsx index dce810294..ddf7920b4 100644 --- a/apps/web/ce/components/projects/create/root.tsx +++ b/apps/web/ce/components/projects/create/root.tsx @@ -1,24 +1,23 @@ -import { useState } from "react"; -import { observer } from "mobx-react"; -import { FormProvider, useForm } from "react-hook-form"; -import { PROJECT_TRACKER_EVENTS, RANDOM_EMOJI_CODES } from "@plane/constants"; +import { PROJECT_TRACKER_EVENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; +import { observer } from "mobx-react"; +import { useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; // ui import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { EFileAssetType } from "@plane/types"; -import type { IProject } from "@plane/types"; // constants import ProjectCommonAttributes from "@/components/project/create/common-attributes"; import ProjectCreateHeader from "@/components/project/create/header"; import ProjectCreateButtons from "@/components/project/create/project-create-buttons"; // hooks -import { DEFAULT_COVER_IMAGE_URL, getCoverImageType, uploadCoverImage } from "@/helpers/cover-image.helper"; +import { getCoverImageType, uploadCoverImage } from "@/helpers/cover-image.helper"; import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useProject } from "@/hooks/store/use-project"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web types import type { TProject } from "@/plane-web/types/projects"; -import ProjectAttributes from "./attributes"; +import { ProjectAttributes } from "./attributes"; import { getProjectFormValues } from "./utils"; export type TCreateProjectFormProps = { @@ -37,7 +36,7 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre const { t } = useTranslation(); const { addProjectToFavorites, createProject, updateProject } = useProject(); // states - const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true); + const [shouldAutoSyncIdentifier, setShouldAutoSyncIdentifier] = useState(true); // form info const methods = useForm({ defaultValues: { ...getProjectFormValues(), ...data }, @@ -167,7 +166,7 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre const handleClose = () => { onClose(); - setIsChangeInIdentifierRequired(true); + setShouldAutoSyncIdentifier(true); setTimeout(() => { reset(); }, 300); @@ -182,8 +181,8 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre diff --git a/apps/web/ce/components/projects/create/template-select.tsx b/apps/web/ce/components/projects/create/template-select.tsx index b41232852..3220c0fe8 100644 --- a/apps/web/ce/components/projects/create/template-select.tsx +++ b/apps/web/ce/components/projects/create/template-select.tsx @@ -1,11 +1,6 @@ -type TProjectTemplateDropdownSize = "xs" | "sm"; - export type TProjectTemplateSelect = { disabled?: boolean; - size?: TProjectTemplateDropdownSize; - placeholder?: string; - dropDownContainerClassName?: string; - handleModalClose: () => void; + onClick?: () => void; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/apps/web/core/components/project/create-project-modal.tsx b/apps/web/core/components/project/create-project-modal.tsx index 46e102703..ca6d0a2a6 100644 --- a/apps/web/core/components/project/create-project-modal.tsx +++ b/apps/web/core/components/project/create-project-modal.tsx @@ -60,7 +60,7 @@ export function CreateProjectModal(props: Props) { }); return ( - + {currentStep === EProjectCreationSteps.CREATE_PROJECT && ( ; isMobile: boolean; - isChangeInIdentifierRequired: boolean; - setIsChangeInIdentifierRequired: (value: boolean) => void; + shouldAutoSyncIdentifier: boolean; + setShouldAutoSyncIdentifier: (value: boolean) => void; handleFormOnChange?: () => void; }; function ProjectCommonAttributes(props: Props) { - const { setValue, isMobile, isChangeInIdentifierRequired, setIsChangeInIdentifierRequired, handleFormOnChange } = - props; + const { setValue, isMobile, shouldAutoSyncIdentifier, setShouldAutoSyncIdentifier, handleFormOnChange } = props; const { formState: { errors }, control, @@ -33,21 +32,22 @@ function ProjectCommonAttributes(props: Props) { const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); const { t } = useTranslation(); - const handleNameChange = (onChange: (...event: any[]) => void) => (e: ChangeEvent) => { - if (!isChangeInIdentifierRequired) { + const handleNameChange = + (onChange: (event: ChangeEvent) => void) => (e: ChangeEvent) => { + if (!shouldAutoSyncIdentifier) { + onChange(e); + return; + } + if (e.target.value === "") setValue("identifier", ""); + else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 10)); onChange(e); - return; - } - if (e.target.value === "") setValue("identifier", ""); - else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 10)); - onChange(e); - handleFormOnChange?.(); - }; + handleFormOnChange?.(); + }; - const handleIdentifierChange = (onChange: any) => (e: ChangeEvent) => { + const handleIdentifierChange = (onChange: (value: string) => void) => (e: ChangeEvent) => { const { value } = e.target; const alphanumericValue = projectIdentifierSanitizer(value); - setIsChangeInIdentifierRequired(false); + setShouldAutoSyncIdentifier(false); onChange(alphanumericValue); handleFormOnChange?.(); }; diff --git a/apps/web/core/components/project/create/header.tsx b/apps/web/core/components/project/create/header.tsx index b201be4fd..37f635cf9 100644 --- a/apps/web/core/components/project/create/header.tsx +++ b/apps/web/core/components/project/create/header.tsx @@ -18,11 +18,14 @@ import { ProjectTemplateSelect } from "@/plane-web/components/projects/create/te type Props = { handleClose: () => void; isMobile?: boolean; + handleFormChange?: () => void; + isClosable?: boolean; + handleTemplateSelect?: () => void; }; function ProjectCreateHeader(props: Props) { - const { handleClose, isMobile = false } = props; - const { watch, control } = useFormContext(); + const { handleClose, isMobile = false, handleFormChange, isClosable = true, handleTemplateSelect } = props; + const { watch, control, setValue } = useFormContext(); const { t } = useTranslation(); // derived values const coverImage = watch("cover_image_url"); @@ -38,13 +41,15 @@ function ProjectCreateHeader(props: Props) { className="absolute left-0 top-0 h-full w-full rounded-lg" />
- -
-
- +
+ {isClosable && ( +
+ +
+ )}
( { + onChange(data); + handleFormChange?.(); + }} control={control} - onChange={onChange} value={value ?? null} tabIndex={getIndex("cover_image")} /> @@ -72,7 +80,7 @@ function ProjectCreateHeader(props: Props) { className="flex items-center justify-center" buttonClassName="flex items-center justify-center" label={ - + } @@ -85,15 +93,20 @@ function ProjectCreateHeader(props: Props) { }; else if (val?.type === "icon") logoValue = val.value; - onChange({ + const newLogoProps = { in_use: val?.type, [val?.type]: logoValue, + }; + setValue("logo_props", newLogoProps, { + shouldDirty: true, }); + onChange(newLogoProps); + handleFormChange?.(); setIsOpen(false); }} - defaultIconColor={value.in_use && value.in_use === "icon" ? value.icon?.color : undefined} + defaultIconColor={value?.in_use && value.in_use === "icon" ? value.icon?.color : undefined} defaultOpen={ - value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON + value?.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON } /> )} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9908d5dba..9d3103c83 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -50,3 +50,4 @@ export * from "./workspace-draft-issues/base"; export * from "./workspace-notifications"; export * from "./workspace-views"; export * from "./base-layouts"; +export * from "./pagination"; diff --git a/packages/types/src/pagination.ts b/packages/types/src/pagination.ts new file mode 100644 index 000000000..94ed237e7 --- /dev/null +++ b/packages/types/src/pagination.ts @@ -0,0 +1,15 @@ +// Generic paginated response type for API responses +export type TPaginatedResponse = { + results: T; + grouped_by?: string | null; + sub_grouped_by?: string | null; + total_count?: number; + next_cursor?: string; + prev_cursor?: string; + next_page_results?: boolean; + prev_page_results?: boolean; + count?: number; + total_pages?: number; + total_results?: number; + extra_stats?: string | null; +};