diff --git a/apps/web/ce/components/issues/issue-modal/additional-properties.tsx b/apps/web/ce/components/issues/issue-modal/additional-properties.tsx deleted file mode 100644 index 99ddc8830..000000000 --- a/apps/web/ce/components/issues/issue-modal/additional-properties.tsx +++ /dev/null @@ -1,9 +0,0 @@ -type TIssueAdditionalPropertiesProps = { - issueId: string | undefined; - issueTypeId: string | null; - projectId: string; - workspaceSlug: string; - isDraft?: boolean; -}; - -export const IssueAdditionalProperties: React.FC = () => <>; diff --git a/apps/web/ce/components/issues/issue-modal/index.ts b/apps/web/ce/components/issues/issue-modal/index.ts index f63453ba3..304be8c91 100644 --- a/apps/web/ce/components/issues/issue-modal/index.ts +++ b/apps/web/ce/components/issues/issue-modal/index.ts @@ -1,4 +1,3 @@ export * from "./provider"; export * from "./issue-type-select"; -export * from "./additional-properties"; export * from "./template-select"; diff --git a/apps/web/ce/components/issues/issue-modal/modal-additional-properties.tsx b/apps/web/ce/components/issues/issue-modal/modal-additional-properties.tsx new file mode 100644 index 000000000..ad35b4892 --- /dev/null +++ b/apps/web/ce/components/issues/issue-modal/modal-additional-properties.tsx @@ -0,0 +1,10 @@ +import React from "react"; + +export type TWorkItemModalAdditionalPropertiesProps = { + isDraft?: boolean; + projectId: string | null; + workItemId: string | undefined; + workspaceSlug: string; +}; + +export const WorkItemModalAdditionalProperties: React.FC = () => null; diff --git a/apps/web/ce/components/issues/issue-modal/provider.tsx b/apps/web/ce/components/issues/issue-modal/provider.tsx index b6e6b33af..0a0ef1e3f 100644 --- a/apps/web/ce/components/issues/issue-modal/provider.tsx +++ b/apps/web/ce/components/issues/issue-modal/provider.tsx @@ -44,6 +44,7 @@ export const IssueModalProvider = observer((props: TIssueModalProviderProps) => handleProjectEntitiesFetch: () => Promise.resolve(), handleTemplateChange: () => Promise.resolve(), handleConvert: () => Promise.resolve(), + handleCreateSubWorkItem: () => Promise.resolve(), }} > {children} diff --git a/apps/web/core/components/cycles/form.tsx b/apps/web/core/components/cycles/form.tsx index bf98a38a6..6b0ec32f0 100644 --- a/apps/web/core/components/cycles/form.tsx +++ b/apps/web/core/components/cycles/form.tsx @@ -83,7 +83,7 @@ export const CycleForm: React.FC = (props) => { }} multiple={false} buttonVariant="border-with-text" - renderCondition={(project) => !!projectsWithCreatePermissions?.[project.id]} + renderCondition={(projectId) => !!projectsWithCreatePermissions?.[projectId]} tabIndex={getIndex("cover_image")} /> diff --git a/apps/web/core/components/dropdowns/index.ts b/apps/web/core/components/dropdowns/index.ts index 0948f5e75..3455fa7f4 100644 --- a/apps/web/core/components/dropdowns/index.ts +++ b/apps/web/core/components/dropdowns/index.ts @@ -1,10 +1,10 @@ -export * from "./member"; +export * from "./member/dropdown"; export * from "./cycle"; export * from "./date-range"; export * from "./date"; export * from "./estimate"; export * from "./merged-date"; -export * from "./module"; +export * from "./module/dropdown"; export * from "./priority"; -export * from "./project"; -export * from "./state"; +export * from "./project/dropdown"; +export * from "./state/dropdown"; diff --git a/apps/web/core/components/dropdowns/member/index.tsx b/apps/web/core/components/dropdowns/member/base.tsx similarity index 91% rename from apps/web/core/components/dropdowns/member/index.tsx rename to apps/web/core/components/dropdowns/member/base.tsx index acfa9f460..ef17e9482 100644 --- a/apps/web/core/components/dropdowns/member/index.tsx +++ b/apps/web/core/components/dropdowns/member/base.tsx @@ -1,33 +1,32 @@ import { useRef, useState } from "react"; import { observer } from "mobx-react"; import { ChevronDown, LucideIcon } from "lucide-react"; +// plane imports import { useTranslation } from "@plane/i18n"; -// ui +import { IUserLite } from "@plane/types"; import { ComboDropDown } from "@plane/ui"; // helpers import { cn } from "@plane/utils"; // hooks -import { useMember } from "@/hooks/store"; import { useDropdown } from "@/hooks/use-dropdown"; -// components +// local imports import { DropdownButton } from "../buttons"; import { BUTTON_VARIANTS_WITH_TEXT } from "../constants"; import { ButtonAvatars } from "./avatar"; -// constants import { MemberOptions } from "./member-options"; -// types import { MemberDropdownProps } from "./types"; -type Props = { - projectId?: string; +type TMemberDropdownBaseProps = { + getUserDetails: (userId: string) => IUserLite | undefined; icon?: LucideIcon; - onClose?: () => void; - renderByDefault?: boolean; - optionsClassName?: string; memberIds?: string[]; + onClose?: () => void; + onDropdownOpen?: () => void; + optionsClassName?: string; + renderByDefault?: boolean; } & MemberDropdownProps; -export const MemberDropdown: React.FC = observer((props) => { +export const MemberDropdownBase: React.FC = observer((props) => { const { t } = useTranslation(); const { button, @@ -38,39 +37,37 @@ export const MemberDropdown: React.FC = observer((props) => { disabled = false, dropdownArrow = false, dropdownArrowClassName = "", - optionsClassName = "", + getUserDetails, hideIcon = false, + icon, + memberIds, multiple, onChange, onClose, + onDropdownOpen, + optionsClassName = "", placeholder = t("members"), - tooltipContent, placement, - projectId, + renderByDefault = true, showTooltip = false, showUserDetails = false, tabIndex, + tooltipContent, value, - icon, - renderByDefault = true, - memberIds, } = props; - // states - const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); + // states + const [isOpen, setIsOpen] = useState(false); - const { getUserDetails } = useMember(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const comboboxProps: any = { + const comboboxProps = { value, onChange, disabled, + multiple, }; - if (multiple) comboboxProps.multiple = true; const { handleClose, handleKeyDown, handleOnClick } = useDropdown({ dropdownRef, @@ -163,19 +160,20 @@ export const MemberDropdown: React.FC = observer((props) => { {isOpen && ( diff --git a/apps/web/core/components/dropdowns/member/dropdown.tsx b/apps/web/core/components/dropdowns/member/dropdown.tsx new file mode 100644 index 000000000..e9f42a50e --- /dev/null +++ b/apps/web/core/components/dropdowns/member/dropdown.tsx @@ -0,0 +1,48 @@ +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { LucideIcon } from "lucide-react"; +// hooks +import { useMember } from "@/hooks/store"; +// local imports +import { MemberDropdownBase } from "./base"; +import { MemberDropdownProps } from "./types"; + +type TMemberDropdownProps = { + icon?: LucideIcon; + memberIds?: string[]; + onClose?: () => void; + optionsClassName?: string; + projectId?: string; + renderByDefault?: boolean; +} & MemberDropdownProps; + +export const MemberDropdown: React.FC = observer((props) => { + const { memberIds: propsMemberIds, projectId } = props; + // router params + const { workspaceSlug } = useParams(); + // store hooks + const { + getUserDetails, + project: { getProjectMemberIds, fetchProjectMembers }, + workspace: { workspaceMemberIds }, + } = useMember(); + + const memberIds = propsMemberIds + ? propsMemberIds + : projectId + ? getProjectMemberIds(projectId, false) + : workspaceMemberIds; + + const onDropdownOpen = () => { + if (!memberIds && projectId && workspaceSlug) fetchProjectMembers(workspaceSlug.toString(), projectId); + }; + + return ( + + ); +}); diff --git a/apps/web/core/components/dropdowns/member/member-options.tsx b/apps/web/core/components/dropdowns/member/member-options.tsx index 189778d46..a251efe8c 100644 --- a/apps/web/core/components/dropdowns/member/member-options.tsx +++ b/apps/web/core/components/dropdowns/member/member-options.tsx @@ -3,46 +3,48 @@ import { useEffect, useRef, useState } from "react"; import { Placement } from "@popperjs/core"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { Check, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; -import { EUserPermissions } from "@plane/constants"; +// plane imports import { useTranslation } from "@plane/i18n"; -// plane ui import { Avatar } from "@plane/ui"; import { cn, getFileURL } from "@plane/utils"; -// helpers // hooks -import { useUser, useMember } from "@/hooks/store"; +import { useUser } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; +import { IUserLite } from "@plane/types"; interface Props { - memberIds?: string[]; className?: string; - optionsClassName?: string; - projectId?: string; - referenceElement: HTMLButtonElement | null; - placement: Placement | undefined; + getUserDetails: (userId: string) => IUserLite | undefined; isOpen: boolean; + memberIds?: string[]; + onDropdownOpen?: () => void; + optionsClassName?: string; + placement: Placement | undefined; + referenceElement: HTMLButtonElement | null; } export const MemberOptions: React.FC = observer((props: Props) => { - const { memberIds: propsMemberIds, projectId, referenceElement, placement, isOpen, optionsClassName = "" } = props; + const { + getUserDetails, + isOpen, + memberIds, + onDropdownOpen, + optionsClassName = "", + placement, + referenceElement, + } = props; + // refs + const inputRef = useRef(null); // states const [query, setQuery] = useState(""); const [popperElement, setPopperElement] = useState(null); - // refs - const inputRef = useRef(null); - // store hooks + // plane hooks const { t } = useTranslation(); - const { workspaceSlug } = useParams(); - const { - getUserDetails, - project: { getProjectMemberIds, fetchProjectMembers, getProjectMemberDetails }, - workspace: { workspaceMemberIds }, - } = useMember(); + // store hooks const { data: currentUser } = useUser(); const { isMobile } = usePlatformOS(); // popper-js init @@ -60,22 +62,13 @@ export const MemberOptions: React.FC = observer((props: Props) => { useEffect(() => { if (isOpen) { - onOpen(); + onDropdownOpen?.(); if (!isMobile) { inputRef.current && inputRef.current.focus(); } } }, [isOpen, isMobile]); - const memberIds = propsMemberIds - ? propsMemberIds - : projectId - ? getProjectMemberIds(projectId, true) - : workspaceMemberIds; - const onOpen = () => { - if (!memberIds && workspaceSlug && projectId) fetchProjectMembers(workspaceSlug.toString(), projectId); - }; - const searchInputKeyDown = (e: React.KeyboardEvent) => { if (query !== "" && e.key === "Escape") { e.stopPropagation(); @@ -86,12 +79,6 @@ export const MemberOptions: React.FC = observer((props: Props) => { const options = memberIds ?.map((userId) => { const userDetails = getUserDetails(userId); - if (projectId) { - const role = getProjectMemberDetails(userId, projectId)?.role; - const isGuest = role === EUserPermissions.GUEST; - if (isGuest) return; - } - return { value: userId, query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`, diff --git a/apps/web/core/components/dropdowns/module/index.tsx b/apps/web/core/components/dropdowns/module/base.tsx similarity index 50% rename from apps/web/core/components/dropdowns/module/index.tsx rename to apps/web/core/components/dropdowns/module/base.tsx index 9e789dbf4..5e0965f9a 100644 --- a/apps/web/core/components/dropdowns/module/index.tsx +++ b/apps/web/core/components/dropdowns/module/base.tsx @@ -2,34 +2,33 @@ import { ReactNode, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; -import { ChevronDown, X } from "lucide-react"; -// i18n +// plane imports import { useTranslation } from "@plane/i18n"; -// ui -import { ComboDropDown, DiceIcon, Tooltip } from "@plane/ui"; -// helpers +import { IModule } from "@plane/types"; +import { ComboDropDown } from "@plane/ui"; import { cn } from "@plane/utils"; // hooks -import { useModule } from "@/hooks/store"; import { useDropdown } from "@/hooks/use-dropdown"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// components +// local imports import { DropdownButton } from "../buttons"; import { BUTTON_VARIANTS_WITHOUT_TEXT } from "../constants"; -// types import { TDropdownProps } from "../types"; -// constants +import { ModuleButtonContent } from "./button-content"; import { ModuleOptions } from "./module-options"; -type Props = TDropdownProps & { +type TModuleDropdownBaseProps = TDropdownProps & { button?: ReactNode; dropdownArrow?: boolean; dropdownArrowClassName?: string; - projectId: string | undefined; - showCount?: boolean; - onClose?: () => void; - renderByDefault?: boolean; + getModuleById: (moduleId: string) => IModule | null; itemClassName?: string; + moduleIds?: string[]; + onClose?: () => void; + onDropdownOpen?: () => void; + projectId: string | undefined; + renderByDefault?: boolean; + showCount?: boolean; } & ( | { multiple: false; @@ -43,149 +42,31 @@ type Props = TDropdownProps & { } ); -type ButtonContentProps = { - disabled: boolean; - dropdownArrow: boolean; - dropdownArrowClassName: string; - hideIcon: boolean; - hideText: boolean; - onChange: (moduleIds: string[]) => void; - placeholder?: string; - showCount: boolean; - showTooltip?: boolean; - value: string | string[] | null; - className?: string; -}; - -const ButtonContent: React.FC = (props) => { - const { - disabled, - dropdownArrow, - dropdownArrowClassName, - hideIcon, - hideText, - onChange, - placeholder, - showCount, - showTooltip = false, - value, - className, - } = props; - // store hooks - const { getModuleById } = useModule(); - const { isMobile } = usePlatformOS(); - - if (Array.isArray(value)) - return ( - <> - {showCount ? ( -
- {!hideIcon && } - {(value.length > 0 || !!placeholder) && ( -
- {value.length > 0 - ? value.length === 1 - ? `${getModuleById(value[0])?.name || "module"}` - : `${value.length} Module${value.length === 1 ? "" : "s"}` - : placeholder} -
- )} -
- ) : value.length > 0 ? ( -
- {value.map((moduleId) => { - const moduleDetails = getModuleById(moduleId); - return ( -
- {!hideIcon && } - {!hideText && ( - - {moduleDetails?.name} - - )} - {!disabled && ( - - - - )} -
- ); - })} -
- ) : ( - <> - {!hideIcon && } - {placeholder} - - )} - {dropdownArrow && ( -
diff --git a/apps/web/core/components/dropdowns/module/button-content.tsx b/apps/web/core/components/dropdowns/module/button-content.tsx new file mode 100644 index 000000000..3fa08ca19 --- /dev/null +++ b/apps/web/core/components/dropdowns/module/button-content.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { ChevronDown, X } from "lucide-react"; +// plane imports +import { DiceIcon, Tooltip } from "@plane/ui"; +import { cn } from "@plane/utils"; +// hooks +import { useModule } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; + +type ModuleButtonContentProps = { + disabled: boolean; + dropdownArrow: boolean; + dropdownArrowClassName: string; + hideIcon: boolean; + hideText: boolean; + onChange: (moduleIds: string[]) => void; + placeholder?: string; + showCount: boolean; + showTooltip?: boolean; + value: string | string[] | null; + className?: string; +}; + +export const ModuleButtonContent: React.FC = (props) => { + const { + disabled, + dropdownArrow, + dropdownArrowClassName, + hideIcon, + hideText, + onChange, + placeholder, + showCount, + showTooltip = false, + value, + className, + } = props; + // store hooks + const { getModuleById } = useModule(); + const { isMobile } = usePlatformOS(); + + if (Array.isArray(value)) + return ( + <> + {showCount ? ( +
+ {!hideIcon && } + {(value.length > 0 || !!placeholder) && ( +
+ {value.length > 0 + ? value.length === 1 + ? `${getModuleById(value[0])?.name || "module"}` + : `${value.length} Module${value.length === 1 ? "" : "s"}` + : placeholder} +
+ )} +
+ ) : value.length > 0 ? ( +
+ {value.map((moduleId) => { + const moduleDetails = getModuleById(moduleId); + return ( +
+ {!hideIcon && } + {!hideText && ( + + {moduleDetails?.name} + + )} + {!disabled && ( + + + + )} +
+ ); + })} +
+ ) : ( + <> + {!hideIcon && } + {placeholder} + + )} + {dropdownArrow && ( +