This commit is contained in:
DCCONSTRUCTIONS
2026-04-18 18:39:25 +03:00
commit 3ba092b60c
4944 changed files with 497564 additions and 0 deletions
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// local imports
import { WorkspaceActiveCyclesUpgrade } from "./workspace-active-cycles-upgrade";
export function WorkspaceActiveCyclesRoot() {
return <WorkspaceActiveCyclesUpgrade />;
}
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { AlertOctagon, BarChart4, CircleDashed, Folder, Microscope } from "lucide-react";
// plane imports
import { MARKETING_PRICING_PAGE_LINK } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { getButtonStyling } from "@plane/propel/button";
import { SearchIcon } from "@plane/propel/icons";
import { ContentWrapper } from "@plane/ui";
import { cn } from "@plane/utils";
// assets
import ctaL1Dark from "@/app/assets/workspace-active-cycles/cta-l-1-dark.webp?url";
import ctaL1Light from "@/app/assets/workspace-active-cycles/cta-l-1-light.webp?url";
import ctaR1Dark from "@/app/assets/workspace-active-cycles/cta-r-1-dark.webp?url";
import ctaR1Light from "@/app/assets/workspace-active-cycles/cta-r-1-light.webp?url";
import ctaR2Dark from "@/app/assets/workspace-active-cycles/cta-r-2-dark.webp?url";
import ctaR2Light from "@/app/assets/workspace-active-cycles/cta-r-2-light.webp?url";
// components
import { ProIcon } from "@/components/common/pro-icon";
// hooks
import { useUser } from "@/hooks/store/user";
export const WORKSPACE_ACTIVE_CYCLES_DETAILS = [
{
key: "10000_feet_view",
title: "10,000-feet view of all active cycles.",
description:
"Zoom out to see running cycles across all your projects at once instead of going from Cycle to Cycle in each project.",
icon: Folder,
},
{
key: "get_snapshot_of_each_active_cycle",
title: "Get a snapshot of each active cycle.",
description:
"Track high-level metrics for all active cycles, see their state of progress, and get a sense of scope against deadlines.",
icon: CircleDashed,
},
{
key: "compare_burndowns",
title: "Compare burndowns.",
description: "Monitor how each of your teams are performing with a peek into each cycle’s burndown report.",
icon: BarChart4,
},
{
key: "quickly_see_make_or_break_issues",
title: "Quickly see make-or-break work items. ",
description:
"Preview high-priority work items for each cycle against due dates. See all of them per cycle in one click.",
icon: AlertOctagon,
},
{
key: "zoom_into_cycles_that_need_attention",
title: "Zoom into cycles that need attention. ",
description: "Investigate the state of any cycle that doesn’t conform to expectations in one click.",
icon: SearchIcon,
},
{
key: "stay_ahead_of_blockers",
title: "Stay ahead of blockers.",
description:
"Spot challenges from one project to another and see inter-cycle dependencies that aren’t obvious from any other view.",
icon: Microscope,
},
];
export const WorkspaceActiveCyclesUpgrade = observer(function WorkspaceActiveCyclesUpgrade() {
const { t } = useTranslation();
// store hooks
const {
userProfile: { data: userProfile },
} = useUser();
const isDarkMode = userProfile?.theme.theme === "dark";
return (
<ContentWrapper className="gap-10">
<div
className={cn("item-center flex min-h-[25rem] justify-between rounded-xl", {
"bg-gradient-to-l from-[#CFCFCF] to-[#212121]": userProfile?.theme.theme === "dark",
"bg-gradient-to-l from-[#3b5ec6] to-[#f5f7fe]": userProfile?.theme.theme === "light",
})}
>
<div className="relative flex flex-col justify-center gap-7 px-14 lg:w-1/2">
<div className="flex max-w-64 flex-col gap-2">
<h2 className="text-20 font-semibold">{t("on_demand_snapshots_of_all_your_cycles")}</h2>
<p className="text-14 font-medium text-tertiary">{t("active_cycles_description")}</p>
</div>
<div className="flex items-center gap-3">
<a
className={`${getButtonStyling("primary", "base")} cursor-pointer`}
href={MARKETING_PRICING_PAGE_LINK}
target="_blank"
rel="noreferrer"
>
<ProIcon className="h-3.5 w-3.5 text-on-color" />
{t("upgrade")}
</a>
</div>
<span className="absolute top-0 left-0">
<img
src={isDarkMode ? ctaL1Dark : ctaL1Light}
className="h-[125px] w-[125px] rounded-xl object-contain"
alt="l-1"
/>
</span>
</div>
<div className="relative hidden w-1/2 lg:block">
<span className="absolute right-0 bottom-0">
<img src={isDarkMode ? ctaR1Dark : ctaR1Light} className="h-full w-full object-contain" alt="r-1" />
</span>
<span className="absolute right-1/2 -bottom-16 rounded-xl">
<img src={isDarkMode ? ctaR2Dark : ctaR2Light} className="h-full w-full object-contain" alt="r-2" />
</span>
</div>
</div>
<div className="grid h-full grid-cols-1 gap-5 pb-8 lg:grid-cols-2 xl:grid-cols-3">
{WORKSPACE_ACTIVE_CYCLES_DETAILS.map((item) => (
<div key={item.title} className="flex min-h-32 w-full flex-col gap-2 rounded-md bg-layer-1 p-4">
<div className="flex justify-between gap-2">
<h3 className="font-medium">{t(item.key)}</h3>
<item.icon className="text-blue-500 mt-1 h-4 w-4" />
</div>
<span className="text-13 text-tertiary">{t(`${item.key}_description`)}</span>
</div>
))}
</div>
</ContentWrapper>
);
});
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { AnalyticsTab } from "@plane/types";
import { Overview } from "@/components/analytics/overview";
import { WorkItems } from "@/components/analytics/work-items";
export const getAnalyticsTabs = (t: (key: string, params?: Record<string, any>) => string): AnalyticsTab[] => [
{ key: "overview", label: t("common.overview"), content: Overview, isDisabled: false },
{ key: "work-items", label: t("sidebar.work_items"), content: WorkItems, isDisabled: false },
];
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useMemo } from "react";
import { useTranslation } from "@plane/i18n";
import { getAnalyticsTabs } from "./tabs";
export const useAnalyticsTabs = (_workspaceSlug: string) => {
const { t } = useTranslation();
const analyticsTabs = useMemo(() => getAnalyticsTabs(t), [t]);
return analyticsTabs;
};
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// hoc/withDockItems.tsx
import React from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { PlaneNewIcon } from "@plane/propel/icons";
import type { AppSidebarItemData } from "@/components/sidebar/sidebar-item";
import { useWorkspacePaths } from "@/hooks/use-workspace-paths";
type WithDockItemsProps = {
dockItems: (AppSidebarItemData & { shouldRender: boolean })[];
};
export function withDockItems<P extends WithDockItemsProps>(WrappedComponent: React.ComponentType<P>) {
const ComponentWithDockItems = observer(function ComponentWithDockItems(props: Omit<P, keyof WithDockItemsProps>) {
const { workspaceSlug } = useParams();
const { isProjectsPath, isNotificationsPath } = useWorkspacePaths();
const dockItems: (AppSidebarItemData & { shouldRender: boolean })[] = [
{
label: "Projects",
icon: <PlaneNewIcon className="size-5" />,
href: `/${workspaceSlug}/`,
isActive: isProjectsPath && !isNotificationsPath,
shouldRender: true,
},
];
return <WrappedComponent {...(props as P)} dockItems={dockItems} />;
});
return ComponentWithDockItems;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./app-rail-hoc";
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
type Props = {
projectId: string;
workspaceSlug: string;
children: React.ReactNode;
};
export function AutomationsListWrapper(props: Props) {
return <>{props.children}</>;
}
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
export type TCustomAutomationsRootProps = {
projectId: string;
workspaceSlug: string;
};
export function CustomAutomationsRoot(_props: TCustomAutomationsRootProps) {
return <></>;
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// local components
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
import { ProjectBreadcrumb } from "./project";
type TCommonProjectBreadcrumbProps = {
workspaceSlug: string;
projectId: string;
};
export function CommonProjectBreadcrumbs(props: TCommonProjectBreadcrumbProps) {
const { workspaceSlug, projectId } = props;
// preferences
const { preferences: projectPreferences } = useProjectNavigationPreferences();
if (projectPreferences.navigationMode === "TABBED") return null;
return <ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />;
}
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { ReactNode } from "react";
import { observer } from "mobx-react";
// plane imports
import type { EProjectFeatureKey } from "@plane/constants";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import type { TNavigationItem } from "@/components/workspace/sidebar/project-navigation";
// hooks
import { useProject } from "@/hooks/store/use-project";
// local imports
import { getProjectFeatureNavigation } from "../projects/navigation/helper";
type TProjectFeatureBreadcrumbProps = {
workspaceSlug: string;
projectId: string;
featureKey: EProjectFeatureKey;
isLast?: boolean;
additionalNavigationItems?: TNavigationItem[];
};
export const ProjectFeatureBreadcrumb = observer(function ProjectFeatureBreadcrumb(
props: TProjectFeatureBreadcrumbProps
) {
const { workspaceSlug, projectId, featureKey, isLast = false, additionalNavigationItems } = props;
// store hooks
const { getPartialProjectById } = useProject();
// derived values
const project = getPartialProjectById(projectId);
if (!project) return null;
const navigationItems = getProjectFeatureNavigation(workspaceSlug, projectId, project);
// if additional navigation items are provided, add them to the navigation items
const allNavigationItems = [...(additionalNavigationItems || []), ...navigationItems];
const currentNavigationItem = allNavigationItems.find((item) => item.key === featureKey);
const icon = currentNavigationItem?.icon as ReactNode;
const name = currentNavigationItem?.name;
const href = currentNavigationItem?.href;
return (
<>
<Breadcrumbs.Item
component={
<BreadcrumbLink
key={featureKey}
label={name}
isLast={isLast}
href={href}
icon={<Breadcrumbs.Icon>{icon}</Breadcrumbs.Icon>}
/>
}
showSeparator={false}
isLast={isLast}
/>
</>
);
});
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { Logo } from "@plane/propel/emoji-icon-picker";
import { ProjectIcon } from "@plane/propel/icons";
// plane imports
import type { ICustomSearchSelectOption } from "@plane/types";
import { BreadcrumbNavigationSearchDropdown, Breadcrumbs } from "@plane/ui";
import { SwitcherLabel } from "@/components/common/switcher-label";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import type { TProject } from "@/plane-web/types";
type TProjectBreadcrumbProps = {
workspaceSlug: string;
projectId: string;
handleOnClick?: () => void;
};
export const ProjectBreadcrumb = observer(function ProjectBreadcrumb(props: TProjectBreadcrumbProps) {
const { workspaceSlug, projectId, handleOnClick } = props;
// router
const router = useAppRouter();
// store hooks
const { joinedProjectIds, getPartialProjectById } = useProject();
const currentProjectDetails = getPartialProjectById(projectId);
// store hooks
if (!currentProjectDetails) return null;
// derived values
const switcherOptions = joinedProjectIds
.map((projectId) => {
const project = getPartialProjectById(projectId);
return {
value: projectId,
query: project?.name,
content: (
<SwitcherLabel
name={project?.name}
logo_props={project?.logo_props}
LabelIcon={ProjectIcon}
type="material"
/>
),
};
})
.filter((option) => option !== undefined) as ICustomSearchSelectOption[];
// helpers
const renderIcon = (projectDetails: TProject) => (
<span className="grid size-4 flex-shrink-0 place-items-center">
<Logo logo={projectDetails.logo_props} size={14} />
</span>
);
return (
<>
<Breadcrumbs.Item
component={
<BreadcrumbNavigationSearchDropdown
selectedItem={currentProjectDetails.id}
navigationItems={switcherOptions}
onChange={(value: string) => {
router.push(`/${workspaceSlug}/projects/${value}/issues`);
}}
title={currentProjectDetails?.name}
icon={renderIcon(currentProjectDetails)}
handleOnClick={() => {
if (handleOnClick) handleOnClick();
else router.push(`/${workspaceSlug}/projects/${currentProjectDetails.id}/issues/`);
}}
shouldTruncate
/>
}
showSeparator={false}
/>
</>
);
});
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import type { TIssue } from "@plane/types";
import { IssueDetailRoot } from "@/components/issues/issue-detail/root";
export type TWorkItemDetailRoot = {
workspaceSlug: string;
projectId: string;
issueId: string;
issue: TIssue | undefined;
};
export const WorkItemDetailRoot = observer(function WorkItemDetailRoot(props: TWorkItemDetailRoot) {
const { workspaceSlug, projectId, issueId, issue } = props;
return (
<IssueDetailRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueId.toString()}
is_archived={!!issue?.archived_at}
/>
);
});
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./work-item-actions";
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { EIconSize } from "@plane/constants";
// plane imports
import { CheckIcon, StateGroupIcon } from "@plane/propel/icons";
import { Spinner } from "@plane/ui";
// store hooks
import { useProjectState } from "@/hooks/store/use-project-state";
export type TChangeWorkItemStateListProps = {
projectId: string | null;
currentStateId: string | null;
handleStateChange: (stateId: string) => void;
};
export const ChangeWorkItemStateList = observer(function ChangeWorkItemStateList(props: TChangeWorkItemStateListProps) {
const { projectId, currentStateId, handleStateChange } = props;
// store hooks
const { getProjectStates } = useProjectState();
// derived values
const projectStates = getProjectStates(projectId);
return (
<>
{projectStates ? (
projectStates.length > 0 ? (
projectStates.map((state) => (
<Command.Item key={state.id} onSelect={() => handleStateChange(state.id)} className="focus:outline-none">
<div className="flex items-center space-x-3">
<StateGroupIcon
stateGroup={state.group}
color={state.color}
size={EIconSize.LG}
percentage={state?.order}
/>
<p>{state.name}</p>
</div>
<div>{state.id === currentStateId && <CheckIcon className="h-3 w-3" />}</div>
</Command.Item>
))
) : (
<div className="text-center">No states found</div>
)
) : (
<Spinner />
)}
</>
);
});
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./change-state-list";
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { LayoutGrid } from "lucide-react";
// plane imports
import { CycleIcon, ModuleIcon, PageIcon, ProjectIcon, ViewsIcon } from "@plane/propel/icons";
import type {
IWorkspaceDefaultSearchResult,
IWorkspaceIssueSearchResult,
IWorkspacePageSearchResult,
IWorkspaceProjectSearchResult,
IWorkspaceSearchResult,
} from "@plane/types";
import { generateWorkItemLink } from "@plane/utils";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier";
export type TCommandGroups = {
[key: string]: {
icon: React.ReactNode | null;
itemName: (item: any) => React.ReactNode;
path: (item: any, projectId: string | undefined) => string;
title: string;
};
};
export const commandGroups: TCommandGroups = {
cycle: {
icon: <CycleIcon className="h-3 w-3" />,
itemName: (cycle: IWorkspaceDefaultSearchResult) => (
<h6>
<span className="text-11 text-tertiary">{cycle.project__identifier}</span> {cycle.name}
</h6>
),
path: (cycle: IWorkspaceDefaultSearchResult) =>
`/${cycle?.workspace__slug}/projects/${cycle?.project_id}/cycles/${cycle?.id}`,
title: "Cycles",
},
issue: {
icon: null,
itemName: (issue: IWorkspaceIssueSearchResult) => (
<div className="flex gap-2">
<IssueIdentifier
projectId={issue.project_id}
issueTypeId={issue.type_id}
projectIdentifier={issue.project__identifier}
issueSequenceId={issue.sequence_id}
size="xs"
/>{" "}
{issue.name}
</div>
),
path: (issue: IWorkspaceIssueSearchResult) =>
generateWorkItemLink({
workspaceSlug: issue?.workspace__slug,
projectId: issue?.project_id,
issueId: issue?.id,
projectIdentifier: issue.project__identifier,
sequenceId: issue?.sequence_id,
}),
title: "Work items",
},
issue_view: {
icon: <ViewsIcon className="h-3 w-3" />,
itemName: (view: IWorkspaceDefaultSearchResult) => (
<h6>
<span className="text-11 text-tertiary">{view.project__identifier}</span> {view.name}
</h6>
),
path: (view: IWorkspaceDefaultSearchResult) =>
`/${view?.workspace__slug}/projects/${view?.project_id}/views/${view?.id}`,
title: "Views",
},
module: {
icon: <ModuleIcon className="h-3 w-3" />,
itemName: (module: IWorkspaceDefaultSearchResult) => (
<h6>
<span className="text-11 text-tertiary">{module.project__identifier}</span> {module.name}
</h6>
),
path: (module: IWorkspaceDefaultSearchResult) =>
`/${module?.workspace__slug}/projects/${module?.project_id}/modules/${module?.id}`,
title: "Modules",
},
page: {
icon: <PageIcon className="h-3 w-3" />,
itemName: (page: IWorkspacePageSearchResult) => (
<h6>
<span className="text-11 text-tertiary">{page.project__identifiers?.[0]}</span> {page.name}
</h6>
),
path: (page: IWorkspacePageSearchResult, projectId: string | undefined) => {
let redirectProjectId = page?.project_ids?.[0];
if (!!projectId && page?.project_ids?.includes(projectId)) redirectProjectId = projectId;
return redirectProjectId
? `/${page?.workspace__slug}/projects/${redirectProjectId}/pages/${page?.id}`
: `/${page?.workspace__slug}/wiki/${page?.id}`;
},
title: "Pages",
},
project: {
icon: <ProjectIcon className="h-3 w-3" />,
itemName: (project: IWorkspaceProjectSearchResult) => project?.name,
path: (project: IWorkspaceProjectSearchResult) => `/${project?.workspace__slug}/projects/${project?.id}/issues/`,
title: "Projects",
},
workspace: {
icon: <LayoutGrid className="h-3 w-3" />,
itemName: (workspace: IWorkspaceSearchResult) => workspace?.name,
path: (workspace: IWorkspaceSearchResult) => `/${workspace?.slug}/`,
title: "Workspaces",
},
};
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./actions";
export * from "./helpers";
@@ -0,0 +1,68 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// components
import { CycleCreateUpdateModal } from "@/components/cycles/modal";
import { CreateUpdateModuleModal } from "@/components/modules";
import { CreatePageModal } from "@/components/pages/modals/create-page-modal";
import { CreateUpdateProjectViewModal } from "@/components/views/modal";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
export type TProjectLevelModalsProps = {
workspaceSlug: string;
projectId: string;
};
export const ProjectLevelModals = observer(function ProjectLevelModals(props: TProjectLevelModalsProps) {
const { workspaceSlug, projectId } = props;
// store hooks
const {
isCreateCycleModalOpen,
toggleCreateCycleModal,
isCreateModuleModalOpen,
toggleCreateModuleModal,
isCreateViewModalOpen,
toggleCreateViewModal,
createPageModal,
toggleCreatePageModal,
} = useCommandPalette();
return (
<>
<CycleCreateUpdateModal
isOpen={isCreateCycleModalOpen}
handleClose={() => toggleCreateCycleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateModuleModal
isOpen={isCreateModuleModalOpen}
onClose={() => toggleCreateModuleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateProjectViewModal
isOpen={isCreateViewModalOpen}
onClose={() => toggleCreateViewModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreatePageModal
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
isModalOpen={createPageModal.isOpen}
pageAccess={createPageModal.pageAccess}
handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
redirectionEnabled
storeType={EPageStoreType.PROJECT}
/>
</>
);
});
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import type { TIssue } from "@plane/types";
import { EIssueServiceType, EIssuesStoreType } from "@plane/types";
// components
import { BulkDeleteIssuesModal } from "@/components/core/modals/bulk-delete-issues-modal";
import { DeleteIssueModal } from "@/components/issues/delete-issue-modal";
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useUser } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useIssuesActions } from "@/hooks/use-issues-actions";
export type TWorkItemLevelModalsProps = {
workItemIdentifier: string | undefined;
};
export const WorkItemLevelModals = observer(function WorkItemLevelModals(props: TWorkItemLevelModalsProps) {
const { workItemIdentifier } = props;
// router
const { workspaceSlug, cycleId, moduleId } = useParams();
const router = useAppRouter();
// store hooks
const { data: currentUser } = useUser();
const {
issue: { getIssueById, getIssueIdByIdentifier },
} = useIssueDetail();
// derived values
const workItemId = workItemIdentifier ? getIssueIdByIdentifier(workItemIdentifier) : undefined;
const workItemDetails = workItemId ? getIssueById(workItemId) : undefined;
const { removeIssue: removeEpic } = useIssuesActions(EIssuesStoreType.EPIC);
const { removeIssue: removeWorkItem } = useIssuesActions(EIssuesStoreType.PROJECT);
const {
isCreateIssueModalOpen,
toggleCreateIssueModal,
isDeleteIssueModalOpen,
toggleDeleteIssueModal,
isBulkDeleteIssueModalOpen,
toggleBulkDeleteIssueModal,
createWorkItemAllowedProjectIds,
} = useCommandPalette();
// derived values
const { fetchSubIssues: fetchSubWorkItems } = useIssueDetail();
const { fetchSubIssues: fetchEpicSubWorkItems } = useIssueDetail(EIssueServiceType.EPICS);
const handleDeleteIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const isEpic = workItemDetails?.is_epic;
const deleteAction = isEpic ? removeEpic : removeWorkItem;
const redirectPath = `/${workspaceSlug}/projects/${projectId}/${isEpic ? "epics" : "issues"}`;
await deleteAction(projectId, issueId);
router.push(redirectPath);
} catch (error) {
console.error("Failed to delete issue:", error);
}
};
const handleCreateIssueSubmit = async (newIssue: TIssue) => {
if (!workspaceSlug || !newIssue.project_id || !newIssue.id || newIssue.parent_id !== workItemDetails?.id) return;
const fetchAction = workItemDetails?.is_epic ? fetchEpicSubWorkItems : fetchSubWorkItems;
await fetchAction(workspaceSlug?.toString(), newIssue.project_id, workItemDetails.id);
};
const getCreateIssueModalData = () => {
if (cycleId) return { cycle_id: cycleId.toString() };
if (moduleId) return { module_ids: [moduleId.toString()] };
return undefined;
};
return (
<>
<CreateUpdateIssueModal
isOpen={isCreateIssueModalOpen}
onClose={() => toggleCreateIssueModal(false)}
data={getCreateIssueModalData()}
onSubmit={handleCreateIssueSubmit}
allowedProjectIds={createWorkItemAllowedProjectIds}
/>
{workspaceSlug && workItemId && workItemDetails && workItemDetails.project_id && (
<DeleteIssueModal
handleClose={() => toggleDeleteIssueModal(false)}
isOpen={isDeleteIssueModalOpen}
data={workItemDetails}
onSubmit={() =>
handleDeleteIssue(workspaceSlug.toString(), workItemDetails.project_id!, workItemId?.toString())
}
isEpic={workItemDetails?.is_epic}
/>
)}
<BulkDeleteIssuesModal
isOpen={isBulkDeleteIssueModalOpen}
onClose={() => toggleBulkDeleteIssueModal(false)}
user={currentUser}
/>
</>
);
});
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// components
import { CreateProjectModal } from "@/components/project/create-project-modal";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
export type TWorkspaceLevelModalsProps = {
workspaceSlug: string;
};
export const WorkspaceLevelModals = observer(function WorkspaceLevelModals(props: TWorkspaceLevelModalsProps) {
const { workspaceSlug } = props;
// store hooks
const { isCreateProjectModalOpen, toggleCreateProjectModal } = useCommandPalette();
return (
<>
<CreateProjectModal
isOpen={isCreateProjectModalOpen}
onClose={() => toggleCreateProjectModal(false)}
workspaceSlug={workspaceSlug.toString()}
/>
</>
);
});
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// core
import type { TPowerKModalPageDetails } from "@/components/power-k/ui/modal/constants";
// local imports
import type { TPowerKPageTypeExtended } from "./types";
export const POWER_K_MODAL_PAGE_DETAILS_EXTENDED: Record<TPowerKPageTypeExtended, TPowerKModalPageDetails> = {};
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { Params } from "react-router";
// local imports
import type { TPowerKContextTypeExtended } from "./types";
export const detectExtendedContextFromURL = (_params: Params): TPowerKContextTypeExtended | null => null;
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// local imports
import type { TPowerKContextType } from "@/components/power-k/core/types";
type TArgs = {
activeContext: TPowerKContextType | null;
};
export const useExtendedContextIndicator = (_args: TArgs): string | null => null;
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// components
import type { TPowerKCommandConfig } from "@/components/power-k/core/types";
import type { ContextBasedActionsProps, TContextEntityMap } from "@/components/power-k/ui/pages/context-based";
// local imports
import type { TPowerKContextTypeExtended } from "../../types";
export const CONTEXT_ENTITY_MAP_EXTENDED: Record<TPowerKContextTypeExtended, TContextEntityMap> = {};
export function PowerKContextBasedActionsExtended(_props: ContextBasedActionsProps) {
return null;
}
export const usePowerKContextBasedExtendedActions = (): TPowerKCommandConfig[] => [];
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// plane types
import { StateGroupIcon } from "@plane/propel/icons";
import type { IState } from "@plane/types";
// components
import { PowerKModalCommandItem } from "@/components/power-k/ui/modal/command-item";
export type TPowerKProjectStatesMenuItemsProps = {
handleSelect: (stateId: string) => void;
projectId: string | undefined;
selectedStateId: string | undefined;
states: IState[];
workspaceSlug: string;
};
export const PowerKProjectStatesMenuItems = observer(function PowerKProjectStatesMenuItems(
props: TPowerKProjectStatesMenuItemsProps
) {
const { handleSelect, selectedStateId, states } = props;
return (
<>
{states.map((state) => (
<PowerKModalCommandItem
key={state.id}
iconNode={<StateGroupIcon stateGroup={state.group} color={state.color} className="size-3.5 shrink-0" />}
label={state.name}
isSelected={state.id === selectedStateId}
onSelect={() => handleSelect(state.id)}
/>
))}
</>
);
});
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { Command } from "cmdk";
import { useTranslation } from "@plane/i18n";
import { SearchIcon } from "@plane/propel/icons";
// plane imports
// components
import type { TPowerKContext } from "@/components/power-k/core/types";
// plane web imports
import { PowerKModalCommandItem } from "@/components/power-k/ui/modal/command-item";
export type TPowerKModalNoSearchResultsCommandProps = {
context: TPowerKContext;
searchTerm: string;
updateSearchTerm: (value: string) => void;
};
export function PowerKModalNoSearchResultsCommand(props: TPowerKModalNoSearchResultsCommandProps) {
const { updateSearchTerm } = props;
// translation
const { t } = useTranslation();
return (
<Command.Group>
<PowerKModalCommandItem
icon={SearchIcon}
value="no-results"
label={
<p className="flex items-center gap-2">
{t("power_k.search_menu.no_results")}{" "}
<span className="shrink-0 text-13 text-tertiary">{t("power_k.search_menu.clear_search")}</span>
</p>
}
onSelect={() => updateSearchTerm("")}
/>
</Command.Group>
);
}
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// components
import type { TPowerKSearchResultGroupDetails } from "@/components/power-k/ui/modal/search-results-map";
// local imports
import type { TPowerKSearchResultsKeysExtended } from "../types";
type TSearchResultsGroupsMapExtended = Record<TPowerKSearchResultsKeysExtended, TPowerKSearchResultGroupDetails>;
export const SEARCH_RESULTS_GROUPS_MAP_EXTENDED: TSearchResultsGroupsMapExtended = {};
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export type TPowerKContextTypeExtended = never;
export type TPowerKPageTypeExtended = never;
export type TPowerKSearchResultsKeysExtended = never;
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { ReactNode } from "react";
import { useRef } from "react";
import { observer } from "mobx-react";
// plane imports
import { CommentReplyIcon } from "@plane/propel/icons";
import type { TIssueComment } from "@plane/types";
import { cn } from "@plane/utils";
// hooks
type TCommentBlock = {
comment: TIssueComment;
ends: "top" | "bottom" | undefined;
children: ReactNode;
};
export const CommentBlock = observer(function CommentBlock(props: TCommentBlock) {
const { comment, ends, children } = props;
const commentBlockRef = useRef<HTMLDivElement>(null);
if (!comment) return null;
return (
<div
id={comment.id}
className={`relative flex gap-3 ${ends === "top" ? `pb-2` : ends === "bottom" ? `pt-2` : `py-2`}`}
ref={commentBlockRef}
>
<div
className="transition-border absolute top-0 bottom-0 left-[13px] w-px bg-layer-3 duration-1000"
aria-hidden
/>
<div
className={cn(
"transition-border relative z-[3] flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg border border-subtle bg-layer-2 uppercase shadow-raised-100 duration-1000"
)}
>
<CommentReplyIcon width={14} height={14} className="text-secondary" aria-hidden="true" />
</div>
<div className="flex flex-grow flex-col gap-3 truncate">
<div className="mb-2 rounded-lg border border-subtle bg-layer-2 p-3 text-body-sm-regular shadow-raised-100">
{children}
</div>
</div>
</div>
);
});
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./comment-block";
export { CommentCardDisplay } from "@/components/comments/card/display";
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { ReactNode } from "react";
import { observer } from "mobx-react";
import { useParams } from "react-router";
// components
import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-button";
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
export const ExtendedAppHeader = observer(function ExtendedAppHeader(props: { header: ReactNode }) {
const { header } = props;
// params
const { projectId, workItem } = useParams();
// preferences
const { preferences: projectPreferences } = useProjectNavigationPreferences();
// store hooks
const { sidebarCollapsed } = useAppTheme();
// derived values
const shouldShowSidebarToggleButton = projectPreferences.navigationMode === "ACCORDION" || (!projectId && !workItem);
return (
<>
{sidebarCollapsed && shouldShowSidebarToggleButton && <AppSidebarToggleButton />}
<div className="w-full">{header}</div>
</>
);
});
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { lazy, Suspense } from "react";
import { observer } from "mobx-react";
const ProfileSettingsModal = lazy(() =>
import("@/components/settings/profile/modal").then((module) => ({
default: module.ProfileSettingsModal,
}))
);
type TGlobalModalsProps = {
workspaceSlug: string;
};
/**
* GlobalModals component manages all workspace-level modals across Plane applications.
*
* This includes:
* - Profile settings modal
*/
export const GlobalModals = observer(function GlobalModals(_props: TGlobalModalsProps) {
return (
<Suspense fallback={null}>
<ProfileSettingsModal />
</Suspense>
);
});
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export { useQuickActionsFactory } from "@/components/common/quick-actions-factory";
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { IWorkspace } from "@plane/types";
type TProps = {
workspace?: IWorkspace;
};
export function SubscriptionPill(_props: TProps) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,154 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { useTheme } from "next-themes";
import { Disclosure } from "@headlessui/react";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
// plane imports
import { useTranslation } from "@plane/i18n";
import type { ICycle } from "@plane/types";
import { Row } from "@plane/ui";
// assets
import darkActiveCycleAsset from "@/app/assets/empty-state/cycle/active-dark.webp?url";
import lightActiveCycleAsset from "@/app/assets/empty-state/cycle/active-light.webp?url";
// components
import { ActiveCycleStats } from "@/components/cycles/active-cycle/cycle-stats";
import { ActiveCycleProductivity } from "@/components/cycles/active-cycle/productivity";
import { ActiveCycleProgress } from "@/components/cycles/active-cycle/progress";
import useCyclesDetails from "@/components/cycles/active-cycle/use-cycles-details";
import { CycleListGroupHeader } from "@/components/cycles/list/cycle-list-group-header";
import { CyclesListItem } from "@/components/cycles/list/cycles-list-item";
// hooks
import { useCycle } from "@/hooks/store/use-cycle";
import type { ActiveCycleIssueDetails } from "@/store/issue/cycle";
interface IActiveCycleDetails {
workspaceSlug: string;
projectId: string;
cycleId?: string;
showHeader?: boolean;
}
type ActiveCyclesComponentProps = {
cycleId: string | null | undefined;
activeCycle: ICycle | null;
activeCycleResolvedPath: string;
workspaceSlug: string;
projectId: string;
handleFiltersUpdate: (filters: any) => void;
cycleIssueDetails?: ActiveCycleIssueDetails | { nextPageResults: boolean };
};
const ActiveCyclesComponent = observer(function ActiveCyclesComponent({
cycleId,
activeCycle,
activeCycleResolvedPath: _activeCycleResolvedPath,
workspaceSlug,
projectId,
handleFiltersUpdate,
cycleIssueDetails,
}: ActiveCyclesComponentProps) {
const { t } = useTranslation();
if (!cycleId || !activeCycle) {
return (
<EmptyStateDetailed
assetKey="cycle"
title={t("project_cycles.empty_state.active.title")}
description={t("project_cycles.empty_state.active.description")}
rootClassName="py-10 h-auto"
/>
);
}
return (
<div className="flex flex-col border-b border-subtle">
<CyclesListItem
key={cycleId}
cycleId={cycleId}
workspaceSlug={workspaceSlug}
projectId={projectId}
className="!border-b-transparent"
/>
<Row className="bg-surface-1 pt-3 pb-6">
<div className="grid grid-cols-1 gap-3 bg-surface-1 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress
handleFiltersUpdate={handleFiltersUpdate}
projectId={projectId}
workspaceSlug={workspaceSlug}
cycle={activeCycle}
/>
<ActiveCycleProductivity workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} />
<ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={cycleId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
</div>
</Row>
</div>
);
});
export const ActiveCycleRoot = observer(function ActiveCycleRoot(props: IActiveCycleDetails) {
const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props;
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks
const { t } = useTranslation();
// store hooks
const { currentProjectActiveCycleId } = useCycle();
// derived values
const cycleId = propsCycleId ?? currentProjectActiveCycleId;
const activeCycleResolvedPath = resolvedTheme === "light" ? lightActiveCycleAsset : darkActiveCycleAsset;
// fetch cycle details
const {
handleFiltersUpdate,
cycle: activeCycle,
cycleIssueDetails,
} = useCyclesDetails({ workspaceSlug, projectId, cycleId });
return (
<>
{showHeader ? (
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
{({ open }) => (
<>
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 cursor-pointer border-b border-subtle bg-layer-1">
<CycleListGroupHeader title={t("project_cycles.active_cycle.label")} type="current" isExpanded={open} />
</Disclosure.Button>
<Disclosure.Panel>
<ActiveCyclesComponent
cycleId={cycleId}
activeCycle={activeCycle}
activeCycleResolvedPath={activeCycleResolvedPath}
workspaceSlug={workspaceSlug}
projectId={projectId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
</Disclosure.Panel>
</>
)}
</Disclosure>
) : (
<ActiveCyclesComponent
cycleId={cycleId}
activeCycle={activeCycle}
activeCycleResolvedPath={activeCycleResolvedPath}
workspaceSlug={workspaceSlug}
projectId={projectId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
)}
</>
);
});
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
type Props = {
cycleId: string;
projectId: string;
};
export const CycleAdditionalActions = observer(function CycleAdditionalActions(_props: Props) {
return <></>;
});
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { Fragment } from "react";
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import type { TCycleEstimateType } from "@plane/types";
import { Loader } from "@plane/ui";
import { getDate } from "@plane/utils";
// components
import ProgressChart from "@/components/core/sidebar/progress-chart";
import { validateCycleSnapshot } from "@/components/cycles/analytics-sidebar/issue-progress";
import { EstimateTypeDropdown } from "@/components/cycles/dropdowns";
// hooks
import { useCycle } from "@/hooks/store/use-cycle";
type ProgressChartProps = {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
export const SidebarChart = observer(function SidebarChart(props: ProgressChartProps) {
const { workspaceSlug, projectId, cycleId } = props;
// hooks
const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } =
useCycle();
const { t } = useTranslation();
// derived data
const cycleDetails = validateCycleSnapshot(getCycleById(cycleId));
const cycleStartDate = getDate(cycleDetails?.start_date);
const cycleEndDate = getDate(cycleDetails?.end_date);
const totalEstimatePoints = cycleDetails?.total_estimate_points || 0;
const totalIssues = cycleDetails?.total_issues || 0;
const estimateType = getEstimateTypeByCycleId(cycleId);
const chartDistributionData =
estimateType === "points" ? cycleDetails?.estimate_distribution : cycleDetails?.distribution || undefined;
const completionChartDistributionData = chartDistributionData?.completion_chart || undefined;
if (!workspaceSlug || !projectId || !cycleId) return null;
const isArchived = !!cycleDetails?.archived_at;
// handlers
const onChange = async (value: TCycleEstimateType) => {
setEstimateType(cycleId, value);
if (!workspaceSlug || !projectId || !cycleId) return;
try {
if (isArchived) {
await fetchArchivedCycleDetails(workspaceSlug, projectId, cycleId);
} else {
await fetchCycleDetails(workspaceSlug, projectId, cycleId);
}
} catch (err) {
console.error(err);
setEstimateType(cycleId, estimateType);
}
};
return (
<div>
<div className="relative flex items-center justify-between gap-2 pt-4">
<EstimateTypeDropdown value={estimateType} onChange={onChange} cycleId={cycleId} projectId={projectId} />
</div>
<div className="py-4">
<div>
{cycleStartDate && cycleEndDate && completionChartDistributionData ? (
<Fragment>
<ProgressChart
distribution={completionChartDistributionData}
totalIssues={estimateType === "points" ? totalEstimatePoints : totalIssues}
plotTitle={estimateType === "points" ? t("points") : t("work_items")}
/>
</Fragment>
) : (
<Loader className="mt-4 h-[160px] w-full">
<Loader.Item width="100%" height="100%" />
</Loader>
)}
</div>
</div>
</div>
);
});
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
// components
import { SidebarChart } from "./base";
type Props = {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
export function SidebarChartRoot(props: Props) {
return <SidebarChart {...props} />;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./modal";
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
interface Props {
isOpen: boolean;
handleClose: () => void;
cycleId: string;
projectId: string;
workspaceSlug: string;
transferrableIssuesCount: number;
cycleName: string;
}
export function EndCycleModal(_props: Props) {
return <></>;
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./active-cycle";
export * from "./analytics-sidebar";
export * from "./additional-actions";
export * from "./end-cycle";
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
// local components
type TDeDupeButtonRoot = {
workspaceSlug: string;
isDuplicateModalOpen: boolean;
handleOnClick: () => void;
label: string;
};
export function DeDupeButtonRoot(_props: TDeDupeButtonRoot) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// types
import type { TDeDupeIssue } from "@plane/types";
type TDuplicateModalRootProps = {
workspaceSlug: string;
issues: TDeDupeIssue[];
handleDuplicateIssueModal: (value: boolean) => void;
};
export function DuplicateModalRoot(_props: TDuplicateModalRootProps) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import { observer } from "mobx-react";
// types
import type { TDeDupeIssue } from "@plane/types";
import type { TIssueOperations } from "@/components/issues/issue-detail";
type TDeDupeIssuePopoverRootProps = {
workspaceSlug: string;
projectId: string;
rootIssueId: string;
issues: TDeDupeIssue[];
issueOperations: TIssueOperations;
disabled?: boolean;
renderDeDupeActionModals?: boolean;
isIntakeIssue?: boolean;
};
export const DeDupeIssuePopoverRoot = observer(function DeDupeIssuePopoverRoot(props: TDeDupeIssuePopoverRootProps) {
const {} = props;
return <></>;
});
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
type TDeDupeIssueButtonLabelProps = {
isOpen: boolean;
buttonLabel: string;
};
export function DeDupeIssueButtonLabel(_props: TDeDupeIssueButtonLabelProps) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export const isSidebarToggleVisible = () => true;
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./helper";
export * from "./sidebar-workspace-menu";
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export function DesktopSidebarWorkspaceMenu() {
return null;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// plane imports
import type { TCallbackMentionComponentProps } from "@plane/editor";
export type TEditorMentionComponentProps = TCallbackMentionComponentProps;
export function EditorAdditionalMentionsRoot(_props: TEditorMentionComponentProps) {
return null;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./modal";
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import type { TIssue } from "@plane/types";
export interface EpicModalProps {
data?: Partial<TIssue>;
isOpen: boolean;
onClose: () => void;
beforeFormSubmit?: () => Promise<void>;
onSubmit?: (res: TIssue) => Promise<void>;
fetchIssueDetails?: boolean;
primaryButtonText?: {
default: string;
loading: string;
};
isProjectSelectionDisabled?: boolean;
}
export function CreateUpdateEpicModal(_props: EpicModalProps) {
return <></>;
}
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { TrashIcon } from "@plane/propel/icons";
type TEstimateListItem = {
estimateId: string;
isAdmin: boolean;
isEstimateEnabled: boolean;
isEditable: boolean;
onEditClick?: (estimateId: string) => void;
onDeleteClick?: (estimateId: string) => void;
};
export const EstimateListItemButtons = observer(function EstimateListItemButtons(props: TEstimateListItem) {
const { estimateId, isAdmin, isEditable, onDeleteClick } = props;
if (!isAdmin || !isEditable) return <></>;
return (
<div className="relative flex items-center gap-1">
<button
className="relative flex h-6 w-6 flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-colors hover:bg-layer-1"
onClick={() => onDeleteClick && onDeleteClick(estimateId)}
data-ph-element={PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_LIST_ITEM}
>
<TrashIcon width={12} height={12} />
</button>
</div>
);
});
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { TEstimateSystemKeys } from "@plane/types";
import { EEstimateSystem } from "@plane/types";
export const isEstimateSystemEnabled = (key: TEstimateSystemKeys) => {
switch (key) {
case EEstimateSystem.POINTS:
return true;
case EEstimateSystem.CATEGORIES:
return true;
default:
return false;
}
};
@@ -0,0 +1,10 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./estimate-list-item-buttons";
export * from "./update";
export * from "./points";
export * from "./helper";
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./time-input";
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export type TEstimateTimeInputProps = {
value?: number;
handleEstimateInputValue: (value: string) => void;
};
export function EstimateTimeInput(_props: TEstimateTimeInputProps) {
return <></>;
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
export type TEstimatePointDelete = {
workspaceSlug: string;
projectId: string;
estimateId: string;
estimatePointId: string;
estimatePoints: TEstimatePointsObject[];
callback: () => void;
estimatePointError?: TEstimateTypeErrorObject | undefined;
handleEstimatePointError?: (newValue: string, message: string | undefined, mode?: "add" | "delete") => void;
estimateSystem: TEstimateSystemKeys;
};
export function EstimatePointDelete(_props: TEstimatePointDelete) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./delete";
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./modal";
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
type TUpdateEstimateModal = {
workspaceSlug: string;
projectId: string;
estimateId: string | undefined;
isOpen: boolean;
handleClose: () => void;
};
export const UpdateEstimateModal = observer(function UpdateEstimateModal(_props: TUpdateEstimateModal) {
return <></>;
});
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// components
import type { IBlockUpdateData, IGanttBlock } from "@plane/types";
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
// hooks
import { BlockRow } from "@/components/gantt-chart/blocks/block-row";
import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants";
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
// types
export type GanttChartBlocksProps = {
blockIds: string[];
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
handleScrollToBlock: (block: IGanttBlock) => void;
enableAddBlock: boolean | ((blockId: string) => boolean);
showAllBlocks: boolean;
selectionHelpers: TSelectionHelper;
ganttContainerRef: React.RefObject<HTMLDivElement>;
};
export function GanttChartRowList(props: GanttChartBlocksProps) {
const {
blockIds,
blockUpdateHandler,
handleScrollToBlock,
enableAddBlock,
showAllBlocks,
selectionHelpers,
ganttContainerRef,
} = props;
return (
<div className="absolute top-0 left-0 w-max min-w-full">
{blockIds?.map((blockId) => (
<>
<RenderIfVisible
root={ganttContainerRef}
horizontalOffset={100}
verticalOffset={200}
classNames="relative min-w-full w-max"
placeholderChildren={<div className="pointer-events-none w-full" style={{ height: `${BLOCK_HEIGHT}px` }} />}
shouldRecordHeights={false}
>
<BlockRow
key={blockId}
blockId={blockId}
showAllBlocks={showAllBlocks}
blockUpdateHandler={blockUpdateHandler}
handleScrollToBlock={handleScrollToBlock}
enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock}
selectionHelpers={selectionHelpers}
ganttContainerRef={ganttContainerRef}
/>
</RenderIfVisible>
</>
))}
</div>
);
}
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
//
import type { IBlockUpdateDependencyData } from "@plane/types";
import { GanttChartBlock } from "@/components/gantt-chart/blocks/block";
export type GanttChartBlocksProps = {
blockIds: string[];
blockToRender: (data: any) => React.ReactNode;
enableBlockLeftResize: boolean | ((blockId: string) => boolean);
enableBlockRightResize: boolean | ((blockId: string) => boolean);
enableBlockMove: boolean | ((blockId: string) => boolean);
ganttContainerRef: React.RefObject<HTMLDivElement>;
showAllBlocks: boolean;
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
enableDependency: boolean | ((blockId: string) => boolean);
};
export function GanttChartBlocksList(props: GanttChartBlocksProps) {
const {
blockIds,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
ganttContainerRef,
showAllBlocks,
updateBlockDates,
enableDependency,
} = props;
return (
<>
{blockIds?.map((blockId) => (
<GanttChartBlock
key={blockId}
blockId={blockId}
showAllBlocks={showAllBlocks}
blockToRender={blockToRender}
enableBlockLeftResize={
typeof enableBlockLeftResize === "function" ? enableBlockLeftResize(blockId) : enableBlockLeftResize
}
enableBlockRightResize={
typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize
}
enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove}
enableDependency={typeof enableDependency === "function" ? enableDependency(blockId) : enableDependency}
ganttContainerRef={ganttContainerRef}
updateBlockDates={updateBlockDates}
/>
))}
</>
);
}
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./left-draggable";
export * from "./right-draggable";
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { RefObject } from "react";
import type { IGanttBlock } from "@plane/types";
type LeftDependencyDraggableProps = {
block: IGanttBlock;
ganttContainerRef: RefObject<HTMLDivElement>;
};
export function LeftDependencyDraggable(_props: LeftDependencyDraggableProps) {
return <></>;
}
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { RefObject } from "react";
import type { IGanttBlock } from "@plane/types";
type RightDependencyDraggableProps = {
block: IGanttBlock;
ganttContainerRef: RefObject<HTMLDivElement>;
};
export function RightDependencyDraggable(_props: RightDependencyDraggableProps) {
return <></>;
}
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
type Props = {
isEpic?: boolean;
};
export function TimelineDependencyPaths(_props: Props) {
return <></>;
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export function TimelineDraggablePath() {
return <></>;
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./blockDraggables";
export * from "./dependency-paths";
export * from "./draggable-dependency-path";
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./dependency";
export * from "./layers";
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { FC } from "react";
type Props = {
itemsContainerWidth: number;
blockCount: number;
};
export const GanttAdditionalLayers: FC<Props> = () => null;
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export { GanttAdditionalLayers } from "./additional-layers";
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./version-number";
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useState, useEffect, useRef } from "react";
import { observer } from "mobx-react";
// hooks
import { Loader } from "@plane/ui";
import { ProductUpdatesFallback } from "@/components/global/product-updates/fallback";
import { useInstance } from "@/hooks/store/use-instance";
export const ProductUpdatesChangelog = observer(function ProductUpdatesChangelog() {
// refs
const isLoadingRef = useRef(true);
// states
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
// store hooks
const { config } = useInstance();
// derived values
const changeLogUrl = config?.instance_changelog_url;
const shouldShowFallback = !changeLogUrl || changeLogUrl === "" || hasError;
// timeout fallback - if iframe doesn't load within 15 seconds, show error
useEffect(() => {
if (!changeLogUrl || changeLogUrl === "") {
setIsLoading(false);
isLoadingRef.current = false;
return;
}
setIsLoading(true);
setHasError(false);
isLoadingRef.current = true;
const timeoutId = setTimeout(() => {
if (isLoadingRef.current) {
setHasError(true);
setIsLoading(false);
isLoadingRef.current = false;
}
}, 15000); // 15 second timeout
return () => {
clearTimeout(timeoutId);
};
}, [changeLogUrl]);
const handleIframeLoad = () => {
setTimeout(() => {
isLoadingRef.current = false;
setIsLoading(false);
}, 1000);
};
const handleIframeError = () => {
isLoadingRef.current = false;
setHasError(true);
setIsLoading(false);
};
// Show fallback if URL is missing, empty, or iframe failed to load
if (shouldShowFallback) {
return (
<ProductUpdatesFallback
description="We're having trouble fetching the updates. Please visit our changelog to view the latest updates."
variant={config?.is_self_managed ? "self-managed" : "cloud"}
/>
);
}
return (
<div className="vertical-scrollbar relative mx-0.5 flex scrollbar-xs h-[550px] flex-col overflow-hidden overflow-y-scroll px-6">
{isLoading && (
<Loader className="absolute inset-0 flex h-full w-full flex-col items-center justify-center gap-3">
<Loader.Item height="95%" width="95%" />
</Loader>
)}
<iframe
src={changeLogUrl}
className={`h-full w-full ${isLoading ? "opacity-0" : "opacity-100"} transition-opacity duration-200`}
onLoad={handleIframeLoad}
onError={handleIframeError}
/>
</div>
);
});
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import packageJson from "package.json";
import { useTranslation } from "@plane/i18n";
// helpers
import { cn } from "@plane/utils";
export const ProductUpdatesHeader = observer(function ProductUpdatesHeader() {
const { t } = useTranslation();
return (
<div className="mx-6 my-4 flex flex-shrink-0 items-center justify-between gap-2">
<div className="flex w-full items-center">
<div className="flex gap-2 text-18 font-medium">{t("whats_new")}</div>
<div
className={cn(
"mx-2 rounded-full bg-accent-primary/20 px-2 py-0.5 text-center text-11 font-medium text-accent-primary"
)}
>
{t("version")}: v{packageJson.version}
</div>
</div>
</div>
);
});
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// assets
import { useTranslation } from "@plane/i18n";
import packageJson from "package.json";
export function PlaneVersionNumber() {
const { t } = useTranslation();
return (
<span>
{t("version")}: v{packageJson.version}
</span>
);
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export function HomePageHeader() {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./peek-overviews";
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { IssuePeekOverview } from "@/components/issues/peek-overview";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
export const HomePeekOverviewsRoot = observer(function HomePeekOverviewsRoot() {
const { peekIssue } = useIssueDetail();
return peekIssue ? <IssuePeekOverview /> : null;
});
@@ -0,0 +1,15 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import type { EInboxIssueSource } from "@plane/types";
export type TInboxSourcePill = {
source: EInboxIssueSource;
};
export function InboxSourcePill(_props: TInboxSourcePill) {
return <></>;
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./maintenance-message";
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export function MaintenanceMessage() {
const linkMap = [
{
key: "mail_to",
label: "Contact Support",
value: "mailto:support@plane.so",
},
];
return (
<>
<div className="flex flex-col gap-2.5">
<h1 className="text-left text-18 font-semibold text-primary">
&#x1F6A7; Looks like NODE.DC didn&apos;t start up correctly!
</h1>
<span className="text-left text-14 font-medium text-secondary">
Some services might have failed to start. Please check your container logs to identify and resolve the issue.
If you&apos;re stuck, reach out to our support team for more help.
</span>
</div>
<div className="mt-1 flex items-center justify-start gap-6">
{linkMap.map((link) => (
<div key={link.key}>
<a
href={link.value}
target="_blank"
rel="noopener noreferrer"
className="text-13 text-accent-primary hover:underline"
>
{link.label}
</a>
</div>
))}
</div>
</>
);
}
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// components
import { BulkOperationsUpgradeBanner } from "@/components/issues/bulk-operations/upgrade-banner";
// hooks
import { useMultipleSelectStore } from "@/hooks/store/use-multiple-select-store";
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
type Props = {
className?: string;
selectionHelpers: TSelectionHelper;
};
export const IssueBulkOperationsRoot = observer(function IssueBulkOperationsRoot(props: Props) {
const { className, selectionHelpers } = props;
// store hooks
const { isSelectionActive } = useMultipleSelectStore();
if (!isSelectionActive || selectionHelpers.isSelectionDisabled) return null;
return <BulkOperationsUpgradeBanner className={className} />;
});
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
type Props = {
handleRemove: (val: string) => void;
values: string[];
editable: boolean | undefined;
};
export const AppliedIssueTypeFilters = observer(function AppliedIssueTypeFilters(_props: Props) {
return null;
});
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterIssueTypes = observer(function FilterIssueTypes(_props: Props) {
return null;
});
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterTeamProjects = observer(function FilterTeamProjects(_props: Props) {
return null;
});
@@ -0,0 +1,135 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { Circle } from "lucide-react";
// plane imports
import {
EUserPermissions,
EUserPermissionsLevel,
SPACE_BASE_PATH,
SPACE_BASE_URL,
WORK_ITEM_TRACKER_ELEMENTS,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { NewTabIcon, WorkItemsIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { EIssuesStoreType } from "@plane/types";
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { CountChip } from "@/components/common/count-chip";
// constants
import { HeaderFilters } from "@/components/issues/filters";
// helpers
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web imports
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const IssuesHeader = observer(function IssuesHeader() {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
// store hooks
const {
issues: { getGroupIssueCount },
} = useIssues(EIssuesStoreType.PROJECT);
// i18n
const { t } = useTranslation();
const { currentProjectDetails, loader } = useProject();
const { toggleCreateIssueModal } = useCommandPalette();
const { allowPermissions } = useUserPermissions();
const { isMobile } = usePlatformOS();
const SPACE_APP_URL = (SPACE_BASE_URL.trim() === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
const publishedURL = `${SPACE_APP_URL}/issues/${currentProjectDetails?.anchor}`;
const issuesCount = getGroupIssueCount(undefined, undefined, false);
const canUserCreateIssue = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
return (
<Header>
<Header.LeftItem>
<div className="flex items-center gap-2.5">
<Breadcrumbs onBack={() => router.back()} isLoading={loader === "init-loader"} className="flex-grow-0">
<CommonProjectBreadcrumbs workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()} />
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t("work_items")}
href={`/${workspaceSlug}/projects/${projectId}/issues/`}
icon={<WorkItemsIcon className="h-4 w-4 text-tertiary" />}
isLast
/>
}
isLast
/>
</Breadcrumbs>
{issuesCount && issuesCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={t("issues_header.count_tooltip", { count: issuesCount })}
position="bottom"
>
<CountChip count={issuesCount} />
</Tooltip>
) : null}
</div>
{currentProjectDetails?.anchor ? (
<a
href={publishedURL}
className="group flex items-center gap-1.5 rounded-sm bg-accent-primary/10 px-2.5 py-1 text-11 font-medium text-accent-primary"
target="_blank"
rel="noopener noreferrer"
>
<Circle className="h-1.5 w-1.5 fill-accent-primary" strokeWidth={2} />
{t("workspace_projects.network.public.title")}
<NewTabIcon className="hidden h-3 w-3 group-hover:block" strokeWidth={2} />
</a>
) : (
<></>
)}
</Header.LeftItem>
<Header.RightItem>
<div className="hidden gap-2 md:flex">
<HeaderFilters
projectId={projectId}
currentProjectDetails={currentProjectDetails}
workspaceSlug={workspaceSlug}
canUserCreateIssue={canUserCreateIssue}
/>
</div>
{canUserCreateIssue && (
<Button
variant="primary"
size="lg"
onClick={() => {
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
}}
data-ph-element={WORK_ITEM_TRACKER_ELEMENTS.HEADER_ADD_BUTTON.WORK_ITEMS}
>
<div className="block sm:hidden">{t("issue.label", { count: 1 })}</div>
<div className="hidden sm:block">{t("issue.add.label")}</div>
</Button>
)}
</Header.RightItem>
</Header>
);
});
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetActionButtonsProps = {
disabled: boolean;
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export function WorkItemAdditionalWidgetActionButtons(_props: TWorkItemAdditionalWidgetActionButtonsProps) {
return null;
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetCollapsiblesProps = {
disabled: boolean;
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export function WorkItemAdditionalWidgetCollapsibles(_props: TWorkItemAdditionalWidgetCollapsiblesProps) {
return null;
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// plane types
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
export type TWorkItemAdditionalWidgetModalsProps = {
hideWidgets: TWorkItemWidgets[];
issueServiceType: TIssueServiceType;
projectId: string;
workItemId: string;
workspaceSlug: string;
};
export function WorkItemAdditionalWidgetModals(_props: TWorkItemAdditionalWidgetModalsProps) {
return null;
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
export type TAdditionalActivityRoot = {
activityId: string;
showIssue?: boolean;
ends: "top" | "bottom" | undefined;
field: string | undefined;
};
export const AdditionalActivityRoot = observer(function AdditionalActivityRoot(_props: TAdditionalActivityRoot) {
return <></>;
});
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
// plane imports
export type TWorkItemAdditionalSidebarProperties = {
workItemId: string;
workItemTypeId: string | null;
projectId: string;
workspaceSlug: string;
isEditable: boolean;
isPeekView?: boolean;
};
export function WorkItemAdditionalSidebarProperties(_props: TWorkItemAdditionalSidebarProperties) {
return <></>;
}
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./issue-identifier";
export * from "./issue-properties-activity";
export * from "./issue-type-switcher";
export * from "./issue-type-activity";
export * from "./parent-select-root";
export * from "./issue-creator";
export * from "./additional-activity-root";

Some files were not shown because too many files have changed in this diff Show More