snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const AutomationsProjectSettingsHeader = observer(function AutomationsProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.automations;
const Icon = PROJECT_SETTINGS_ICONS.automations;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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";
import { Outlet } from "react-router";
function AutomationsListLayout() {
return <Outlet />;
}
export default observer(AutomationsListLayout);

View File

@@ -0,0 +1,75 @@
/**
* 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 imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IProject } from "@plane/types";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automation";
import { PageHead } from "@/components/core/page-title";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { AutomationsProjectSettingsHeader } from "./header";
function AutomationSettingsPage({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails: projectDetails, updateProject } = useProject();
const { t } = useTranslation();
// derived values
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
const handleChange = async (formData: Partial<IProject>) => {
if (!projectDetails) return;
try {
await updateProject(workspaceSlug, projectId, formData);
} catch {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again.",
});
}
};
// derived values
const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined;
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<AutomationsProjectSettingsHeader />} hugging>
<PageHead title={pageTitle} />
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<SettingsHeading
title={t("project_settings.automations.heading")}
description={t("project_settings.automations.description")}
/>
<div className="mt-6">
<AutoArchiveAutomation handleChange={handleChange} />
<AutoCloseAutomation handleChange={handleChange} />
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(AutomationSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const EstimatesProjectSettingsHeader = observer(function EstimatesProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.estimates;
const Icon = PROJECT_SETTINGS_ICONS.estimates;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -0,0 +1,45 @@
/**
* 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 { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { EstimateRoot } from "@/components/estimates";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { EstimatesProjectSettingsHeader } from "./header";
function EstimatesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store
const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<EstimatesProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<div className={`w-full ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}>
<EstimateRoot workspaceSlug={workspaceSlug} projectId={projectId} isAdmin={canPerformProjectAdminActions} />
</div>
</SettingsContentWrapper>
);
}
export default observer(EstimatesSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const FeaturesCyclesProjectSettingsHeader = observer(function FeaturesCyclesProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.features_cycles;
const Icon = PROJECT_SETTINGS_ICONS.features_cycles;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { ProjectSettingsFeatureControlItem } from "@/components/settings/project/content/feature-control-item";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { FeaturesCyclesProjectSettingsHeader } from "./header";
import { SettingsHeading } from "@/components/settings/heading";
function FeaturesCyclesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
// translation
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name
? `${currentProjectDetails?.name} settings - ${t("project_settings.features.cycles.short_title")}`
: undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<FeaturesCyclesProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<section className="w-full">
<SettingsHeading
title={t("project_settings.features.cycles.title")}
description={t("project_settings.features.cycles.description")}
/>
<div className="mt-7">
<ProjectSettingsFeatureControlItem
title={t("project_settings.features.cycles.toggle_title")}
description={t("project_settings.features.cycles.toggle_description")}
featureProperty="cycle_view"
projectId={projectId}
value={!!currentProjectDetails?.cycle_view}
workspaceSlug={workspaceSlug}
/>
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(FeaturesCyclesSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const FeaturesIntakeProjectSettingsHeader = observer(function FeaturesIntakeProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.features_intake;
const Icon = PROJECT_SETTINGS_ICONS.features_intake;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
import { ProjectSettingsFeatureControlItem } from "@/components/settings/project/content/feature-control-item";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { FeaturesIntakeProjectSettingsHeader } from "./header";
function FeaturesIntakeSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
// translation
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name
? `${currentProjectDetails?.name} settings - ${t("project_settings.features.intake.short_title")}`
: undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<FeaturesIntakeProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<section className="w-full">
<SettingsHeading
title={t("project_settings.features.intake.title")}
description={t("project_settings.features.intake.description")}
/>
<div className="mt-7">
<ProjectSettingsFeatureControlItem
title={t("project_settings.features.intake.toggle_title")}
description={t("project_settings.features.intake.toggle_description")}
featureProperty="inbox_view"
projectId={projectId}
value={!!currentProjectDetails?.inbox_view}
workspaceSlug={workspaceSlug}
/>
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(FeaturesIntakeSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const FeaturesModulesProjectSettingsHeader = observer(function FeaturesModulesProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.features_modules;
const Icon = PROJECT_SETTINGS_ICONS.features_modules;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
import { ProjectSettingsFeatureControlItem } from "@/components/settings/project/content/feature-control-item";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { FeaturesModulesProjectSettingsHeader } from "./header";
function FeaturesModulesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
// translation
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name
? `${currentProjectDetails?.name} settings - ${t("project_settings.features.modules.short_title")}`
: undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<FeaturesModulesProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<section className="w-full">
<SettingsHeading
title={t("project_settings.features.modules.title")}
description={t("project_settings.features.modules.description")}
/>
<div className="mt-7">
<ProjectSettingsFeatureControlItem
title={t("project_settings.features.modules.toggle_title")}
description={t("project_settings.features.modules.toggle_description")}
featureProperty="module_view"
projectId={projectId}
value={!!currentProjectDetails?.module_view}
workspaceSlug={workspaceSlug}
/>
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(FeaturesModulesSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const FeaturesPagesProjectSettingsHeader = observer(function FeaturesPagesProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.features_pages;
const Icon = PROJECT_SETTINGS_ICONS.features_pages;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
import { ProjectSettingsFeatureControlItem } from "@/components/settings/project/content/feature-control-item";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { FeaturesPagesProjectSettingsHeader } from "./header";
function FeaturesPagesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
// translation
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name
? `${currentProjectDetails?.name} settings - ${t("project_settings.features.pages.short_title")}`
: undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<FeaturesPagesProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<section className="w-full">
<SettingsHeading
title={t("project_settings.features.pages.title")}
description={t("project_settings.features.pages.description")}
/>
<div className="mt-7">
<ProjectSettingsFeatureControlItem
title={t("project_settings.features.pages.toggle_title")}
description={t("project_settings.features.pages.toggle_description")}
featureProperty="page_view"
projectId={projectId}
value={!!currentProjectDetails?.page_view}
workspaceSlug={workspaceSlug}
/>
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(FeaturesPagesSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const FeaturesViewsProjectSettingsHeader = observer(function FeaturesViewsProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.features_views;
const Icon = PROJECT_SETTINGS_ICONS.features_views;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
import { ProjectSettingsFeatureControlItem } from "@/components/settings/project/content/feature-control-item";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { FeaturesViewsProjectSettingsHeader } from "./header";
function FeaturesViewsSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails } = useProject();
// translation
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name
? `${currentProjectDetails?.name} settings - ${t("project_settings.features.views.short_title")}`
: undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<FeaturesViewsProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<section className="w-full">
<SettingsHeading
title={t("project_settings.features.views.title")}
description={t("project_settings.features.views.description")}
/>
<div className="mt-7">
<ProjectSettingsFeatureControlItem
title={t("project_settings.features.views.toggle_title")}
description={t("project_settings.features.views.toggle_description")}
featureProperty="issue_views_view"
projectId={projectId}
value={!!currentProjectDetails?.issue_views_view}
workspaceSlug={workspaceSlug}
/>
</div>
</section>
</SettingsContentWrapper>
);
}
export default observer(FeaturesViewsSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const GeneralProjectSettingsHeader = observer(function GeneralProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.general;
const Icon = PROJECT_SETTINGS_ICONS.general;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const LabelsProjectSettingsHeader = observer(function LabelsProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.labels;
const Icon = PROJECT_SETTINGS_ICONS.labels;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect, useRef } from "react";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
import { observer } from "mobx-react";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { ProjectSettingsLabelList } from "@/components/labels";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import { LabelsProjectSettingsHeader } from "./header";
function LabelsSettingsPage() {
// store hooks
const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Labels` : undefined;
const scrollableContainerRef = useRef<HTMLDivElement | null>(null);
// derived values
const canPerformProjectMemberActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
// Enable Auto Scroll for Labels list
useEffect(() => {
const element = scrollableContainerRef.current;
if (!element) return;
return combine(
autoScrollForElements({
element,
})
);
}, []);
if (workspaceUserInfo && !canPerformProjectMemberActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<LabelsProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<div ref={scrollableContainerRef} className="size-full">
<ProjectSettingsLabelList />
</div>
</SettingsContentWrapper>
);
}
export default observer(LabelsSettingsPage);

View File

@@ -0,0 +1,44 @@
/**
* 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 { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// components
import { getProjectActivePath } from "@/components/settings/helper";
import { SettingsMobileNav } from "@/components/settings/mobile/nav";
// layouts
import { ProjectAuthWrapper } from "@/layouts/auth-layout/project-wrapper";
// types
import type { Route } from "./+types/layout";
import { ProjectSettingsSidebarRoot } from "@/components/settings/project/sidebar";
function ProjectDetailSettingsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// router
const pathname = usePathname();
return (
<>
<SettingsMobileNav
hamburgerContent={(props) => <ProjectSettingsSidebarRoot {...props} projectId={projectId} />}
activePath={getProjectActivePath(pathname) || ""}
/>
<div className="inset-y-0 flex h-full w-full flex-row">
<div className="relative flex size-full">
<div className="hidden h-full shrink-0 md:block">
<ProjectSettingsSidebarRoot projectId={projectId} />
</div>
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<Outlet />
</ProjectAuthWrapper>
</div>
</div>
</>
);
}
export default observer(ProjectDetailSettingsLayout);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const MembersProjectSettingsHeader = observer(function MembersProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.members;
const Icon = PROJECT_SETTINGS_ICONS.members;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -0,0 +1,56 @@
/**
* 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 imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { ProjectMemberList } from "@/components/project/member-list";
import { ProjectSettingsMemberDefaults } from "@/components/project/project-settings-member-defaults";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { MembersProjectSettingsHeader } from "./header";
function MembersSettingsPage({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId } = params;
// plane hooks
const { t } = useTranslation();
// store hooks
const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
const isProjectMemberOrAdmin = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const canPerformProjectMemberActions = isProjectMemberOrAdmin || isWorkspaceAdmin;
if (workspaceUserInfo && !canPerformProjectMemberActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<MembersProjectSettingsHeader />} hugging>
<PageHead title={pageTitle} />
<SettingsHeading title={t("common.members")} />
<ProjectSettingsMemberDefaults projectId={projectId} workspaceSlug={workspaceSlug} />
<ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} />
</SettingsContentWrapper>
);
}
export default observer(MembersSettingsPage);

View File

@@ -0,0 +1,54 @@
/**
* 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 imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
// components
import { PageHead } from "@/components/core/page-title";
import { ProjectDetailsForm } from "@/components/project/form";
import { ProjectDetailsFormLoader } from "@/components/project/form-loader";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { GeneralProjectSettingsHeader } from "./header";
import { GeneralProjectSettingsControlSection } from "@/components/project/settings/control-section";
function ProjectSettingsPage({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId } = params;
// store hooks
const { currentProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions();
// derived values
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
return (
<SettingsContentWrapper header={<GeneralProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<div className={`w-full ${isAdmin ? "" : "opacity-60"}`}>
{currentProjectDetails ? (
<ProjectDetailsForm
project={currentProjectDetails}
workspaceSlug={workspaceSlug}
projectId={projectId}
isAdmin={isAdmin}
/>
) : (
<ProjectDetailsFormLoader />
)}
{isAdmin && <GeneralProjectSettingsControlSection projectId={projectId} />}
</div>
</SettingsContentWrapper>
);
}
export default observer(ProjectSettingsPage);

View File

@@ -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 { observer } from "mobx-react";
// plane imports
import { PROJECT_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SettingsPageHeader } from "@/components/settings/page-header";
import { PROJECT_SETTINGS_ICONS } from "@/components/settings/project/sidebar/item-icon";
export const StatesProjectSettingsHeader = observer(function StatesProjectSettingsHeader() {
// translation
const { t } = useTranslation();
// derived values
const settingsDetails = PROJECT_SETTINGS.states;
const Icon = PROJECT_SETTINGS_ICONS.states;
return (
<SettingsPageHeader
leftItem={
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={t(settingsDetails.i18n_label)}
icon={<Icon className="size-4 text-tertiary" />}
/>
}
/>
</Breadcrumbs>
</div>
}
/>
);
});

View File

@@ -0,0 +1,59 @@
/**
* 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 { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// components
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title";
import { ProjectStateRoot } from "@/components/project-states";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading";
// hook
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
// local imports
import type { Route } from "./+types/page";
import { StatesProjectSettingsHeader } from "./header";
function StatesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// store
const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { t } = useTranslation();
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - States` : undefined;
// derived values
const canPerformProjectMemberActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
if (workspaceUserInfo && !canPerformProjectMemberActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
}
return (
<SettingsContentWrapper header={<StatesProjectSettingsHeader />}>
<PageHead title={pageTitle} />
<div className="w-full">
<SettingsHeading
title={t("project_settings.states.heading")}
description={t("project_settings.states.description")}
/>
<div className="mt-6">
<ProjectStateRoot workspaceSlug={workspaceSlug} projectId={projectId} />
</div>
</div>
</SettingsContentWrapper>
);
}
export default observer(StatesSettingsPage);

View File

@@ -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 { useEffect } from "react";
import { observer } from "mobx-react";
import { Outlet } from "react-router";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
// types
import type { Route } from "./+types/layout";
function ProjectSettingsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// router
const router = useAppRouter();
// store hooks
const { joinedProjectIds } = useProject();
useEffect(() => {
if (projectId) return;
if (joinedProjectIds.length > 0) {
router.push(`/${workspaceSlug}/settings/projects/${joinedProjectIds[0]}`);
}
}, [joinedProjectIds, router, workspaceSlug, projectId]);
return <Outlet />;
}
export default observer(ProjectSettingsLayout);

View File

@@ -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.
*/
import { observer } from "mobx-react";
import Link from "next/link";
import { useTheme } from "next-themes";
// plane imports
import { Button, getButtonStyling } from "@plane/propel/button";
import { cn } from "@plane/utils";
// assets
import ProjectDarkEmptyState from "@/app/assets/empty-state/project-settings/no-projects-dark.png?url";
import ProjectLightEmptyState from "@/app/assets/empty-state/project-settings/no-projects-light.png?url";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
function ProjectSettingsPage() {
// store hooks
const { resolvedTheme } = useTheme();
const { toggleCreateProjectModal } = useCommandPalette();
// derived values
const resolvedPath = resolvedTheme === "dark" ? ProjectDarkEmptyState : ProjectLightEmptyState;
return (
<div className="mx-auto flex h-full max-w-[480px] flex-col items-center justify-center gap-4">
<img src={resolvedPath} alt="No projects yet" />
<div className="text-16 font-semibold text-tertiary">No projects yet</div>
<div className="text-center text-13 text-tertiary">
Projects act as the foundation for goal-driven work. They let you manage your teams, tasks, and everything you
need to get things done.
</div>
<div className="flex gap-2">
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("secondary", "base"))}>
Learn more about projects
</Link>
<Button onClick={() => toggleCreateProjectModal(true)}>Start your first project</Button>
</div>
</div>
);
}
export default observer(ProjectSettingsPage);