From 9a5d06b906974d5995fa41fc1c9b37673e974b75 Mon Sep 17 00:00:00 2001 From: gakshita Date: Tue, 17 Sep 2024 14:32:53 +0530 Subject: [PATCH 1/4] chore: intake emails and forms --- packages/ui/src/popovers/popover.tsx | 3 +- packages/ui/src/popovers/types.ts | 1 + .../(detail)/[projectId]/inbox/header.tsx | 26 +- .../[projectId]/inbox/intake-tooltip.tsx | 19 ++ .../[projectId]/submit-issue/header.tsx | 174 +++++++++++++ .../[projectId]/submit-issue/layout.tsx | 14 + .../[projectId]/submit-issue/page.tsx | 29 +++ .../constants/project/settings/features.tsx | 69 ++++- web/core/components/inbox/create-form.tsx | 240 ++++++++++++++++++ .../project/settings/features-list.tsx | 51 ++-- .../project/settings/intake-sub-features.tsx | 90 +++++++ 11 files changed, 679 insertions(+), 37 deletions(-) create mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx create mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx create mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx create mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx create mode 100644 web/core/components/inbox/create-form.tsx create mode 100644 web/core/components/project/settings/intake-sub-features.tsx diff --git a/packages/ui/src/popovers/popover.tsx b/packages/ui/src/popovers/popover.tsx index 30a168965fd..4860a25a89a 100644 --- a/packages/ui/src/popovers/popover.tsx +++ b/packages/ui/src/popovers/popover.tsx @@ -18,6 +18,7 @@ export const Popover = (props: TPopover) => { panelClassName = "", children, popoverButtonRef, + buttonRefClassName = "", } = props; // states const [referenceElement, setReferenceElement] = useState(null); @@ -38,7 +39,7 @@ export const Popover = (props: TPopover) => { return ( -
+
} className={cn( diff --git a/packages/ui/src/popovers/types.ts b/packages/ui/src/popovers/types.ts index 51b1e877a52..7801e2d8536 100644 --- a/packages/ui/src/popovers/types.ts +++ b/packages/ui/src/popovers/types.ts @@ -5,6 +5,7 @@ export type TPopoverButtonDefaultOptions = { // button and button styling button?: ReactNode; buttonClassName?: string; + buttonRefClassName?: string; disabled?: boolean; }; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx index a543eca0bea..bb7e2a192e9 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx @@ -1,17 +1,20 @@ "use client"; -import { FC, useState } from "react"; +import { FC, useRef, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { RefreshCcw } from "lucide-react"; +import { Info, RefreshCcw } from "lucide-react"; // ui -import { Breadcrumbs, Button, Intake, Header } from "@plane/ui"; +import { Breadcrumbs, Button, Intake, Header, Tooltip, Popover } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { InboxIssueCreateEditModalRoot } from "@/components/inbox"; // hooks import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store"; import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; +import IntakeSubFeatures from "@/components/project/settings/intake-sub-features"; +import IntakeTooltip from "./intake-tooltip"; +import { cn } from "@plane/editor"; export const ProjectInboxHeader: FC = observer(() => { // states @@ -29,11 +32,13 @@ export const ProjectInboxHeader: FC = observer(() => { [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], EUserPermissionsLevel.PROJECT ); + // ref + const popoverButtonRef = useRef(null); return (
-
+
{ /> } /> - } />} /> - + } + popperPosition="bottom-end" + panelClassName="rounded border-[0.5px] border-custom-border-300 bg-custom-background-100 p-3 text-xs shadow-custom-shadow-rg focus:outline-none" + > + + {loader === "pagination-loading" && (
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx new file mode 100644 index 00000000000..07349a02f64 --- /dev/null +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx @@ -0,0 +1,19 @@ +import IntakeSubFeatures from "@/components/project/settings/intake-sub-features"; +import { X } from "lucide-react"; +import { Popover } from "@headlessui/react"; + +const IntakeTooltip = () => { + return ( +
+
+ Intake info + + + + +
+ +
+ ); +}; +export default IntakeTooltip; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx new file mode 100644 index 00000000000..49816bad741 --- /dev/null +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { FC, useCallback } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +// types +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; +// ui +import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui"; +// components +import { BreadcrumbLink, Logo } from "@/components/common"; +import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; +// constants +import { + EIssueFilterType, + EIssuesStoreType, + EIssueLayoutTypes, + ISSUE_DISPLAY_FILTERS_BY_LAYOUT, +} from "@/constants/issue"; +// helpers +import { isIssueFilterActive } from "@/helpers/filter.helper"; +// hooks +import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; + +export const ProjectDraftIssueHeader: FC = observer(() => { + // router + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; + // store hooks + const { + issuesFilter: { issueFilters, updateFilters }, + } = useIssues(EIssuesStoreType.DRAFT); + const { currentProjectDetails, loader } = useProject(); + const { projectStates } = useProjectState(); + const { projectLabels } = useLabel(); + const { + project: { projectMemberIds }, + } = useMember(); + const { isMobile } = usePlatformOS(); + const activeLayout = issueFilters?.displayFilters?.layout; + + const handleFiltersUpdate = useCallback( + (key: keyof IIssueFilterOptions, value: string | string[]) => { + if (!workspaceSlug || !projectId) return; + const newValues = issueFilters?.filters?.[key] ?? []; + + if (Array.isArray(value)) { + // this validation is majorly for the filter start_date, target_date custom + value.forEach((val) => { + if (!newValues.includes(val)) newValues.push(val); + else newValues.splice(newValues.indexOf(val), 1); + }); + } else { + if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + } + + updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }); + }, + [workspaceSlug, projectId, issueFilters, updateFilters] + ); + + const handleLayoutChange = useCallback( + (layout: EIssueLayoutTypes) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }); + }, + [workspaceSlug, projectId, updateFilters] + ); + + const handleDisplayFilters = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter); + }, + [workspaceSlug, projectId, updateFilters] + ); + + const handleDisplayProperties = useCallback( + (property: Partial) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); + }, + [workspaceSlug, projectId, updateFilters] + ); + + const issueCount = currentProjectDetails + ? !issueFilters?.displayFilters?.sub_issue && currentProjectDetails.draft_sub_issues + ? currentProjectDetails.draft_issues - currentProjectDetails.draft_sub_issues + : currentProjectDetails.draft_issues + : undefined; + + return ( +
+
+
+ + + + + ) + } + /> + } + /> + + } /> + } + /> + + {issueCount && issueCount > 0 ? ( + 1 ? "issues" : "issue"} in project's draft`} + position="bottom" + > + + {issueCount} + + + ) : null} +
+ +
+ handleLayoutChange(layout)} + selectedLayout={activeLayout} + /> + + + + + + +
+
+
+ ); +}); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx new file mode 100644 index 00000000000..b6ff43c6c34 --- /dev/null +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx @@ -0,0 +1,14 @@ +"use client"; + +// components +import { AppHeader, ContentWrapper } from "@/components/core"; +import { ProjectDraftIssueHeader } from "./header"; + +export default function ProjectDraftIssuesLayou({ children }: { children: React.ReactNode }) { + return ( + <> + {/* } /> */} + {children} + + ); +} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx new file mode 100644 index 00000000000..6d615767d00 --- /dev/null +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +// components +import { PageHead } from "@/components/core"; +// hooks +import { useProject } from "@/hooks/store"; +import { IssueFormRoot } from "@/components/inbox/create-form"; + +const ProjectDraftIssuesPage = observer(() => { + const { projectId } = useParams(); + // store + const { getProjectById } = useProject(); + // derived values + const project = projectId ? getProjectById(projectId.toString()) : undefined; + const pageTitle = project?.name ? `${project?.name} - Submit Issues` : undefined; + + return ( + <> + +
+ +
+ + ); +}); + +export default ProjectDraftIssuesPage; diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx index 3fdccc97940..ff5658bf993 100644 --- a/web/ce/constants/project/settings/features.tsx +++ b/web/ce/constants/project/settings/features.tsx @@ -1,15 +1,29 @@ import { ReactNode } from "react"; -import { FileText, Layers, Timer } from "lucide-react"; +import { FileText, Layers, ListTodo, Mail, Timer, Zap } from "lucide-react"; import { ContrastIcon, DiceIcon, Intake } from "@plane/ui"; - +import IntakeSubFeatures from "../../../../core/components/project/settings/intake-sub-features"; +import { IProject } from "@plane/types"; +export type TProperties = { + property: string; + title: string; + description: string; + icon: ReactNode; + isPro: boolean; + isEnabled: boolean; + renderChildren?: ( + currentProjectDetails: IProject, + isAdmin: boolean, + handleSubmit: (featureKey: string, featureProperty: string) => Promise + ) => ReactNode; +}; export type TFeatureList = { - [key: string]: { - property: string; - title: string; - description: string; - icon: ReactNode; - isPro: boolean; - isEnabled: boolean; + [key: string]: TProperties; +}; +export type TIntakeFeatureList = { + [key: string]: TProperties & { + hasOptions: boolean; + hasHyperlink?: boolean; + canShuffle?: boolean; }; }; @@ -65,6 +79,9 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { icon: , isPro: false, isEnabled: true, + renderChildren: (currentProjectDetails, isAdmin, handleSubmit) => ( + + ), }, }, }, @@ -83,3 +100,37 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { }, }, }; + +export const INTAKE_FEATURES_LIST: TIntakeFeatureList = { + "in-app": { + property: "in_app", + title: "In-app", + description: "Let the Plane app users in your org add issues via intake", + icon: , + isPro: false, + isEnabled: true, + hasOptions: false, + }, + email: { + property: "email", + title: "Email", + description: "You can send or forward emails to this address to create tasks and attach the email to them", + icon: , + isPro: false, + isEnabled: true, + hasOptions: true, + hasHyperlink: false, + canShuffle: true, + }, + forms: { + property: "forms", + title: "Forms", + description: "You can share this link to get tasks created directly from the Web", + icon: , + isPro: false, + isEnabled: true, + hasOptions: true, + hasHyperlink: true, + canShuffle: true, + }, +}; diff --git a/web/core/components/inbox/create-form.tsx b/web/core/components/inbox/create-form.tsx new file mode 100644 index 00000000000..c84f89110d1 --- /dev/null +++ b/web/core/components/inbox/create-form.tsx @@ -0,0 +1,240 @@ +"use client"; + +import React, { FC, useState, useRef, useEffect } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { useForm } from "react-hook-form"; +// editor +import { EditorRefApi } from "@plane/editor"; +// types +import type { TIssue } from "@plane/types"; +// hooks +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; +// components +import { IssueDescriptionEditor, IssueTitleInput } from "@/components/issues/issue-modal/components"; +import { ETabIndices } from "@/constants/tab-indices"; +// helpers +import { cn } from "@/helpers/common.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; +// hooks +import { useIssueModal } from "@/hooks/context/use-issue-modal"; +import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; +import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; + +const defaultValues: Partial = { + project_id: "", + type_id: null, + name: "", + description_html: "", + estimate_point: null, + state_id: "", + parent_id: null, + priority: "none", + assignee_ids: [], + label_ids: [], + cycle_id: null, + module_ids: null, + start_date: null, + target_date: null, +}; + +export interface IssueFormProps { + projectId: string; +} + +export const IssueFormRoot: FC = observer((props) => { + const { projectId: defaultProjectId } = props; + + // states + const [gptAssistantModal, setGptAssistantModal] = useState(false); + + // refs + const editorRef = useRef(null); + const submitBtnRef = useRef(null); + const issueTitleRef = useRef(null); + + // router + const { workspaceSlug, projectId: routeProjectId } = useParams(); + + // store hooks + const { getProjectById } = useProject(); + const { isMobile } = usePlatformOS(); + + const { + issue: { getIssueById }, + } = useIssueDetail(); + const { fetchCycles } = useProjectIssueProperties(); + + // form info + const { + formState: { errors, isDirty, isSubmitting, dirtyFields }, + handleSubmit, + reset, + watch, + control, + getValues, + setValue, + } = useForm({ + defaultValues: { ...defaultValues, project_id: defaultProjectId }, + }); + + const projectId = watch("project_id"); + const activeAdditionalPropertiesLength = getActiveAdditionalPropertiesLength({ + projectId: projectId, + workspaceSlug: workspaceSlug?.toString(), + watch: watch, + }); + + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); + + //reset few fields on projectId change + useEffect(() => { + if (isDirty) { + const formData = getValues(); + + reset({ + ...defaultValues, + project_id: projectId, + name: formData.name, + description_html: formData.description_html, + priority: formData.priority, + start_date: formData.start_date, + target_date: formData.target_date, + parent_id: formData.parent_id, + }); + } + if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug?.toString(), projectId); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId]); + + const handleFormChange = () => {}; + const onSubmit = async (formData: Partial) => {}; + const onClose = () => {}; + + const handleFormSubmit = async (formData: Partial) => { + // Check if the editor is ready to discard + if (!editorRef.current?.isEditorReadyToDiscard()) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Editor is not ready to discard changes.", + }); + return; + } + + // check for required properties validation + if ( + !handlePropertyValuesValidation({ + projectId: projectId, + workspaceSlug: workspaceSlug?.toString(), + watch: watch, + }) + ) + return; + + const submitData = formData; + + // this condition helps to move the issues from draft to project issues + if (formData.hasOwnProperty("is_draft")) submitData.is_draft = formData.is_draft; + + await onSubmit(submitData) + .then(() => { + setGptAssistantModal(false); + reset({ + ...defaultValues, + project_id: getValues<"project_id">("project_id"), + type_id: getValues<"type_id">("type_id"), + }); + editorRef?.current?.clearEditor(); + }) + .catch((error) => { + console.error(error); + }); + }; + + const condition = + (watch("name") && watch("name") !== "") || (watch("description_html") && watch("description_html") !== "

"); + + return ( + <> +
handleFormSubmit(data))}> +
+

{"Create new"} issue

+ +
+ +
+
+
4 && + "max-h-[45vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm" + )} + > +
+ + setValue<"description_html">("description_html", description_html) + } + setGptAssistantModal={setGptAssistantModal} + handleGptAssistantClose={() => reset(getValues())} + onClose={onClose} + descriptionHtmlData={watch("description_html")} + /> +
+
+
+
+
+ + + +
+
+
+
+ + ); +}); diff --git a/web/core/components/project/settings/features-list.tsx b/web/core/components/project/settings/features-list.tsx index 91eb188024d..f1f17f2d598 100644 --- a/web/core/components/project/settings/features-list.tsx +++ b/web/core/components/project/settings/features-list.tsx @@ -70,31 +70,40 @@ export const ProjectFeaturesList: FC = observer((props) => { return (
-
-
- {featureItem.icon} -
-
-
-

{featureItem.title}

- {featureItem.isPro && ( - - - - )} +
+
+
+ {featureItem.icon} +
+
+
+

{featureItem.title}

+ {featureItem.isPro && ( + + + + )} +
+

+ {featureItem.description} +

-

{featureItem.description}

-
- handleSubmit(featureItemKey, featureItem.property)} - disabled={!featureItem.isEnabled || !isAdmin} - size="sm" - /> + handleSubmit(featureItemKey, featureItem.property)} + disabled={!featureItem.isEnabled || !isAdmin} + size="sm" + /> +
+
+ {currentProjectDetails?.[featureItem.property as keyof IProject] && + featureItem.renderChildren && + featureItem.renderChildren(currentProjectDetails, isAdmin, handleSubmit)} +
); })} diff --git a/web/core/components/project/settings/intake-sub-features.tsx b/web/core/components/project/settings/intake-sub-features.tsx new file mode 100644 index 00000000000..47816839bc6 --- /dev/null +++ b/web/core/components/project/settings/intake-sub-features.tsx @@ -0,0 +1,90 @@ +import { Button, Input, setToast, TOAST_TYPE, ToggleSwitch, Tooltip } from "@plane/ui"; +import { INTAKE_FEATURES_LIST } from "../../../../ce/constants/project/settings/features"; +import { UpgradeBadge } from "ee/components/workspace"; +import { IProject } from "@plane/types"; +import { Copy, ExternalLink, RefreshCcw } from "lucide-react"; +import { copyTextToClipboard } from "@/helpers/string.helper"; + +type Props = { + projectDetails?: IProject; + isAdmin?: boolean; + handleSubmit?: (featureKey: string, featureProperty: string) => Promise; + allowEdit?: boolean; + showDefault?: boolean; +}; +const IntakeSubFeatures = (props: Props) => { + const { projectDetails, isAdmin, handleSubmit, allowEdit = true, showDefault = true } = props; + const copyToClipboard = (text: string) => { + copyTextToClipboard(text).then(() => + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Copied to clipboard", + message: "The URL has been successfully copied to your clipboard", + }) + ); + }; + + return Object.keys(INTAKE_FEATURES_LIST) + .filter((featureKey) => featureKey !== "in-app" || showDefault) + .map((featureKey) => { + const feature = INTAKE_FEATURES_LIST[featureKey]; + return ( +
+
+
+
+
{feature.icon}
+
+
+

{feature.title}

+ {feature.isPro && ( + + + + )} +
+
+
+

{feature.description}

+
+ {allowEdit && handleSubmit && ( + handleSubmit(featureKey, feature.property)} + disabled={!feature.isEnabled || !isAdmin} + size="sm" + /> + )} +
+ {feature.hasOptions && ( +
+
+ + https://sites.plane.so/views/65f53425fe764f2589f28495fa0e8262/65f53425fe764f2589f28495fa0e8262 + + copyToClipboard("abc")} /> + {feature.hasHyperlink && ( + + + + )} +
+ {allowEdit && ( + + )} +
+ )} +
+ ); + }); +}; + +export default IntakeSubFeatures; From 252698e8c1aa8382b4e972f3033aeb2f70e1bfda Mon Sep 17 00:00:00 2001 From: gakshita Date: Fri, 4 Oct 2024 14:30:42 +0530 Subject: [PATCH 2/4] fix: moved files to ee --- .../[projectId]/inbox/intake-tooltip.tsx | 19 -- .../(detail)/[projectId]/inbox/layout.tsx | 2 +- .../[projectId]/submit-issue/header.tsx | 174 ------------- .../[projectId]/submit-issue/layout.tsx | 14 - .../[projectId]/submit-issue/page.tsx | 29 --- .../projects/settings/intake}/header.tsx | 26 +- .../projects/settings/intake/index.ts | 1 + .../constants/project/settings/features.tsx | 46 +--- web/core/components/inbox/create-form.tsx | 240 ------------------ .../project/settings/intake-sub-features.tsx | 90 ------- .../projects/settings/intake/index.ts | 1 + 11 files changed, 10 insertions(+), 632 deletions(-) delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx delete mode 100644 web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx rename web/{app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox => ce/components/projects/settings/intake}/header.tsx (74%) create mode 100644 web/ce/components/projects/settings/intake/index.ts delete mode 100644 web/core/components/inbox/create-form.tsx delete mode 100644 web/core/components/project/settings/intake-sub-features.tsx create mode 100644 web/ee/components/projects/settings/intake/index.ts diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx deleted file mode 100644 index 07349a02f64..00000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/intake-tooltip.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import IntakeSubFeatures from "@/components/project/settings/intake-sub-features"; -import { X } from "lucide-react"; -import { Popover } from "@headlessui/react"; - -const IntakeTooltip = () => { - return ( -
-
- Intake info - - - - -
- -
- ); -}; -export default IntakeTooltip; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/layout.tsx index 167823fc2b4..e4cde6cbb1e 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/layout.tsx @@ -2,7 +2,7 @@ // components import { AppHeader, ContentWrapper } from "@/components/core"; -import { ProjectInboxHeader } from "./header"; +import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake"; export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx deleted file mode 100644 index 49816bad741..00000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/header.tsx +++ /dev/null @@ -1,174 +0,0 @@ -"use client"; - -import { FC, useCallback } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// types -import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; -// ui -import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui"; -// components -import { BreadcrumbLink, Logo } from "@/components/common"; -import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; -// constants -import { - EIssueFilterType, - EIssuesStoreType, - EIssueLayoutTypes, - ISSUE_DISPLAY_FILTERS_BY_LAYOUT, -} from "@/constants/issue"; -// helpers -import { isIssueFilterActive } from "@/helpers/filter.helper"; -// hooks -import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; - -export const ProjectDraftIssueHeader: FC = observer(() => { - // router - const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; - // store hooks - const { - issuesFilter: { issueFilters, updateFilters }, - } = useIssues(EIssuesStoreType.DRAFT); - const { currentProjectDetails, loader } = useProject(); - const { projectStates } = useProjectState(); - const { projectLabels } = useLabel(); - const { - project: { projectMemberIds }, - } = useMember(); - const { isMobile } = usePlatformOS(); - const activeLayout = issueFilters?.displayFilters?.layout; - - const handleFiltersUpdate = useCallback( - (key: keyof IIssueFilterOptions, value: string | string[]) => { - if (!workspaceSlug || !projectId) return; - const newValues = issueFilters?.filters?.[key] ?? []; - - if (Array.isArray(value)) { - // this validation is majorly for the filter start_date, target_date custom - value.forEach((val) => { - if (!newValues.includes(val)) newValues.push(val); - else newValues.splice(newValues.indexOf(val), 1); - }); - } else { - if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); - else newValues.push(value); - } - - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }); - }, - [workspaceSlug, projectId, issueFilters, updateFilters] - ); - - const handleLayoutChange = useCallback( - (layout: EIssueLayoutTypes) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }); - }, - [workspaceSlug, projectId, updateFilters] - ); - - const handleDisplayFilters = useCallback( - (updatedDisplayFilter: Partial) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter); - }, - [workspaceSlug, projectId, updateFilters] - ); - - const handleDisplayProperties = useCallback( - (property: Partial) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); - }, - [workspaceSlug, projectId, updateFilters] - ); - - const issueCount = currentProjectDetails - ? !issueFilters?.displayFilters?.sub_issue && currentProjectDetails.draft_sub_issues - ? currentProjectDetails.draft_issues - currentProjectDetails.draft_sub_issues - : currentProjectDetails.draft_issues - : undefined; - - return ( -
-
-
- - - - - ) - } - /> - } - /> - - } /> - } - /> - - {issueCount && issueCount > 0 ? ( - 1 ? "issues" : "issue"} in project's draft`} - position="bottom" - > - - {issueCount} - - - ) : null} -
- -
- handleLayoutChange(layout)} - selectedLayout={activeLayout} - /> - - - - - - -
-
-
- ); -}); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx deleted file mode 100644 index b6ff43c6c34..00000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -// components -import { AppHeader, ContentWrapper } from "@/components/core"; -import { ProjectDraftIssueHeader } from "./header"; - -export default function ProjectDraftIssuesLayou({ children }: { children: React.ReactNode }) { - return ( - <> - {/* } /> */} - {children} - - ); -} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx deleted file mode 100644 index 6d615767d00..00000000000 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/submit-issue/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// components -import { PageHead } from "@/components/core"; -// hooks -import { useProject } from "@/hooks/store"; -import { IssueFormRoot } from "@/components/inbox/create-form"; - -const ProjectDraftIssuesPage = observer(() => { - const { projectId } = useParams(); - // store - const { getProjectById } = useProject(); - // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; - const pageTitle = project?.name ? `${project?.name} - Submit Issues` : undefined; - - return ( - <> - -
- -
- - ); -}); - -export default ProjectDraftIssuesPage; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx b/web/ce/components/projects/settings/intake/header.tsx similarity index 74% rename from web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx rename to web/ce/components/projects/settings/intake/header.tsx index bb7e2a192e9..a543eca0bea 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx +++ b/web/ce/components/projects/settings/intake/header.tsx @@ -1,20 +1,17 @@ "use client"; -import { FC, useRef, useState } from "react"; +import { FC, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { Info, RefreshCcw } from "lucide-react"; +import { RefreshCcw } from "lucide-react"; // ui -import { Breadcrumbs, Button, Intake, Header, Tooltip, Popover } from "@plane/ui"; +import { Breadcrumbs, Button, Intake, Header } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { InboxIssueCreateEditModalRoot } from "@/components/inbox"; // hooks import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store"; import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; -import IntakeSubFeatures from "@/components/project/settings/intake-sub-features"; -import IntakeTooltip from "./intake-tooltip"; -import { cn } from "@plane/editor"; export const ProjectInboxHeader: FC = observer(() => { // states @@ -32,13 +29,11 @@ export const ProjectInboxHeader: FC = observer(() => { [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], EUserPermissionsLevel.PROJECT ); - // ref - const popoverButtonRef = useRef(null); return (
-
+
{ /> } /> + } />} /> - } - popperPosition="bottom-end" - panelClassName="rounded border-[0.5px] border-custom-border-300 bg-custom-background-100 p-3 text-xs shadow-custom-shadow-rg focus:outline-none" - > - - + {loader === "pagination-loading" && (
diff --git a/web/ce/components/projects/settings/intake/index.ts b/web/ce/components/projects/settings/intake/index.ts new file mode 100644 index 00000000000..49ac70fe213 --- /dev/null +++ b/web/ce/components/projects/settings/intake/index.ts @@ -0,0 +1 @@ +export * from "./header"; diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx index ff5658bf993..18a5d8b0d58 100644 --- a/web/ce/constants/project/settings/features.tsx +++ b/web/ce/constants/project/settings/features.tsx @@ -1,8 +1,8 @@ import { ReactNode } from "react"; import { FileText, Layers, ListTodo, Mail, Timer, Zap } from "lucide-react"; import { ContrastIcon, DiceIcon, Intake } from "@plane/ui"; -import IntakeSubFeatures from "../../../../core/components/project/settings/intake-sub-features"; import { IProject } from "@plane/types"; + export type TProperties = { property: string; title: string; @@ -19,13 +19,6 @@ export type TProperties = { export type TFeatureList = { [key: string]: TProperties; }; -export type TIntakeFeatureList = { - [key: string]: TProperties & { - hasOptions: boolean; - hasHyperlink?: boolean; - canShuffle?: boolean; - }; -}; export type TProjectFeatures = { [key: string]: { @@ -79,9 +72,6 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { icon: , isPro: false, isEnabled: true, - renderChildren: (currentProjectDetails, isAdmin, handleSubmit) => ( - - ), }, }, }, @@ -100,37 +90,3 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { }, }, }; - -export const INTAKE_FEATURES_LIST: TIntakeFeatureList = { - "in-app": { - property: "in_app", - title: "In-app", - description: "Let the Plane app users in your org add issues via intake", - icon: , - isPro: false, - isEnabled: true, - hasOptions: false, - }, - email: { - property: "email", - title: "Email", - description: "You can send or forward emails to this address to create tasks and attach the email to them", - icon: , - isPro: false, - isEnabled: true, - hasOptions: true, - hasHyperlink: false, - canShuffle: true, - }, - forms: { - property: "forms", - title: "Forms", - description: "You can share this link to get tasks created directly from the Web", - icon: , - isPro: false, - isEnabled: true, - hasOptions: true, - hasHyperlink: true, - canShuffle: true, - }, -}; diff --git a/web/core/components/inbox/create-form.tsx b/web/core/components/inbox/create-form.tsx deleted file mode 100644 index c84f89110d1..00000000000 --- a/web/core/components/inbox/create-form.tsx +++ /dev/null @@ -1,240 +0,0 @@ -"use client"; - -import React, { FC, useState, useRef, useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -import { useForm } from "react-hook-form"; -// editor -import { EditorRefApi } from "@plane/editor"; -// types -import type { TIssue } from "@plane/types"; -// hooks -import { Button, TOAST_TYPE, setToast } from "@plane/ui"; -// components -import { IssueDescriptionEditor, IssueTitleInput } from "@/components/issues/issue-modal/components"; -import { ETabIndices } from "@/constants/tab-indices"; -// helpers -import { cn } from "@/helpers/common.helper"; -import { getTabIndex } from "@/helpers/tab-indices.helper"; -// hooks -import { useIssueModal } from "@/hooks/context/use-issue-modal"; -import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; -import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; - -const defaultValues: Partial = { - project_id: "", - type_id: null, - name: "", - description_html: "", - estimate_point: null, - state_id: "", - parent_id: null, - priority: "none", - assignee_ids: [], - label_ids: [], - cycle_id: null, - module_ids: null, - start_date: null, - target_date: null, -}; - -export interface IssueFormProps { - projectId: string; -} - -export const IssueFormRoot: FC = observer((props) => { - const { projectId: defaultProjectId } = props; - - // states - const [gptAssistantModal, setGptAssistantModal] = useState(false); - - // refs - const editorRef = useRef(null); - const submitBtnRef = useRef(null); - const issueTitleRef = useRef(null); - - // router - const { workspaceSlug, projectId: routeProjectId } = useParams(); - - // store hooks - const { getProjectById } = useProject(); - const { isMobile } = usePlatformOS(); - - const { - issue: { getIssueById }, - } = useIssueDetail(); - const { fetchCycles } = useProjectIssueProperties(); - - // form info - const { - formState: { errors, isDirty, isSubmitting, dirtyFields }, - handleSubmit, - reset, - watch, - control, - getValues, - setValue, - } = useForm({ - defaultValues: { ...defaultValues, project_id: defaultProjectId }, - }); - - const projectId = watch("project_id"); - const activeAdditionalPropertiesLength = getActiveAdditionalPropertiesLength({ - projectId: projectId, - workspaceSlug: workspaceSlug?.toString(), - watch: watch, - }); - - const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); - - //reset few fields on projectId change - useEffect(() => { - if (isDirty) { - const formData = getValues(); - - reset({ - ...defaultValues, - project_id: projectId, - name: formData.name, - description_html: formData.description_html, - priority: formData.priority, - start_date: formData.start_date, - target_date: formData.target_date, - parent_id: formData.parent_id, - }); - } - if (projectId && routeProjectId !== projectId) fetchCycles(workspaceSlug?.toString(), projectId); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [projectId]); - - const handleFormChange = () => {}; - const onSubmit = async (formData: Partial) => {}; - const onClose = () => {}; - - const handleFormSubmit = async (formData: Partial) => { - // Check if the editor is ready to discard - if (!editorRef.current?.isEditorReadyToDiscard()) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Editor is not ready to discard changes.", - }); - return; - } - - // check for required properties validation - if ( - !handlePropertyValuesValidation({ - projectId: projectId, - workspaceSlug: workspaceSlug?.toString(), - watch: watch, - }) - ) - return; - - const submitData = formData; - - // this condition helps to move the issues from draft to project issues - if (formData.hasOwnProperty("is_draft")) submitData.is_draft = formData.is_draft; - - await onSubmit(submitData) - .then(() => { - setGptAssistantModal(false); - reset({ - ...defaultValues, - project_id: getValues<"project_id">("project_id"), - type_id: getValues<"type_id">("type_id"), - }); - editorRef?.current?.clearEditor(); - }) - .catch((error) => { - console.error(error); - }); - }; - - const condition = - (watch("name") && watch("name") !== "") || (watch("description_html") && watch("description_html") !== "

"); - - return ( - <> -
handleFormSubmit(data))}> -
-

{"Create new"} issue

- -
- -
-
-
4 && - "max-h-[45vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm" - )} - > -
- - setValue<"description_html">("description_html", description_html) - } - setGptAssistantModal={setGptAssistantModal} - handleGptAssistantClose={() => reset(getValues())} - onClose={onClose} - descriptionHtmlData={watch("description_html")} - /> -
-
-
-
-
- - - -
-
-
-
- - ); -}); diff --git a/web/core/components/project/settings/intake-sub-features.tsx b/web/core/components/project/settings/intake-sub-features.tsx deleted file mode 100644 index 47816839bc6..00000000000 --- a/web/core/components/project/settings/intake-sub-features.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Button, Input, setToast, TOAST_TYPE, ToggleSwitch, Tooltip } from "@plane/ui"; -import { INTAKE_FEATURES_LIST } from "../../../../ce/constants/project/settings/features"; -import { UpgradeBadge } from "ee/components/workspace"; -import { IProject } from "@plane/types"; -import { Copy, ExternalLink, RefreshCcw } from "lucide-react"; -import { copyTextToClipboard } from "@/helpers/string.helper"; - -type Props = { - projectDetails?: IProject; - isAdmin?: boolean; - handleSubmit?: (featureKey: string, featureProperty: string) => Promise; - allowEdit?: boolean; - showDefault?: boolean; -}; -const IntakeSubFeatures = (props: Props) => { - const { projectDetails, isAdmin, handleSubmit, allowEdit = true, showDefault = true } = props; - const copyToClipboard = (text: string) => { - copyTextToClipboard(text).then(() => - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Copied to clipboard", - message: "The URL has been successfully copied to your clipboard", - }) - ); - }; - - return Object.keys(INTAKE_FEATURES_LIST) - .filter((featureKey) => featureKey !== "in-app" || showDefault) - .map((featureKey) => { - const feature = INTAKE_FEATURES_LIST[featureKey]; - return ( -
-
-
-
-
{feature.icon}
-
-
-

{feature.title}

- {feature.isPro && ( - - - - )} -
-
-
-

{feature.description}

-
- {allowEdit && handleSubmit && ( - handleSubmit(featureKey, feature.property)} - disabled={!feature.isEnabled || !isAdmin} - size="sm" - /> - )} -
- {feature.hasOptions && ( -
-
- - https://sites.plane.so/views/65f53425fe764f2589f28495fa0e8262/65f53425fe764f2589f28495fa0e8262 - - copyToClipboard("abc")} /> - {feature.hasHyperlink && ( - - - - )} -
- {allowEdit && ( - - )} -
- )} -
- ); - }); -}; - -export default IntakeSubFeatures; diff --git a/web/ee/components/projects/settings/intake/index.ts b/web/ee/components/projects/settings/intake/index.ts new file mode 100644 index 00000000000..4ffe8263896 --- /dev/null +++ b/web/ee/components/projects/settings/intake/index.ts @@ -0,0 +1 @@ +export * from "ce/components/projects/settings/intake"; From 44e7b70cd8d145fab9a536d554133bf3c0b4cf39 Mon Sep 17 00:00:00 2001 From: gakshita Date: Fri, 4 Oct 2024 19:24:30 +0530 Subject: [PATCH 3/4] fix: intake form ui --- space/app/intake/[anchor]/layout.tsx | 70 +++++++++++++ space/app/intake/[anchor]/page.tsx | 26 +++++ .../intake/create/create-issue-modal.tsx | 36 +++++++ space/core/components/intake/create/form.tsx | 96 ++++++++++++++++++ .../core/components/intake/create/success.tsx | 13 +++ space/core/components/intake/index.ts | 1 + space/public/instance/success.png | Bin 0 -> 107189 bytes 7 files changed, 242 insertions(+) create mode 100644 space/app/intake/[anchor]/layout.tsx create mode 100644 space/app/intake/[anchor]/page.tsx create mode 100644 space/core/components/intake/create/create-issue-modal.tsx create mode 100644 space/core/components/intake/create/form.tsx create mode 100644 space/core/components/intake/create/success.tsx create mode 100644 space/core/components/intake/index.ts create mode 100644 space/public/instance/success.png diff --git a/space/app/intake/[anchor]/layout.tsx b/space/app/intake/[anchor]/layout.tsx new file mode 100644 index 00000000000..5c642d1a06a --- /dev/null +++ b/space/app/intake/[anchor]/layout.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { observer } from "mobx-react"; +import Image from "next/image"; +import useSWR from "swr"; +// components +import { LogoSpinner } from "@/components/common"; +import { SomethingWentWrongError } from "@/components/issues/issue-layouts/error"; +// hooks +import { usePublish, usePublishList } from "@/hooks/store"; +// Plane web +import { ViewNavbarRoot } from "@/plane-web/components/navbar"; +import { useView } from "@/plane-web/hooks/store"; +// assets +import planeLogo from "@/public/plane-logo.svg"; + +type Props = { + children: React.ReactNode; + params: { + anchor: string; + }; +}; + +const IntakeLayout = observer((props: Props) => { + const { children, params } = props; + // params + const { anchor } = params; + // store hooks + const { fetchPublishSettings } = usePublishList(); + const { viewData, fetchViewDetails } = useView(); + const publishSettings = usePublish(anchor); + + // fetch publish settings && view details + const { error } = useSWR( + anchor ? `PUBLISHED_VIEW_SETTINGS_${anchor}` : null, + anchor + ? async () => { + const promises = []; + promises.push(fetchPublishSettings(anchor)); + promises.push(fetchViewDetails(anchor)); + await Promise.all(promises); + } + : null + ); + + if (error) return ; + + if (!publishSettings || !viewData) return ; + + return ( + + ); +}); + +export default IntakeLayout; diff --git a/space/app/intake/[anchor]/page.tsx b/space/app/intake/[anchor]/page.tsx new file mode 100644 index 00000000000..f763d68d749 --- /dev/null +++ b/space/app/intake/[anchor]/page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { observer } from "mobx-react"; +// hooks +import CreateIssueModal from "@/components/intake/create/create-issue-modal"; +import { usePublish } from "@/hooks/store"; + +type Props = { + params: { + anchor: string; + }; +}; + +const IssuesPage = observer((props: Props) => { + const { params } = props; + const { anchor } = params; + // params + + const publishSettings = usePublish(anchor); + + if (!publishSettings?.project_details) return null; + + return ; +}); + +export default IssuesPage; diff --git a/space/core/components/intake/create/create-issue-modal.tsx b/space/core/components/intake/create/create-issue-modal.tsx new file mode 100644 index 00000000000..a1f2daffc19 --- /dev/null +++ b/space/core/components/intake/create/create-issue-modal.tsx @@ -0,0 +1,36 @@ +import { FormProvider, useForm } from "react-hook-form"; +import { IProject } from "@plane/types"; +import { Card, ECardSpacing } from "@plane/ui"; +import IssueForm from "./form"; +import FormSuccess from "./success"; + +type TProps = { + project: Partial; +}; +const CreateIssueModal = ({ project }: TProps) => { + // form data + const methods = useForm(); + const { + formState: { isSubmitting, isSubmitted }, + handleSubmit, + } = methods; + if (!project) return null; + + const onSubmit = async (data) => { + console.log(data); + }; + + return ( + + {!isSubmitted && ( + +
+ + +
+ )} + {isSubmitted && } +
+ ); +}; +export default CreateIssueModal; diff --git a/space/core/components/intake/create/form.tsx b/space/core/components/intake/create/form.tsx new file mode 100644 index 00000000000..883fabc0f18 --- /dev/null +++ b/space/core/components/intake/create/form.tsx @@ -0,0 +1,96 @@ +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Controller, useFormContext } from "react-hook-form"; +import { IProject } from "@plane/types"; +import { Button, Input, TextArea } from "@plane/ui"; +import { ProjectLogo } from "@/components/common"; +import { useUser } from "@/hooks/store"; + +type TProps = { + project: Partial; + isSubmitting: boolean; +}; + +const IssueForm = ({ project, isSubmitting }: TProps) => { + const { + formState: { errors }, + control, + } = useFormContext(); + const pathName = usePathname(); + + // hooks + const { data: currentUser } = useUser(); + return ( + <> +
+
+

Create Issue

+
+ This issue will be added to the intake of the project + + {project.logo_props && } + {project.name} + +
+
+
+ ( + + )} + /> + {errors?.name?.message?.toString()} +
+
+ ( +