diff --git a/apps/web/ce/components/common/quick-actions-factory.tsx b/apps/web/ce/components/common/quick-actions-factory.tsx new file mode 100644 index 00000000000..a59a61e5332 --- /dev/null +++ b/apps/web/ce/components/common/quick-actions-factory.tsx @@ -0,0 +1 @@ +export { useQuickActionsFactory } from "@/components/common/quick-actions-factory"; diff --git a/apps/web/ce/components/cycles/end-cycle/index.ts b/apps/web/ce/components/cycles/end-cycle/index.ts index 2e60c456193..031608e25ff 100644 --- a/apps/web/ce/components/cycles/end-cycle/index.ts +++ b/apps/web/ce/components/cycles/end-cycle/index.ts @@ -1,2 +1 @@ export * from "./modal"; -export * from "./use-end-cycle"; diff --git a/apps/web/ce/components/cycles/end-cycle/use-end-cycle.tsx b/apps/web/ce/components/cycles/end-cycle/use-end-cycle.tsx deleted file mode 100644 index c1bf6261855..00000000000 --- a/apps/web/ce/components/cycles/end-cycle/use-end-cycle.tsx +++ /dev/null @@ -1,7 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const useEndCycle = (isCurrentCycle: boolean) => ({ - isEndCycleModalOpen: false, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setEndCycleModalOpen: (value: boolean) => {}, - endCycleContextMenu: undefined, -}); diff --git a/apps/web/ce/components/views/helper.tsx b/apps/web/ce/components/views/helper.tsx index d2932ddac6d..1063bfcec4d 100644 --- a/apps/web/ce/components/views/helper.tsx +++ b/apps/web/ce/components/views/helper.tsx @@ -1,7 +1,4 @@ -import { ExternalLink, Link, Pencil, Trash2 } from "lucide-react"; -import { useTranslation } from "@plane/i18n"; import type { EIssueLayoutTypes, IProjectView } from "@plane/types"; -import type { TContextMenuItem } from "@plane/ui"; import type { TWorkspaceLayoutProps } from "@/components/views/helper"; export type TLayoutSelectionProps = { @@ -14,67 +11,5 @@ export const GlobalViewLayoutSelection = (props: TLayoutSelectionProps) => <> export const WorkspaceAdditionalLayouts = (props: TWorkspaceLayoutProps) => <>; -export type TMenuItemsFactoryProps = { - isOwner: boolean; - isAdmin: boolean; - setDeleteViewModal: (open: boolean) => void; - setCreateUpdateViewModal: (open: boolean) => void; - handleOpenInNewTab: () => void; - handleCopyText: () => void; - isLocked: boolean; - workspaceSlug: string; - projectId?: string; - viewId: string; -}; - -export const useMenuItemsFactory = (props: TMenuItemsFactoryProps) => { - const { isOwner, isAdmin, setDeleteViewModal, setCreateUpdateViewModal, handleOpenInNewTab, handleCopyText } = props; - - const { t } = useTranslation(); - - const editMenuItem = () => ({ - key: "edit", - action: () => setCreateUpdateViewModal(true), - title: t("edit"), - icon: Pencil, - shouldRender: isOwner, - }); - - const openInNewTabMenuItem = () => ({ - key: "open-new-tab", - action: handleOpenInNewTab, - title: t("open_in_new_tab"), - icon: ExternalLink, - }); - - const copyLinkMenuItem = () => ({ - key: "copy-link", - action: handleCopyText, - title: t("copy_link"), - icon: Link, - }); - - const deleteMenuItem = () => ({ - key: "delete", - action: () => setDeleteViewModal(true), - title: t("delete"), - icon: Trash2, - shouldRender: isOwner || isAdmin, - }); - - return { - editMenuItem, - openInNewTabMenuItem, - copyLinkMenuItem, - deleteMenuItem, - }; -}; - -export const useViewMenuItems = (props: TMenuItemsFactoryProps): TContextMenuItem[] => { - const factory = useMenuItemsFactory(props); - - return [factory.editMenuItem(), factory.openInNewTabMenuItem(), factory.copyLinkMenuItem(), factory.deleteMenuItem()]; -}; - // eslint-disable-next-line @typescript-eslint/no-unused-vars export const AdditionalHeaderItems = (view: IProjectView) => <>; diff --git a/apps/web/core/components/common/quick-actions-factory.tsx b/apps/web/core/components/common/quick-actions-factory.tsx new file mode 100644 index 00000000000..52e6bfbd30c --- /dev/null +++ b/apps/web/core/components/common/quick-actions-factory.tsx @@ -0,0 +1,82 @@ +import { Pencil, ExternalLink, Link, Trash2, ArchiveRestoreIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; +import { ArchiveIcon } from "@plane/propel/icons"; +import type { TContextMenuItem } from "@plane/ui"; + +/** + * Unified factory for creating menu items across all entities (cycles, modules, views, epics) + */ +export const useQuickActionsFactory = () => { + const { t } = useTranslation(); + + return { + // Common menu items + createEditMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({ + key: "edit", + title: t("edit"), + icon: Pencil, + action: handler, + shouldRender, + }), + + createOpenInNewTabMenuItem: (handler: () => void): TContextMenuItem => ({ + key: "open-new-tab", + title: t("open_in_new_tab"), + icon: ExternalLink, + action: handler, + }), + + createCopyLinkMenuItem: (handler: () => void): TContextMenuItem => ({ + key: "copy-link", + title: t("copy_link"), + icon: Link, + action: handler, + }), + + createArchiveMenuItem: ( + handler: () => void, + opts: { shouldRender?: boolean; disabled?: boolean; description?: string } + ): TContextMenuItem => ({ + key: "archive", + title: t("archive"), + icon: ArchiveIcon, + action: handler, + className: "items-start", + iconClassName: "mt-1", + description: opts.description, + disabled: opts.disabled, + shouldRender: opts.shouldRender, + }), + + createRestoreMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({ + key: "restore", + title: t("restore"), + icon: ArchiveRestoreIcon, + action: handler, + shouldRender, + }), + + createDeleteMenuItem: (handler: () => void, shouldRender: boolean = true): TContextMenuItem => ({ + key: "delete", + title: t("delete"), + icon: Trash2, + action: handler, + shouldRender, + }), + + // Layout-level actions (for work item list views) + createOpenInNewTab: (handler: () => void): TContextMenuItem => ({ + key: "open-in-new-tab", + title: "Open in new tab", + icon: ExternalLink, + action: handler, + }), + + createCopyLayoutLinkMenuItem: (handler: () => void): TContextMenuItem => ({ + key: "copy-link", + title: "Copy link", + icon: Link, + action: handler, + }), + }; +}; diff --git a/apps/web/core/components/common/quick-actions-helper.tsx b/apps/web/core/components/common/quick-actions-helper.tsx new file mode 100644 index 00000000000..eadb1a7062d --- /dev/null +++ b/apps/web/core/components/common/quick-actions-helper.tsx @@ -0,0 +1,145 @@ +// types +import type { ICycle, IModule, IProjectView, IWorkspaceView } from "@plane/types"; +import type { TContextMenuItem } from "@plane/ui"; +// hooks +import { useQuickActionsFactory } from "@/plane-web/components/common/quick-actions-factory"; + +// Types +interface UseCycleMenuItemsProps { + cycleDetails: ICycle | undefined; + isEditingAllowed: boolean; + workspaceSlug: string; + projectId: string; + cycleId: string; + handleEdit: () => void; + handleArchive: () => void; + handleRestore: () => void; + handleDelete: () => void; + handleCopyLink: () => void; + handleOpenInNewTab: () => void; +} + +interface UseModuleMenuItemsProps { + moduleDetails: IModule | undefined; + isEditingAllowed: boolean; + workspaceSlug: string; + projectId: string; + moduleId: string; + handleEdit: () => void; + handleArchive: () => void; + handleRestore: () => void; + handleDelete: () => void; + handleCopyLink: () => void; + handleOpenInNewTab: () => void; +} + +interface UseViewMenuItemsProps { + isOwner: boolean; + isAdmin: boolean; + workspaceSlug: string; + projectId?: string; + view: IProjectView | IWorkspaceView; + handleEdit: () => void; + handleDelete: () => void; + handleCopyLink: () => void; + handleOpenInNewTab: () => void; +} + +interface UseLayoutMenuItemsProps { + workspaceSlug: string; + projectId: string; + storeType: "PROJECT" | "EPIC"; + handleCopyLink: () => void; + handleOpenInNewTab: () => void; +} + +type MenuResult = { + items: TContextMenuItem[]; + modals: JSX.Element | null; +}; + +export const useCycleMenuItems = (props: UseCycleMenuItemsProps): MenuResult => { + const factory = useQuickActionsFactory(); + const { cycleDetails, isEditingAllowed, ...handlers } = props; + + const isArchived = !!cycleDetails?.archived_at; + const isCompleted = cycleDetails?.status?.toLowerCase() === "completed"; + + // Assemble final menu items - order defined here + const items = [ + factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isCompleted && !isArchived), + factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab), + factory.createCopyLinkMenuItem(handlers.handleCopyLink), + factory.createArchiveMenuItem(handlers.handleArchive, { + shouldRender: isEditingAllowed && !isArchived, + disabled: !isCompleted, + description: isCompleted ? undefined : "Only completed cycles can be archived", + }), + factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived), + factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isCompleted && !isArchived), + ].filter((item) => item.shouldRender !== false); + + return { items, modals: null }; +}; + +export const useModuleMenuItems = (props: UseModuleMenuItemsProps): MenuResult => { + const factory = useQuickActionsFactory(); + const { moduleDetails, isEditingAllowed, ...handlers } = props; + + const isArchived = !!moduleDetails?.archived_at; + const moduleState = moduleDetails?.status?.toLocaleLowerCase(); + const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState); + + // Assemble final menu items - order defined here + const items = [ + factory.createEditMenuItem(handlers.handleEdit, isEditingAllowed && !isArchived), + factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab), + factory.createCopyLinkMenuItem(handlers.handleCopyLink), + factory.createArchiveMenuItem(handlers.handleArchive, { + shouldRender: isEditingAllowed && !isArchived, + disabled: !isInArchivableGroup, + description: isInArchivableGroup ? undefined : "Only completed or cancelled modules can be archived", + }), + factory.createRestoreMenuItem(handlers.handleRestore, isEditingAllowed && isArchived), + factory.createDeleteMenuItem(handlers.handleDelete, isEditingAllowed && !isArchived), + ].filter((item) => item.shouldRender !== false); + + return { items, modals: null }; +}; + +export const useViewMenuItems = (props: UseViewMenuItemsProps): MenuResult => { + const factory = useQuickActionsFactory(); + const { workspaceSlug, isOwner, isAdmin, projectId, view, ...handlers } = props; + + if (!view) return { items: [], modals: null }; + + // Assemble final menu items - order defined here + const items = [ + factory.createEditMenuItem(handlers.handleEdit, isOwner), + factory.createOpenInNewTabMenuItem(handlers.handleOpenInNewTab), + factory.createCopyLinkMenuItem(handlers.handleCopyLink), + factory.createDeleteMenuItem(handlers.handleDelete, isOwner || isAdmin), + ].filter((item) => item.shouldRender !== false); + + return { items, modals: null }; +}; + +export const useLayoutMenuItems = (props: UseLayoutMenuItemsProps): MenuResult => { + const factory = useQuickActionsFactory(); + const { ...handlers } = props; + + // Assemble final menu items - order defined here + const items = [ + factory.createOpenInNewTab(handlers.handleOpenInNewTab), + factory.createCopyLayoutLinkMenuItem(handlers.handleCopyLink), + ].filter((item) => item.shouldRender !== false); + + return { items, modals: null }; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const useIntakeHeaderMenuItems = (props: { + workspaceSlug: string; + projectId: string; + handleCopyLink: () => void; +}): MenuResult => ({ items: [], modals: null }); diff --git a/apps/web/core/components/cycles/quick-actions.tsx b/apps/web/core/components/cycles/quick-actions.tsx index 242e91eca18..451f287c33a 100644 --- a/apps/web/core/components/cycles/quick-actions.tsx +++ b/apps/web/core/components/cycles/quick-actions.tsx @@ -3,8 +3,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -// icons -import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react"; // ui import { CYCLE_TRACKER_EVENTS, @@ -13,18 +11,17 @@ import { CYCLE_TRACKER_ELEMENTS, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { ArchiveIcon } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { TContextMenuItem } from "@plane/ui"; import { ContextMenu, CustomMenu } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // helpers // hooks +import { useCycleMenuItems } from "@/components/common/quick-actions-helper"; import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useCycle } from "@/hooks/store/use-cycle"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useEndCycle, EndCycleModal } from "@/plane-web/components/cycles"; // local imports import { ArchiveCycleModal } from "./archived-cycles/modal"; import { CycleDeleteModal } from "./delete-modal"; @@ -52,12 +49,6 @@ export const CycleQuickActions: React.FC = observer((props) => { const { t } = useTranslation(); // derived values const cycleDetails = getCycleById(cycleId); - const isArchived = !!cycleDetails?.archived_at; - const isCompleted = cycleDetails?.status?.toLowerCase() === "completed"; - const isCurrentCycle = cycleDetails?.status?.toLowerCase() === "current"; - const transferrableIssuesCount = cycleDetails - ? cycleDetails.total_issues - (cycleDetails.cancelled_issues + cycleDetails.completed_issues) - : 0; // auth const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -66,8 +57,6 @@ export const CycleQuickActions: React.FC = observer((props) => { projectId ); - const { isEndCycleModalOpen, setEndCycleModalOpen, endCycleContextMenu } = useEndCycle(isCurrentCycle); - const cycleLink = `${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`; const handleCopyText = () => copyUrlToClipboard(cycleLink).then(() => { @@ -79,12 +68,6 @@ export const CycleQuickActions: React.FC = observer((props) => { }); const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank"); - const handleEditCycle = () => { - setUpdateModal(true); - }; - - const handleArchiveCycle = () => setArchiveCycleModal(true); - const handleRestoreCycle = async () => await restoreCycle(workspaceSlug, projectId, cycleId) .then(() => { @@ -115,60 +98,22 @@ export const CycleQuickActions: React.FC = observer((props) => { }); }); - const handleDeleteCycle = () => { - setDeleteModal(true); - }; - - const MENU_ITEMS: TContextMenuItem[] = [ - { - key: "edit", - title: t("edit"), - icon: Pencil, - action: handleEditCycle, - shouldRender: isEditingAllowed && !isCompleted && !isArchived, - }, - { - key: "open-new-tab", - action: handleOpenInNewTab, - title: t("open_in_new_tab"), - icon: ExternalLink, - shouldRender: !isArchived, - }, - { - key: "copy-link", - action: handleCopyText, - title: t("copy_link"), - icon: LinkIcon, - shouldRender: !isArchived, - }, - { - key: "archive", - action: handleArchiveCycle, - title: t("archive"), - description: isCompleted ? undefined : t("project_cycles.only_completed_cycles_can_be_archived"), - icon: ArchiveIcon, - className: "items-start", - iconClassName: "mt-1", - shouldRender: isEditingAllowed && !isArchived, - disabled: !isCompleted, - }, - { - key: "restore", - action: handleRestoreCycle, - title: t("restore"), - icon: ArchiveRestoreIcon, - shouldRender: isEditingAllowed && isArchived, - }, - { - key: "delete", - action: handleDeleteCycle, - title: t("delete"), - icon: Trash2, - shouldRender: isEditingAllowed && !isCompleted && !isArchived, - }, - ]; + const menuResult = useCycleMenuItems({ + cycleDetails: cycleDetails ?? undefined, + workspaceSlug, + projectId, + cycleId, + isEditingAllowed, + handleEdit: () => setUpdateModal(true), + handleArchive: () => setArchiveCycleModal(true), + handleRestore: handleRestoreCycle, + handleDelete: () => setDeleteModal(true), + handleCopyLink: handleCopyText, + handleOpenInNewTab, + }); - if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu); + const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items; + const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals; const CONTEXT_MENU_ITEMS = MENU_ITEMS.map((item) => ({ ...item, @@ -205,17 +150,7 @@ export const CycleQuickActions: React.FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} /> - {isCurrentCycle && ( - setEndCycleModalOpen(false)} - cycleId={cycleId} - projectId={projectId} - workspaceSlug={workspaceSlug} - transferrableIssuesCount={transferrableIssuesCount} - cycleName={cycleDetails.name} - /> - )} + {additionalModals} )} diff --git a/apps/web/core/components/issues/layout-quick-actions.tsx b/apps/web/core/components/issues/layout-quick-actions.tsx new file mode 100644 index 00000000000..ab3d198dc62 --- /dev/null +++ b/apps/web/core/components/issues/layout-quick-actions.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { observer } from "mobx-react"; +import { TOAST_TYPE, setToast } from "@plane/propel/toast"; +import type { TContextMenuItem } from "@plane/ui"; +import { CustomMenu } from "@plane/ui"; +import { copyUrlToClipboard, cn } from "@plane/utils"; +import { useLayoutMenuItems } from "@/components/common/quick-actions-helper"; + +type Props = { + workspaceSlug: string; + projectId: string; + storeType: "PROJECT" | "EPIC"; +}; + +export const LayoutQuickActions: React.FC = observer((props) => { + const { workspaceSlug, projectId, storeType } = props; + + const layoutLink = `${workspaceSlug}/projects/${projectId}/${storeType === "EPIC" ? "epics" : "issues"}`; + + const handleCopyLink = () => + copyUrlToClipboard(layoutLink).then(() => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Link copied", + message: `${storeType === "EPIC" ? "Epics" : "Work items"} link copied to clipboard.`, + }); + }); + + const handleOpenInNewTab = () => window.open(`/${layoutLink}`, "_blank"); + + const menuResult = useLayoutMenuItems({ + workspaceSlug, + projectId, + storeType, + handleCopyLink, + handleOpenInNewTab, + }); + + const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items; + const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals; + + return ( + <> + {additionalModals} + + {MENU_ITEMS.map((item) => { + if (item.shouldRender === false) return null; + return ( + + {item.icon && } + {item.title} + + ); + })} + + + ); +}); diff --git a/apps/web/core/components/modules/quick-actions.tsx b/apps/web/core/components/modules/quick-actions.tsx index cc7c470a5db..7648af4a5fa 100644 --- a/apps/web/core/components/modules/quick-actions.tsx +++ b/apps/web/core/components/modules/quick-actions.tsx @@ -3,8 +3,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -// icons -import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react"; // plane imports import { EUserPermissions, @@ -13,13 +11,12 @@ import { MODULE_TRACKER_EVENTS, } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -// ui -import { ArchiveIcon } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { TContextMenuItem } from "@plane/ui"; import { ContextMenu, CustomMenu } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // components +import { useModuleMenuItems } from "@/components/common/quick-actions-helper"; import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules"; // helpers import { captureClick, captureSuccess, captureError } from "@/helpers/event-tracker.helper"; @@ -52,7 +49,6 @@ export const ModuleQuickActions: React.FC = observer((props) => { const { t } = useTranslation(); // derived values const moduleDetails = getModuleById(moduleId); - const isArchived = !!moduleDetails?.archived_at; // auth const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -61,9 +57,6 @@ export const ModuleQuickActions: React.FC = observer((props) => { projectId ); - const moduleState = moduleDetails?.status?.toLocaleLowerCase(); - const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState); - const moduleLink = `${workspaceSlug}/projects/${projectId}/modules/${moduleId}`; const handleCopyText = () => copyUrlToClipboard(moduleLink).then(() => { @@ -75,12 +68,6 @@ export const ModuleQuickActions: React.FC = observer((props) => { }); const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank"); - const handleEditModule = () => { - setEditModal(true); - }; - - const handleArchiveModule = () => setArchiveModuleModal(true); - const handleRestoreModule = async () => await restoreModule(workspaceSlug, projectId, moduleId) .then(() => { @@ -108,62 +95,28 @@ export const ModuleQuickActions: React.FC = observer((props) => { }); }); - const handleDeleteModule = () => { - setDeleteModal(true); - }; + // Use unified menu hook from plane-web (resolves to CE or EE) + const menuResult = useModuleMenuItems({ + moduleDetails: moduleDetails ?? undefined, + workspaceSlug, + projectId, + moduleId, + isEditingAllowed, + handleEdit: () => setEditModal(true), + handleArchive: () => setArchiveModuleModal(true), + handleRestore: handleRestoreModule, + handleDelete: () => setDeleteModal(true), + handleCopyLink: handleCopyText, + handleOpenInNewTab, + }); - const MENU_ITEMS: TContextMenuItem[] = [ - { - key: "edit", - title: t("edit"), - icon: Pencil, - action: handleEditModule, - shouldRender: isEditingAllowed && !isArchived, - }, - { - key: "open-new-tab", - action: handleOpenInNewTab, - title: t("open_in_new_tab"), - icon: ExternalLink, - shouldRender: !isArchived, - }, - { - key: "copy-link", - action: handleCopyText, - title: t("copy_link"), - icon: LinkIcon, - shouldRender: !isArchived, - }, - { - key: "archive", - action: handleArchiveModule, - title: t("archive"), - description: isInArchivableGroup ? undefined : t("project_module.quick_actions.archive_module_description"), - icon: ArchiveIcon, - className: "items-start", - iconClassName: "mt-1", - shouldRender: isEditingAllowed && !isArchived, - disabled: !isInArchivableGroup, - }, - { - key: "restore", - action: handleRestoreModule, - title: t("restore"), - icon: ArchiveRestoreIcon, - shouldRender: isEditingAllowed && isArchived, - }, - { - key: "delete", - action: handleDeleteModule, - title: t("delete"), - icon: Trash2, - shouldRender: isEditingAllowed, - }, - ]; + // Handle both CE (array) and EE (object) return types + const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items; + const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals; const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({ ...item, - onClick: () => { + action: () => { captureClick({ elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU, }); @@ -190,6 +143,7 @@ export const ModuleQuickActions: React.FC = observer((props) => { handleClose={() => setArchiveModuleModal(false)} /> setDeleteModal(false)} /> + {additionalModals} )} diff --git a/apps/web/core/components/views/quick-actions.tsx b/apps/web/core/components/views/quick-actions.tsx index 96cf8b4c34f..bc7d257e327 100644 --- a/apps/web/core/components/views/quick-actions.tsx +++ b/apps/web/core/components/views/quick-actions.tsx @@ -11,10 +11,10 @@ import type { TContextMenuItem } from "@plane/ui"; import { ContextMenu, CustomMenu } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // helpers +import { useViewMenuItems } from "@/components/common/quick-actions-helper"; import { captureClick } from "@/helpers/event-tracker.helper"; // hooks import { useUser, useUserPermissions } from "@/hooks/store/user"; -import { useViewMenuItems } from "@/plane-web/components/views/helper"; import { PublishViewModal, useViewPublish } from "@/plane-web/components/views/publish"; // local imports import { DeleteProjectViewModal } from "./delete-view-modal"; @@ -56,19 +56,22 @@ export const ViewQuickActions: React.FC = observer((props) => { }); const handleOpenInNewTab = () => window.open(`/${viewLink}`, "_blank"); - const MENU_ITEMS: TContextMenuItem[] = useViewMenuItems({ + const menuResult = useViewMenuItems({ isOwner, isAdmin, - setDeleteViewModal, - setCreateUpdateViewModal, - handleOpenInNewTab, - handleCopyText, - isLocked: view.is_locked, workspaceSlug, projectId, - viewId: view.id, + view, + handleEdit: () => setCreateUpdateViewModal(true), + handleDelete: () => setDeleteViewModal(true), + handleCopyLink: handleCopyText, + handleOpenInNewTab, }); + // Handle both CE (array) and EE (object) return types + const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items; + const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals; + if (publishContextMenu) MENU_ITEMS.splice(2, 0, publishContextMenu); const CONTEXT_MENU_ITEMS = MENU_ITEMS.map((item) => ({ @@ -90,6 +93,7 @@ export const ViewQuickActions: React.FC = observer((props) => { /> setDeleteViewModal(false)} /> setPublishModalOpen(false)} view={view} /> + {additionalModals} {MENU_ITEMS.map((item) => { diff --git a/apps/web/core/components/workspace/views/quick-action.tsx b/apps/web/core/components/workspace/views/quick-action.tsx index 7998e7255ab..0427306571a 100644 --- a/apps/web/core/components/workspace/views/quick-action.tsx +++ b/apps/web/core/components/workspace/views/quick-action.tsx @@ -6,14 +6,13 @@ import { observer } from "mobx-react"; import { EUserPermissions, EUserPermissionsLevel, GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IWorkspaceView } from "@plane/types"; -import type { TContextMenuItem } from "@plane/ui"; import { CustomMenu } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // helpers +import { useViewMenuItems } from "@/components/common/quick-actions-helper"; import { captureClick } from "@/helpers/event-tracker.helper"; // hooks import { useUser, useUserPermissions } from "@/hooks/store/user"; -import { useViewMenuItems } from "@/plane-web/components/views/helper"; // local imports import { DeleteGlobalViewModal } from "./delete-view-modal"; import { CreateUpdateWorkspaceViewModal } from "./modal"; @@ -46,16 +45,15 @@ export const WorkspaceViewQuickActions: React.FC = observer((props) => { }); const handleOpenInNewTab = () => window.open(`/${viewLink}`, "_blank"); - const MENU_ITEMS: TContextMenuItem[] = useViewMenuItems({ + const MENU_ITEMS = useViewMenuItems({ isOwner, isAdmin, - setDeleteViewModal, - setCreateUpdateViewModal: setUpdateViewModal, + handleDelete: () => setDeleteViewModal(true), + handleEdit: () => setUpdateViewModal(true), handleOpenInNewTab, - handleCopyText, - isLocked: view.is_locked, + handleCopyLink: handleCopyText, workspaceSlug, - viewId: view.id, + view, }); return ( @@ -68,7 +66,7 @@ export const WorkspaceViewQuickActions: React.FC = observer((props) => { closeOnSelect buttonClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-custom-background-80/70 rounded" > - {MENU_ITEMS.map((item) => { + {MENU_ITEMS.items.map((item) => { if (item.shouldRender === false) return null; return (