[WEB-1846] chore: integrated project other features enabled/disabled feature on project settings and updated the pro icon as a component (#5071)
* chore: integrated time traking enabled/disabled feature on project settings and updated the pro icon as a component * chore: Showing the toggle and disabled to make any operations on project features * chore: default exports in constants * chore: seperated isEnabled and isPro * chore: updated time traking key * chore: updated UI in project feature settings
This commit is contained in:
parent
08d9e95a86
commit
f5f3c4915f
15 changed files with 183 additions and 83 deletions
|
|
@ -26,7 +26,7 @@ const ToggleSwitch: React.FC<IToggleSwitchProps> = (props) => {
|
|||
"h-4 w-6": size === "sm",
|
||||
"h-5 w-8": size === "md",
|
||||
"bg-custom-primary-100": value,
|
||||
"cursor-not-allowed": disabled,
|
||||
"cursor-not-allowed bg-custom-background-80": disabled,
|
||||
},
|
||||
className
|
||||
)}
|
||||
|
|
@ -43,7 +43,7 @@ const ToggleSwitch: React.FC<IToggleSwitchProps> = (props) => {
|
|||
"translate-x-3": value && size === "sm",
|
||||
"translate-x-4": value && size === "md",
|
||||
"translate-x-0.5 bg-custom-background-90": !value,
|
||||
"cursor-not-allowed": disabled,
|
||||
"cursor-not-allowed bg-custom-background-90": disabled,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ const FeaturesSettingsPage = observer(() => {
|
|||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<section className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
|
||||
<div className="flex items-center border-b border-custom-border-100 py-3.5">
|
||||
<h3 className="text-xl font-medium">Features</h3>
|
||||
</div>
|
||||
<ProjectFeaturesList
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
|
|
@ -46,4 +43,4 @@ const FeaturesSettingsPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default FeaturesSettingsPage;
|
||||
export default FeaturesSettingsPage;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// icons
|
||||
import { Crown } from "lucide-react";
|
||||
// ui
|
||||
import { getButtonStyling } from "@plane/ui";
|
||||
// components
|
||||
import { ProIcon } from "@/components/common";
|
||||
// constants
|
||||
import { MARKETING_PRICING_PAGE_LINK } from "@/constants/common";
|
||||
import { WORKSPACE_ACTIVE_CYCLES_DETAILS } from "@/constants/cycle";
|
||||
|
|
@ -45,7 +45,7 @@ export const WorkspaceActiveCyclesUpgrade = observer(() => {
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Crown className="h-3.5 w-3.5" />
|
||||
<ProIcon className="h-3.5 w-3.5 text-white" />
|
||||
Upgrade
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Crown, Pen, Trash } from "lucide-react";
|
||||
import { Pen, Trash } from "lucide-react";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { ProIcon } from "@/components/common";
|
||||
|
||||
type TEstimateListItem = {
|
||||
estimateId: string;
|
||||
|
|
@ -22,7 +24,7 @@ export const EstimateListItemButtons: FC<TEstimateListItem> = observer((props) =
|
|||
tooltipContent={
|
||||
<div className="relative flex items-center gap-2">
|
||||
<div>Upgrade</div>
|
||||
<Crown size={12} className="text-amber-400" />
|
||||
<ProIcon className="w-3 h-3" />
|
||||
</div>
|
||||
}
|
||||
position="top"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { Crown } from "lucide-react";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { ProIcon } from "@/components/common";
|
||||
|
||||
export const IssueEmbedUpgradeCard: React.FC<any> = (props) => (
|
||||
<div
|
||||
className={`${props.selected ? "border-custom-primary-200 border-[2px]" : ""
|
||||
} w-full h-[100px] cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 shadow-custom-shadow-2xs`}
|
||||
className={`${
|
||||
props.selected ? "border-custom-primary-200 border-[2px]" : ""
|
||||
} w-full h-[100px] cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 shadow-custom-shadow-2xs`}
|
||||
>
|
||||
<h5 className="h-[20%] text-xs text-custom-text-300 p-2">
|
||||
{props.node?.attrs?.project_identifier}-{props?.node?.attrs?.sequence_id}
|
||||
|
|
@ -14,7 +16,7 @@ export const IssueEmbedUpgradeCard: React.FC<any> = (props) => (
|
|||
<div className="h-full backdrop-filter backdrop-blur-[30px] bg-custom-background-80 bg-opacity-30 flex items-center w-full justify-between gap-5 mt-2.5 pl-4 pr-5 py-3 max-md:max-w-full max-md:flex-wrap relative">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="rounded">
|
||||
<Crown className="m-2" size={16} color="#FFBA18" />
|
||||
<ProIcon className="m-2 w-4 h-4" />
|
||||
</div>
|
||||
<div className="text-custom-text text-sm">
|
||||
Embed and access issues in pages seamlessly, upgrade to plane pro now.
|
||||
|
|
|
|||
1
web/ce/constants/project/index.ts
Normal file
1
web/ce/constants/project/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./settings";
|
||||
82
web/ce/constants/project/settings/features.tsx
Normal file
82
web/ce/constants/project/settings/features.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { ReactNode } from "react";
|
||||
import { FileText, Inbox, Timer } from "lucide-react";
|
||||
import { ContrastIcon, DiceIcon, PhotoFilterIcon } from "@plane/ui";
|
||||
|
||||
export type TFeatureList = {
|
||||
[key: string]: {
|
||||
property: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: ReactNode;
|
||||
isPro: boolean;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type TProjectFeatures = {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
featureList: TFeatureList;
|
||||
};
|
||||
};
|
||||
|
||||
export const PROJECT_FEATURES_LIST: TProjectFeatures = {
|
||||
project_features: {
|
||||
title: "Features",
|
||||
featureList: {
|
||||
cycles: {
|
||||
property: "cycle_view",
|
||||
title: "Cycles",
|
||||
description: "Time-box issues and boost momentum, similar to sprints in scrum.",
|
||||
icon: <ContrastIcon className="h-5 w-5 flex-shrink-0 rotate-180 text-custom-text-300" />,
|
||||
isPro: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
modules: {
|
||||
property: "module_view",
|
||||
title: "Modules",
|
||||
description: "Group multiple issues together and track the progress.",
|
||||
icon: <DiceIcon width={20} height={20} className="flex-shrink-0 text-custom-text-300" />,
|
||||
isPro: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
views: {
|
||||
property: "issue_views_view",
|
||||
title: "Views",
|
||||
description: "Apply filters to issues and save them to analyse and investigate work.",
|
||||
icon: <PhotoFilterIcon className="h-5 w-5 flex-shrink-0 text-custom-text-300" />,
|
||||
isPro: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
pages: {
|
||||
property: "page_view",
|
||||
title: "Pages",
|
||||
description: "Document ideas, feature requirements, discussions within your project.",
|
||||
icon: <FileText className="h-5 w-5 flex-shrink-0 text-custom-text-300" />,
|
||||
isPro: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
inbox: {
|
||||
property: "inbox_view",
|
||||
title: "Inbox",
|
||||
description: "Capture external inputs, move valid issues to workflow.",
|
||||
icon: <Inbox className="h-5 w-5 flex-shrink-0 text-custom-text-300" />,
|
||||
isPro: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
project_others: {
|
||||
title: "Others",
|
||||
featureList: {
|
||||
is_time_tracking_enabled: {
|
||||
property: "is_time_tracking_enabled",
|
||||
title: "Time Tracking",
|
||||
description: "Keep the work logs of the users in track ",
|
||||
icon: <Timer className="h-5 w-5 flex-shrink-0 text-custom-text-300" />,
|
||||
isPro: true,
|
||||
isEnabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
1
web/ce/constants/project/settings/index.ts
Normal file
1
web/ce/constants/project/settings/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./features";
|
||||
|
|
@ -4,4 +4,5 @@ export * from "./latest-feature-block";
|
|||
export * from "./breadcrumb-link";
|
||||
export * from "./logo-spinner";
|
||||
export * from "./logo";
|
||||
export * from "./pro-icon";
|
||||
export * from "./count-chip";
|
||||
|
|
|
|||
16
web/core/components/common/pro-icon.tsx
Normal file
16
web/core/components/common/pro-icon.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Crown } from "lucide-react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
type TProIcon = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const ProIcon: FC<TProIcon> = (props) => {
|
||||
const { className } = props;
|
||||
|
||||
return <Crown className={cn("inline-block h-3.5 w-3.5 text-amber-400", className)} />;
|
||||
};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Crown, Info } from "lucide-react";
|
||||
import { Info } from "lucide-react";
|
||||
import { TEstimateSystemKeys } from "@plane/types";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { ProIcon } from "@/components/common";
|
||||
import { RadioInput } from "@/components/estimates";
|
||||
// plane web constants
|
||||
import { ESTIMATE_SYSTEMS } from "@/plane-web/constants/estimates";
|
||||
|
|
@ -39,7 +40,7 @@ export const EstimateCreateStageOne: FC<TEstimateCreateStageOne> = (props) => {
|
|||
<div className="relative flex items-center gap-2 cursor-no-drop text-custom-text-300">
|
||||
{ESTIMATE_SYSTEMS[currentSystem]?.name}
|
||||
<Tooltip tooltipContent={"upgrade"}>
|
||||
<Crown size={12} className="text-amber-400" />
|
||||
<ProIcon className="w-3 h-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { FileText, Inbox } from "lucide-react";
|
||||
// types
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { ContrastIcon, DiceIcon, PhotoFilterIcon, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||
import { ToggleSwitch, Tooltip, setPromiseToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useProject, useUser } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { UpgradeBadge } from "@/plane-web/components/workspace";
|
||||
// plane web constants
|
||||
import { PROJECT_FEATURES_LIST } from "@/plane-web/constants/project/settings";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -16,39 +17,6 @@ type Props = {
|
|||
isAdmin: boolean;
|
||||
};
|
||||
|
||||
const PROJECT_FEATURES_LIST = [
|
||||
{
|
||||
title: "Cycles",
|
||||
description: "Time-box issues and boost momentum, similar to sprints in scrum.",
|
||||
icon: <ContrastIcon className="h-4 w-4 flex-shrink-0 rotate-180 text-purple-500" />,
|
||||
property: "cycle_view",
|
||||
},
|
||||
{
|
||||
title: "Modules",
|
||||
description: "Group multiple issues together and track the progress.",
|
||||
icon: <DiceIcon width={16} height={16} className="flex-shrink-0 text-red-500" />,
|
||||
property: "module_view",
|
||||
},
|
||||
{
|
||||
title: "Views",
|
||||
description: "Apply filters to issues and save them to analyse and investigate work.",
|
||||
icon: <PhotoFilterIcon className="h-4 w-4 flex-shrink-0 text-cyan-500" />,
|
||||
property: "issue_views_view",
|
||||
},
|
||||
{
|
||||
title: "Pages",
|
||||
description: "Document ideas, feature requirements, discussions within your project.",
|
||||
icon: <FileText className="h-4 w-4 flex-shrink-0 text-red-400" />,
|
||||
property: "page_view",
|
||||
},
|
||||
{
|
||||
title: "Inbox",
|
||||
description: "Capture external inputs, move valid issues to workflow.",
|
||||
icon: <Inbox className="h-4 w-4 flex-shrink-0 text-fuchsia-500" />,
|
||||
property: "inbox_view",
|
||||
},
|
||||
];
|
||||
|
||||
export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, isAdmin } = props;
|
||||
// store hooks
|
||||
|
|
@ -58,9 +26,20 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
// derived values
|
||||
const currentProjectDetails = getProjectById(projectId);
|
||||
|
||||
const handleSubmit = async (formData: Partial<IProject>) => {
|
||||
const handleSubmit = async (featureKey: string, featureProperty: string) => {
|
||||
if (!workspaceSlug || !projectId || !currentProjectDetails) return;
|
||||
const updateProjectPromise = updateProject(workspaceSlug, projectId, formData);
|
||||
|
||||
// capturing event
|
||||
captureEvent(`Toggle ${featureKey}`, {
|
||||
enabled: !currentProjectDetails?.[featureProperty as keyof IProject],
|
||||
element: "Project settings feature page",
|
||||
});
|
||||
|
||||
// making the request to update the project feature
|
||||
const settingsPayload = {
|
||||
[featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject],
|
||||
};
|
||||
const updateProjectPromise = updateProject(workspaceSlug, projectId, settingsPayload);
|
||||
setPromiseToast(updateProjectPromise, {
|
||||
loading: "Updating project feature...",
|
||||
success: {
|
||||
|
|
@ -77,35 +56,50 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
if (!currentUser) return <></>;
|
||||
|
||||
return (
|
||||
<div className="mx-4">
|
||||
{PROJECT_FEATURES_LIST.map((feature) => (
|
||||
<div
|
||||
key={feature.property}
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 border-b border-custom-border-100 bg-custom-background-100 pb-2 pt-4 last:border-b-0"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center rounded bg-custom-primary-50/10 p-3">{feature.icon}</div>
|
||||
<div className="">
|
||||
<h4 className="text-sm font-medium leading-5">{feature.title}</h4>
|
||||
<p className="text-sm leading-5 tracking-tight text-custom-text-300">{feature.description}</p>
|
||||
<div className="mx-4 space-y-6">
|
||||
{Object.keys(PROJECT_FEATURES_LIST).map((featureSectionKey) => {
|
||||
const feature = PROJECT_FEATURES_LIST[featureSectionKey];
|
||||
return (
|
||||
<div key={featureSectionKey} className="">
|
||||
<div className="flex items-center border-b border-custom-border-100 py-3.5">
|
||||
<h3 className="text-xl font-medium">{feature.title}</h3>
|
||||
</div>
|
||||
{Object.keys(feature.featureList).map((featureItemKey) => {
|
||||
const featureItem = feature.featureList[featureItemKey];
|
||||
return (
|
||||
<div
|
||||
key={featureItemKey}
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 border-b border-custom-border-100 bg-custom-background-100 pb-2 pt-4 last:border-b-0"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center rounded bg-custom-background-90 p-3">
|
||||
{featureItem.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-sm font-medium leading-5">{featureItem.title}</h4>
|
||||
{featureItem.isPro && (
|
||||
<Tooltip tooltipContent="Pro feature" position="top">
|
||||
<UpgradeBadge />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm leading-5 tracking-tight text-custom-text-300">{featureItem.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToggleSwitch
|
||||
value={Boolean(currentProjectDetails?.[featureItem.property as keyof IProject])}
|
||||
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
|
||||
disabled={!featureItem.isEnabled || !isAdmin}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={Boolean(currentProjectDetails?.[feature.property as keyof IProject])}
|
||||
onChange={() => {
|
||||
captureEvent(`Toggle ${feature.title.toLowerCase()}`, {
|
||||
enabled: !currentProjectDetails?.[feature.property as keyof IProject],
|
||||
element: "Project settings feature page",
|
||||
});
|
||||
handleSubmit({
|
||||
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],
|
||||
});
|
||||
}}
|
||||
disabled={!isAdmin}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
1
web/ee/constants/project/index.ts
Normal file
1
web/ee/constants/project/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./settings";
|
||||
1
web/ee/constants/project/settings/features.tsx
Normal file
1
web/ee/constants/project/settings/features.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/constants/project/settings/features";
|
||||
1
web/ee/constants/project/settings/index.ts
Normal file
1
web/ee/constants/project/settings/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./features";
|
||||
Loading…
Add table
Add a link
Reference in a new issue