[WEB-1960]: chore: upgrade to plane paid plans modal. (#5149)
This commit is contained in:
parent
281948c1ce
commit
cfc70622d6
9 changed files with 328 additions and 29 deletions
|
|
@ -1,19 +1,35 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { Button, Tooltip } from "@plane/ui";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
// local components
|
||||
import { PaidPlanUpgradeModal } from "./upgrade";
|
||||
|
||||
export const WorkspaceEditionBadge = observer(() => {
|
||||
const { isMobile } = usePlatformOS();
|
||||
// states
|
||||
const [isPaidPlanPurchaseModalOpen, setIsPaidPlanPurchaseModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Tooltip tooltipContent={`Version: v${packageJson.version}`} isMobile={isMobile}>
|
||||
<div className="w-full cursor-default rounded-md bg-green-500/10 px-2 py-1 text-center text-xs font-medium text-green-500 outline-none leading-6">
|
||||
Community
|
||||
</div>
|
||||
</Tooltip>
|
||||
<>
|
||||
<PaidPlanUpgradeModal
|
||||
isOpen={isPaidPlanPurchaseModalOpen}
|
||||
handleClose={() => setIsPaidPlanPurchaseModalOpen(false)}
|
||||
/>
|
||||
<Tooltip tooltipContent={`Version: v${packageJson.version}`} isMobile={isMobile}>
|
||||
<Button
|
||||
tabIndex={-1}
|
||||
variant="accent-primary"
|
||||
className="w-full cursor-pointer rounded-2xl px-4 py-1.5 text-center text-sm font-medium outline-none"
|
||||
onClick={() => setIsPaidPlanPurchaseModalOpen(true)}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
3
web/ce/components/workspace/upgrade/index.tsx
Normal file
3
web/ce/components/workspace/upgrade/index.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./pro-plan-upgrade";
|
||||
export * from "./one-plan-upgrade";
|
||||
export * from "./paid-plans-upgrade-modal";
|
||||
55
web/ce/components/workspace/upgrade/one-plan-upgrade.tsx
Normal file
55
web/ce/components/workspace/upgrade/one-plan-upgrade.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { FC } from "react";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
export type OnePlanUpgradeProps = {
|
||||
features: string[];
|
||||
verticalFeatureList?: boolean;
|
||||
extraFeatures?: string | React.ReactNode;
|
||||
};
|
||||
|
||||
export const OnePlanUpgrade: FC<OnePlanUpgradeProps> = (props) => {
|
||||
const { features, verticalFeatureList = false, extraFeatures } = props;
|
||||
// env
|
||||
const PLANE_ONE_PAYMENT_URL = process.env.NEXT_PUBLIC_PLANE_ONE_PAYMENT_URL ?? "https://plane.so/one";
|
||||
|
||||
return (
|
||||
<div className="py-4 px-2 border border-custom-border-90 rounded-xl bg-custom-background-90">
|
||||
<div className="flex w-full justify-center h-10" />
|
||||
<div className="pt-6 pb-4 text-center font-semibold">
|
||||
<div className="text-2xl">Plane One</div>
|
||||
<div className="text-3xl">$799</div>
|
||||
<div className="text-sm text-custom-text-300">for two years’ support and updates</div>
|
||||
</div>
|
||||
<div className="flex justify-center w-full">
|
||||
<a
|
||||
href={PLANE_ONE_PAYMENT_URL}
|
||||
target="_blank"
|
||||
className="relative inline-flex items-center justify-center w-56 px-4 py-2.5 text-white text-sm font-medium border border-[#525252] bg-gradient-to-r from-[#353535] via-[#1111118C] to-[#21212153] rounded-lg focus:outline-none"
|
||||
>
|
||||
Upgrade to One
|
||||
</a>
|
||||
</div>
|
||||
<div className="px-2 pt-6 pb-2">
|
||||
<div className="p-2 text-sm font-semibold">Everything in Free +</div>
|
||||
<ul className="w-full grid grid-cols-12 gap-x-4">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className={cn("col-span-12 relative rounded-md p-2 flex", {
|
||||
"sm:col-span-6": !verticalFeatureList,
|
||||
})}
|
||||
>
|
||||
<p className="w-full text-sm font-medium leading-5 flex items-center">
|
||||
<CheckCircle className="h-4 w-4 mr-4 text-custom-text-300 flex-shrink-0" />
|
||||
<span className="text-custom-text-200 truncate">{feature}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{extraFeatures && <div>{extraFeatures}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
113
web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx
Normal file
113
web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { FC } from "react";
|
||||
// types
|
||||
import { CircleX } from "lucide-react";
|
||||
// services
|
||||
import { EModalWidth, ModalCore } from "@plane/ui";
|
||||
// plane web components
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// local components
|
||||
import { OnePlanUpgrade } from "./one-plan-upgrade";
|
||||
import { ProPlanUpgrade } from "./pro-plan-upgrade";
|
||||
|
||||
const PRO_PLAN_FEATURES = [
|
||||
"More Cycles features",
|
||||
"Full Time Tracking + Bulk Ops",
|
||||
"Workflow manager",
|
||||
"Automations",
|
||||
"Popular integrations",
|
||||
"Plane AI",
|
||||
];
|
||||
|
||||
const ONE_PLAN_FEATURES = [
|
||||
"OIDC + SAML for SSO",
|
||||
"Active Cycles",
|
||||
"Real-time collab + public views and page",
|
||||
"Link pages in issues and vice-versa",
|
||||
"Time-tracking + limited bulk ops",
|
||||
"Docker, Kubernetes and more",
|
||||
];
|
||||
|
||||
const FREE_PLAN_UPGRADE_FEATURES = [
|
||||
"OIDC + SAML for SSO",
|
||||
"Time tracking and bulk ops",
|
||||
"Integrations",
|
||||
"Public views and pages",
|
||||
];
|
||||
|
||||
export type PaidPlanUpgradeModalProps = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const PaidPlanUpgradeModal: FC<PaidPlanUpgradeModalProps> = (props) => {
|
||||
const { isOpen, handleClose } = props;
|
||||
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} handleClose={handleClose} width={EModalWidth.VIXL} className="rounded-2xl">
|
||||
<div className="p-10 max-h-[90vh] overflow-auto">
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
<div className="text-3xl font-bold leading-8 flex">Upgrade to a paid plan and unlock missing features.</div>
|
||||
<div className="mt-4 mb-12">
|
||||
<p className="text-sm mb-4 pr-8 text-custom-text-100">
|
||||
Active Cycles, time tracking, bulk ops, and other features are waiting for you on one of our paid plans.
|
||||
Upgrade today to unlock features your teams need yesterday.
|
||||
</p>
|
||||
</div>
|
||||
{/* Free plan details */}
|
||||
<div className="py-4 px-2 border border-custom-border-90 rounded-xl">
|
||||
<div className="py-2 px-3">
|
||||
<span className="px-2 py-1 bg-custom-background-90 text-sm text-custom-text-300 font-medium rounded">
|
||||
Your plan
|
||||
</span>
|
||||
</div>
|
||||
<div className="px-4 py-2 font-semibold">
|
||||
<div className="text-3xl">Free</div>
|
||||
<div className="text-sm text-custom-text-300">$0 a user per month</div>
|
||||
</div>
|
||||
<div className="px-2 pt-2 pb-3">
|
||||
<ul className="w-full grid grid-cols-12 gap-x-4">
|
||||
{FREE_PLAN_UPGRADE_FEATURES.map((feature) => (
|
||||
<li key={feature} className={cn("col-span-12 relative rounded-md p-2 flex")}>
|
||||
<p className="w-full text-sm font-medium leading-5 flex items-center">
|
||||
<CircleX className="h-4 w-4 mr-4 text-red-500 flex-shrink-0" />
|
||||
<span className="text-custom-text-200 truncate">{feature}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
<ProPlanUpgrade
|
||||
basePlan="One"
|
||||
features={PRO_PLAN_FEATURES}
|
||||
verticalFeatureList
|
||||
extraFeatures={
|
||||
<p className="pt-1.5 text-center text-xs text-custom-primary-200 font-semibold underline">
|
||||
<a href="https://plane.so/pro" target="_blank">
|
||||
See full features list
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
<OnePlanUpgrade
|
||||
features={ONE_PLAN_FEATURES}
|
||||
verticalFeatureList
|
||||
extraFeatures={
|
||||
<p className="pt-1.5 text-center text-xs text-custom-primary-200 font-semibold underline">
|
||||
<a href="https://plane.so/one" target="_blank">
|
||||
See full features list
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalCore>
|
||||
);
|
||||
};
|
||||
111
web/ce/components/workspace/upgrade/pro-plan-upgrade.tsx
Normal file
111
web/ce/components/workspace/upgrade/pro-plan-upgrade.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { FC, useState } from "react";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
export type ProPlanUpgradeProps = {
|
||||
basePlan: "Free" | "One";
|
||||
features: string[];
|
||||
verticalFeatureList?: boolean;
|
||||
extraFeatures?: string | React.ReactNode;
|
||||
};
|
||||
|
||||
type TProPiceFrequency = "month" | "year";
|
||||
|
||||
type TProPlanPrice = {
|
||||
key: string;
|
||||
price: string;
|
||||
recurring: TProPiceFrequency;
|
||||
};
|
||||
|
||||
const PRO_PLAN_PRICES: TProPlanPrice[] = [
|
||||
{ key: "monthly", price: "$7", recurring: "month" },
|
||||
{ key: "yearly", price: "$5", recurring: "year" },
|
||||
];
|
||||
|
||||
export const ProPlanUpgrade: FC<ProPlanUpgradeProps> = (props) => {
|
||||
const { basePlan, features, verticalFeatureList = false, extraFeatures } = props;
|
||||
// states
|
||||
const [selectedPlan, setSelectedPlan] = useState<TProPiceFrequency>("month");
|
||||
// env
|
||||
const PRO_PLAN_MONTHLY_PAYMENT_URL = process.env.NEXT_PUBLIC_PRO_PLAN_MONTHLY_PAYMENT_URL ?? "https://plane.so/pro";
|
||||
const PRO_PLAN_YEARLY_PAYMENT_URL = process.env.NEXT_PUBLIC_PRO_PLAN_YEARLY_PAYMENT_URL ?? "https://plane.so/pro";
|
||||
|
||||
return (
|
||||
<div className="py-4 px-2 border border-custom-primary-200/30 rounded-xl bg-custom-primary-200/5">
|
||||
<Tab.Group>
|
||||
<div className="flex w-full justify-center h-10">
|
||||
<Tab.List className="flex space-x-1 rounded-lg bg-custom-primary-200/10 p-1 w-60">
|
||||
{PRO_PLAN_PRICES.map((price: TProPlanPrice) => (
|
||||
<Tab
|
||||
key={price.key}
|
||||
className={({ selected }) =>
|
||||
cn(
|
||||
"w-full rounded-lg py-1.5 text-sm font-medium leading-5",
|
||||
selected
|
||||
? "bg-custom-background-100 text-custom-primary-300 shadow"
|
||||
: "hover:bg-custom-primary-100/5 text-custom-text-300 hover:text-custom-text-200"
|
||||
)
|
||||
}
|
||||
onClick={() => setSelectedPlan(price.recurring)}
|
||||
>
|
||||
<>
|
||||
{price.recurring === "month" && ("Monthly" as string)}
|
||||
{price.recurring === "year" && ("Yearly" as string)}
|
||||
{price.recurring === "year" && (
|
||||
<span className="bg-gradient-to-r from-[#C78401] to-[#896828] text-white rounded-full px-2 py-1 ml-1 text-xs">
|
||||
-28%
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
</div>
|
||||
<Tab.Panels>
|
||||
{PRO_PLAN_PRICES.map((price: TProPlanPrice) => (
|
||||
<Tab.Panel key={price.key}>
|
||||
<div className="pt-6 pb-4 text-center font-semibold">
|
||||
<div className="text-2xl">Plane Pro</div>
|
||||
<div className="text-3xl">
|
||||
{price.recurring === "month" && "$7"}
|
||||
{price.recurring === "year" && "$5"}
|
||||
</div>
|
||||
<div className="text-sm text-custom-text-300">a user per month</div>
|
||||
</div>
|
||||
<div className="flex justify-center w-full">
|
||||
<a
|
||||
href={selectedPlan === "month" ? PRO_PLAN_MONTHLY_PAYMENT_URL : PRO_PLAN_YEARLY_PAYMENT_URL}
|
||||
target="_blank"
|
||||
className="relative inline-flex items-center justify-center w-56 px-4 py-2.5 text-white text-sm font-medium border border-[#E9DBBF99]/60 bg-gradient-to-r from-[#C78401] to-[#896828] rounded-lg focus:outline-none"
|
||||
>
|
||||
Upgrade to Pro
|
||||
</a>
|
||||
</div>
|
||||
<div className="px-2 pt-6 pb-2">
|
||||
<div className="p-2 text-sm font-semibold">{`Everything in ${basePlan} +`}</div>
|
||||
<ul className="grid grid-cols-12 gap-x-4">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className={cn("col-span-12 relative rounded-md p-2 flex", {
|
||||
"sm:col-span-6": !verticalFeatureList,
|
||||
})}
|
||||
>
|
||||
<p className="w-full text-sm font-medium leading-5 flex items-center line-clamp-1">
|
||||
<CheckCircle className="h-4 w-4 mr-4 text-custom-text-300 flex-shrink-0" />
|
||||
<span className="text-custom-text-200 truncate">{feature}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{extraFeatures && <div>{extraFeatures}</div>}
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue