Files
plane/web/ee/components/templates/settings/create-button.tsx
Nikhil 9b05964f8a [WEB-2963] feat: project templates (#2693)
* chore: add template

* chore: add page template and project template migrations

* chore: add migration file for project and page templates

* chore: page templates

* chore: update template

* feat: page templates

* chore: update model

* chore: asset copy

* chore: update page templates

* feat: add project template create/update endpoint

* improvement: update all templates endpoint

* chore: project template basic types and services

* feat: project template instance and store

* feat: project template store hooks

* chore: feature flags and minor improvements

* feat: project templates list settings

* feat: project templates

* chore: common constants across templates

* improvement: work item template util types

* improvement: moved all project related types to packages and re-exported them to avoid changes in files.

* feat: create project template form with all basic fields and attributes

* chore: add project creation bgtask from template

* chore: update project template configuration

* chore: update project template migrations

* chore: added migration for workspace connection disconnect_meta

* chore: update migration files

* chore: model changes

* chore: add project template validation

* chore: update template task

* chore: project templates endpoints

* fix: base serializers

* chore: add comment edit migrations

* chore: minor improvements and base template store fixes

* chore: minor improvements

* improvement: common template related components

* feat: project template features

* chore: minor formatting

* chore: minor translation updates

* feat: update project template form data

* chore: work item template util improvements

* feat: use project templates in create project modal

* chore: use template endpoint

* chore: update imports

* feat: project creation using templates

* fix: build error

* feat: work item type creation form

* chore: convert project work item types form data to required schema

* feat: update work item type form data extraction and sanitization

* chore: add project template default settings

* chore: update formatting

* improvement: sync all project level features

* revert: utils package build related changes

* feat: project epics in templates

* chore: update background task to handle member and intake creation

* improvement: epic name while creating template

* fix: api server

* revert: utils build

* feat: added work item labels while creating project templates

* chore: update project template task

* refactor: project state list components

* improvement: additional classNames for the group list item and state item

* fix: options

* fix: epic creations

* feat: add project states while creating project templates

* improvements: add empty state for labels

* chore: remove unused exports

* fix: build error

* chore: add intake setting schema

* feat: intake feature settings for project templates

* feat: intake settings and deploy board when creating project from template

* fix: feature children layout

* fix: form keydown events

* fix: template description

* fix: delete state button type

* improvement: UI enhancements related to background, border and shadow

* improvement: project templates dropdown

* improvement: remove empty templates list sections

* improvement: remove empty templates list sections

* improvement: enhance project level template list modularity

* improvement: minor component improvements

* chore: minor type changes

* improvement: template list loader

* improvement: project template form background color

* improvement: work item template form background color

* chore: add asset url validation and resolve pr comments

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2025-04-09 15:22:04 +05:30

130 lines
4.3 KiB
TypeScript

import { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { ChevronDown } from "lucide-react";
// plane imports
import {
ETemplateLevel,
ETemplateType,
EUserPermissionsLevel,
EUserProjectRoles,
EUserWorkspaceRoles,
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CustomMenu, Button, TButtonSizes } from "@plane/ui";
import {
getCreateUpdateTemplateSettingsPath,
getTemplateI18nLabel,
TCreateTemplateSettingsPathProps,
} from "@plane/utils";
// hooks
import { useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
type TCreateTemplatesButtonProps = {
workspaceSlug: string;
buttonI18nLabel?: string;
buttonSize?: TButtonSizes;
} & (
| {
projectId: string;
currentLevel: ETemplateLevel.PROJECT;
}
| {
currentLevel: ETemplateLevel.WORKSPACE;
}
);
type TCreateTemplateOption = {
i18n_label: string;
onClick: () => void;
availableForLevels: ETemplateLevel[];
};
export const CreateTemplatesButton = observer((props: TCreateTemplatesButtonProps) => {
// router
const router = useAppRouter();
// plane hooks
const { t } = useTranslation();
// store hooks
const { allowPermissions } = useUserPermissions();
// derived values
const hasAdminPermission =
props.currentLevel === ETemplateLevel.PROJECT
? allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT, props.workspaceSlug, props.projectId)
: allowPermissions([EUserWorkspaceRoles.ADMIN], EUserPermissionsLevel.WORKSPACE, props.workspaceSlug);
const CREATE_TEMPLATE_OPTIONS: TCreateTemplateOption[] = useMemo(() => {
const getCreateTemplateSettingsPathProps = (type: ETemplateType) => {
const createTemplatePathProps: TCreateTemplateSettingsPathProps = {
type,
workspaceSlug: props.workspaceSlug,
...("projectId" in props
? { level: ETemplateLevel.PROJECT, projectId: props.projectId }
: { level: ETemplateLevel.WORKSPACE }),
};
return createTemplatePathProps;
};
return [
{
i18n_label: getTemplateI18nLabel(ETemplateType.PROJECT),
onClick: () =>
router.push(getCreateUpdateTemplateSettingsPath(getCreateTemplateSettingsPathProps(ETemplateType.PROJECT))),
availableForLevels: [ETemplateLevel.WORKSPACE],
},
{
i18n_label: getTemplateI18nLabel(ETemplateType.WORK_ITEM),
onClick: () =>
router.push(getCreateUpdateTemplateSettingsPath(getCreateTemplateSettingsPathProps(ETemplateType.WORK_ITEM))),
availableForLevels: [ETemplateLevel.WORKSPACE, ETemplateLevel.PROJECT],
},
{
i18n_label: getTemplateI18nLabel(ETemplateType.PAGE),
onClick: () =>
router.push(getCreateUpdateTemplateSettingsPath(getCreateTemplateSettingsPathProps(ETemplateType.PAGE))),
availableForLevels: [],
},
];
}, [router, props]);
const getButtonLabel = useCallback(() => {
if (!hasAdminPermission)
return props.currentLevel === ETemplateLevel.PROJECT
? t("templates.settings.create_template.no_permission.project")
: t("templates.settings.create_template.no_permission.workspace");
return t(props.buttonI18nLabel || "templates.settings.create_template.label");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasAdminPermission, props.buttonI18nLabel, props.currentLevel]);
if (!hasAdminPermission) return null;
return (
<CustomMenu
customButton={
<Button
variant="primary"
size={props.buttonSize}
className="flex items-center justify-center gap-1.5"
disabled={!hasAdminPermission}
>
{getButtonLabel()}
{hasAdminPermission && <ChevronDown className="size-3.5" />}
</Button>
}
placement="bottom-end"
disabled={!hasAdminPermission}
closeOnSelect
>
{CREATE_TEMPLATE_OPTIONS.map((option) => {
const isAvailable = option.availableForLevels.includes(props.currentLevel);
if (!isAvailable) return null;
return (
<CustomMenu.MenuItem key={option.i18n_label} onClick={option.onClick} disabled={!isAvailable}>
{t(option.i18n_label)}
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
);
});