[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:
guru_sainath 2024-07-15 19:48:27 +05:30 committed by GitHub
parent 08d9e95a86
commit f5f3c4915f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 183 additions and 83 deletions

View file

@ -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,
}
)}
/>

View file

@ -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;

View file

@ -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>

View file

@ -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"

View file

@ -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.

View file

@ -0,0 +1 @@
export * from "./settings";

View 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,
},
},
},
};

View file

@ -0,0 +1 @@
export * from "./features";

View file

@ -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";

View 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)} />;
};

View file

@ -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>
) : (

View file

@ -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>
);
});

View file

@ -0,0 +1 @@
export * from "./settings";

View file

@ -0,0 +1 @@
export * from "ce/constants/project/settings/features";

View file

@ -0,0 +1 @@
export * from "./features";