mirror of
https://github.com/makeplane/plane.git
synced 2025-12-29 00:24:56 +01:00
feat: pro plan changes (#65)
* feat: adding pro plan pricing modal * fix: pricing changes * fix: env updates * fix: adding payment links
This commit is contained in:
committed by
GitHub
parent
64ae3b0a2a
commit
3d565d1c41
17
turbo.json
17
turbo.json
@@ -17,17 +17,14 @@
|
||||
"NEXT_PUBLIC_POSTHOG_KEY",
|
||||
"NEXT_PUBLIC_POSTHOG_HOST",
|
||||
"JITSU_TRACKER_ACCESS_KEY",
|
||||
"JITSU_TRACKER_HOST"
|
||||
"JITSU_TRACKER_HOST",
|
||||
"PRO_PLAN_MONTHLY_REDIRECT_URL",
|
||||
"PRO_PLAN_YEARLY_REDIRECT_URL"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"dist/**"
|
||||
]
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [".next/**", "dist/**"]
|
||||
},
|
||||
"web#develop": {
|
||||
"cache": false,
|
||||
@@ -89,9 +86,7 @@
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": []
|
||||
},
|
||||
"lint": {
|
||||
|
||||
1
web/components/license/index.ts
Normal file
1
web/components/license/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./pro-plan-modal";
|
||||
193
web/components/license/pro-plan-modal.tsx
Normal file
193
web/components/license/pro-plan-modal.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import { FC, Fragment, useState } from "react";
|
||||
import { Dialog, Transition, Tab } from "@headlessui/react";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
function classNames(...classes: any[]) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
const PRICING_CATEGORIES = ["Monthly", "Yearly"];
|
||||
|
||||
const MONTHLY_PLAN_ITEMS = [
|
||||
"White-glove onboarding for your use-cases",
|
||||
"Bespoke implementation",
|
||||
"Priority integrations",
|
||||
"Priority Support and SLAs",
|
||||
"Early access to all paid features",
|
||||
"Locked-in discount for a whole year",
|
||||
];
|
||||
|
||||
const YEARLY_PLAN_ITEMS = [
|
||||
"White-glove onboarding for your use-cases",
|
||||
"Bespoke implementation",
|
||||
"Priority integrations",
|
||||
"Priority Support and SLAs",
|
||||
"Early access to all paid features",
|
||||
"Tiered discounts for the second and third years",
|
||||
];
|
||||
|
||||
export type ProPlanModalProps = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const ProPlanModal: FC<ProPlanModalProps> = (props) => {
|
||||
const { isOpen, handleClose } = props;
|
||||
// store
|
||||
const {
|
||||
trackEvent: { captureEvent },
|
||||
} = useMobxStore();
|
||||
// states
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
|
||||
const handleProPlaneMonthRedirection = () => {
|
||||
if (process.env.PRO_PLAN_MONTHLY_REDIRECT_URL) {
|
||||
window.open(process.env.PRO_PLAN_MONTHLY_REDIRECT_URL, "_blank");
|
||||
captureEvent("pro_plan_modal_month_redirection");
|
||||
}
|
||||
};
|
||||
|
||||
const handleProPlanYearlyRedirection = () => {
|
||||
if (process.env.PRO_PLAN_YEARLY_REDIRECT_URL) {
|
||||
window.open(process.env.PRO_PLAN_YEARLY_REDIRECT_URL, "_blank");
|
||||
captureEvent("pro_plan_modal_yearly_redirection");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h2"
|
||||
className="text-2xl font-bold leading-6 text-gray-900 mt-4 flex justify-center items-center"
|
||||
>
|
||||
Early-adopter pricing for believers
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-5">
|
||||
<p className="text-center text-sm mb-6 px-10">
|
||||
Build Plane to your specs. You decide what we prioritize and build for everyone. Also get tailored
|
||||
onboarding + implementation and priority support.
|
||||
</p>
|
||||
<Tab.Group>
|
||||
<div className="flex w-full justify-center">
|
||||
<Tab.List className="flex space-x-1 rounded-xl bg-slate-100 p-1 w-[72%]">
|
||||
{PRICING_CATEGORIES.map((category, index) => (
|
||||
<Tab
|
||||
key={category}
|
||||
className={({ selected }) =>
|
||||
classNames(
|
||||
"w-full rounded-lg py-2 text-sm font-medium leading-5 text-slate-800",
|
||||
"ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none",
|
||||
selected
|
||||
? "bg-white text-blue-700 shadow"
|
||||
: "text-blue-100 hover:bg-white/[0.12] hover:text-slate-600"
|
||||
)
|
||||
}
|
||||
onClick={() => setTabIndex(index)}
|
||||
>
|
||||
<>
|
||||
{category}
|
||||
{category === "Yearly" && (
|
||||
<span className=" bg-blue-500 text-white rounded-full px-2 py-1 ml-1 text-xs">
|
||||
-28%
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
</div>
|
||||
|
||||
<Tab.Panels className="mt-2">
|
||||
<Tab.Panel className={classNames("rounded-xl bg-white p-3")}>
|
||||
<p className="ml-4 text-4xl font-bold mb-2">
|
||||
$7
|
||||
<span className="text-sm ml-3 text-slate-600">/user/month</span>
|
||||
</p>
|
||||
<ul>
|
||||
{MONTHLY_PLAN_ITEMS.map((item) => (
|
||||
<li key={item} className="relative rounded-md p-3 flex">
|
||||
<p className="text-sm font-medium leading-5 flex items-center">
|
||||
<CheckCircle height="16px" className="mr-4" />
|
||||
<span>{item}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-center w-full">
|
||||
<div className="relative inline-flex group mt-8">
|
||||
<div className="absolute transition-all duration-1000 opacity-50 -inset-px bg-gradient-to-r from-[#44BCFF] via-[#FF44EC] to-[#FF675E] rounded-xl blur-lg group-hover:opacity-100 group-hover:-inset-1 group-hover:duration-200 animate-tilt" />
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center justify-center px-8 py-4 text-sm font-medium text-slate-900 border-slate-200 border-[1.5px] transition-all duration-200 bg-white rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900"
|
||||
onClick={handleProPlaneMonthRedirection}
|
||||
>
|
||||
Become Early Adopter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel className={classNames("rounded-xl bg-white p-3")}>
|
||||
<p className="ml-4 text-4xl font-bold mb-2">
|
||||
$5
|
||||
<span className="text-sm ml-3 text-slate-600">/user/month</span>
|
||||
</p>
|
||||
<ul>
|
||||
{YEARLY_PLAN_ITEMS.map((item) => (
|
||||
<li key={item} className="relative rounded-md p-3 flex">
|
||||
<p className="text-sm font-medium leading-5 flex items-center">
|
||||
<CheckCircle height="16px" className="mr-4" />
|
||||
<span>{item}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-center w-full">
|
||||
<div className="relative inline-flex group mt-8">
|
||||
<div className="absolute transition-all duration-1000 opacity-50 -inset-px bg-gradient-to-r from-[#44BCFF] via-[#FF44EC] to-[#FF675E] rounded-xl blur-lg group-hover:opacity-100 group-hover:-inset-1 group-hover:duration-200 animate-tilt" />
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center justify-center px-8 py-4 text-sm font-medium text-slate-900 border-slate-600 transition-all duration-200 bg-white rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900"
|
||||
onClick={handleProPlanYearlyRedirection}
|
||||
>
|
||||
Become Early Adopter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,8 @@ import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-reac
|
||||
import { DiscordIcon, GithubIcon } from "@plane/ui";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
// components
|
||||
import { ProPlanModal } from "components/license";
|
||||
|
||||
const helpOptions = [
|
||||
{
|
||||
@@ -41,10 +43,13 @@ export interface WorkspaceHelpSectionProps {
|
||||
}
|
||||
|
||||
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
|
||||
// states
|
||||
const [isProPlanModalOpen, setIsProPlanModalOpen] = useState(false);
|
||||
// store
|
||||
const {
|
||||
theme: { sidebarCollapsed, toggleSidebar },
|
||||
commandPalette: { toggleShortcutModal },
|
||||
trackEvent: { captureEvent },
|
||||
} = useMobxStore();
|
||||
// states
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
@@ -55,16 +60,25 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
||||
|
||||
const isCollapsed = sidebarCollapsed || false;
|
||||
|
||||
const handleProPlanModalOpen = () => {
|
||||
setIsProPlanModalOpen(true);
|
||||
captureEvent("pro_plan_modal_opened");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProPlanModal isOpen={isProPlanModalOpen} handleClose={() => setIsProPlanModalOpen(false)} />
|
||||
<div
|
||||
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
|
||||
isCollapsed ? "flex-col" : ""
|
||||
}`}
|
||||
>
|
||||
{!isCollapsed && (
|
||||
<div className="w-1/2 cursor-default rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none">
|
||||
Free Plan
|
||||
<div
|
||||
className="w-1/2 cursor-pointer rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none"
|
||||
onClick={handleProPlanModalOpen}
|
||||
>
|
||||
Plan Pro
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex items-center gap-1 ${isCollapsed ? "flex-col justify-center" : "w-1/2 justify-evenly"}`}>
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ITrackEventStore {
|
||||
payload: object | [] | null,
|
||||
group?: { isGrouping: boolean | null; groupType: string | null; gorupId: string | null } | null
|
||||
) => void;
|
||||
captureEvent: (eventName: string, payload?: any) => void;
|
||||
}
|
||||
|
||||
export class TrackEventStore implements ITrackEventStore {
|
||||
@@ -74,4 +75,12 @@ export class TrackEventStore implements ITrackEventStore {
|
||||
}
|
||||
this.setTrackElement("");
|
||||
};
|
||||
|
||||
captureEvent = (eventName: string, payload?: any) => {
|
||||
try {
|
||||
posthog?.capture(eventName, payload);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user