mirror of
https://github.com/makeplane/plane.git
synced 2025-12-29 00:24:56 +01:00
fix: cloud pro plan implementation
This commit is contained in:
5
packages/types/src/payment.d.ts
vendored
5
packages/types/src/payment.d.ts
vendored
@@ -14,3 +14,8 @@ export type IPaymentProduct = {
|
||||
type: "PRO" | "ULTIMATE";
|
||||
prices: IPaymentProductPrice[];
|
||||
};
|
||||
|
||||
export type IWorkspaceProductSubscription = {
|
||||
product: FREE | PRO | ULTIMATE;
|
||||
expiry_date: string | null;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ import { CheckCircle } from "lucide-react";
|
||||
import { Dialog, Transition, Tab } from "@headlessui/react";
|
||||
// types
|
||||
import { IPaymentProduct, IPaymentProductPrice } from "@plane/types";
|
||||
// ui
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// store
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
// services
|
||||
@@ -77,6 +79,13 @@ export const CloudProductsModal: FC<CloudProductsModalProps> = (props) => {
|
||||
window.open(response.payment_link, "_blank");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Failed to generate payment link. Please try again.",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./cloud-products-modal";
|
||||
export * from "./plane-one-modal";
|
||||
export * from "./plane-one-billing";
|
||||
export * from "./plane-cloud-billing";
|
||||
export * from "./pro-plan-details-modal";
|
||||
|
||||
55
web/components/license/pro-plan-details-modal.tsx
Normal file
55
web/components/license/pro-plan-details-modal.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { FC, Fragment } from "react";
|
||||
// ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
export type ProPlanDetailsModalProps = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const ProPlanDetailsModal: FC<ProPlanDetailsModalProps> = (props) => {
|
||||
const { isOpen, handleClose } = props;
|
||||
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-custom-backdrop" />
|
||||
</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-custom-background-100 p-6 text-left align-middle shadow-xl transition-all border-[0.5px] border-custom-border-100">
|
||||
<Dialog.Title as="h2" className="text-2xl font-bold leading-6 mt-4 flex justify-center items-center">
|
||||
Thank you for being an early adopter
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-5">
|
||||
<p className="text-center text-sm mb-6 px-10 text-custom-text-200">
|
||||
The wait will be worth it! We’re excited to announce that our pro features will be rolling out
|
||||
shortly. Billing will commence from the day these features become available.
|
||||
</p>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,46 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { Tooltip, Button, getButtonStyling } from "@plane/ui";
|
||||
// components
|
||||
import { PlaneOneModal, CloudProductsModal } from "@/components/license";
|
||||
import { PlaneOneModal, CloudProductsModal, ProPlanDetailsModal } from "@/components/license";
|
||||
// hooks
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// assets
|
||||
import PlaneOneLogo from "@/public/plane-logos/plane-one.svg";
|
||||
// services
|
||||
import { DiscoService } from "@/services/disco.service";
|
||||
|
||||
import packageJson from "package.json";
|
||||
|
||||
export const PlaneBadge: React.FC = () => {
|
||||
const discoService = new DiscoService();
|
||||
|
||||
export const PlaneBadge: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// states
|
||||
const [isProPlanModalOpen, setIsProPlanModalOpen] = useState(false);
|
||||
const [isProPlanDetailsModalOpen, setProPlanDetailsModalOpen] = useState(false);
|
||||
const [isPlaneOneModalOpen, setIsPlaneOneModalOpen] = useState(false);
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { instance } = useInstance();
|
||||
|
||||
const handleProPlanModalOpen = () => {
|
||||
// fetch workspace current plane information
|
||||
const { data } = useSWR(
|
||||
workspaceSlug ? "WORKSPACE_CURRENT_PLANE" : null,
|
||||
workspaceSlug ? () => discoService.getWorkspaceCurrentPlane(workspaceSlug.toString()) : null
|
||||
);
|
||||
|
||||
console.log("data", data);
|
||||
|
||||
const handleProPlanPurchaseModalOpen = () => {
|
||||
setIsProPlanModalOpen(true);
|
||||
captureEvent("pro_plan_modal_opened", {});
|
||||
};
|
||||
@@ -31,17 +50,31 @@ export const PlaneBadge: React.FC = () => {
|
||||
captureEvent("plane_one_modal_opened", {});
|
||||
};
|
||||
|
||||
// const handleProPlanDetailsModalOpen = () => {
|
||||
// setProPlanDetailsModalOpen(true);
|
||||
// };
|
||||
|
||||
if (process.env.NEXT_PUBLIC_IS_MULTI_TENANT === "1") {
|
||||
return (
|
||||
<>
|
||||
<CloudProductsModal isOpen={isProPlanModalOpen} handleClose={() => setIsProPlanModalOpen(false)} />
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
className="w-1/2 cursor-pointer rounded-2xl px-3 py-1.5 text-center text-sm font-medium outline-none"
|
||||
onClick={handleProPlanModalOpen}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
<ProPlanDetailsModal isOpen={isProPlanDetailsModalOpen} handleClose={() => setProPlanDetailsModalOpen(false)} />
|
||||
{data && data.product === "s" && (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
className="w-1/2 cursor-pointer rounded-2xl px-3 py-1.5 text-center text-sm font-medium outline-none"
|
||||
onClick={handleProPlanPurchaseModalOpen}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
{data && data.product === "FREE" && (
|
||||
<div className="w-1/2 flex justify-start">
|
||||
<span className="items-center justify-center px-3.5 py-0.5 text-xs leading-4 rounded-xl bg-custom-primary-100/10 text-custom-primary-100">
|
||||
Pro
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -76,4 +109,4 @@ export const PlaneBadge: React.FC = () => {
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IPaymentProduct } from "@plane/types";
|
||||
import { IPaymentProduct, IWorkspaceProductSubscription } from "@plane/types";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
// services
|
||||
@@ -9,7 +9,6 @@ export class DiscoService extends APIService {
|
||||
super(API_BASE_URL);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listProducts(workspaceSlug: string): Promise<IPaymentProduct[]> {
|
||||
return this.get(`/api/payments/workspaces/${workspaceSlug}/products/`)
|
||||
.then((response) => response?.data)
|
||||
@@ -25,4 +24,12 @@ export class DiscoService extends APIService {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
getWorkspaceCurrentPlane(workspaceSlug: string): Promise<IWorkspaceProductSubscription> {
|
||||
return this.get(`/api/payments/workspaces/${workspaceSlug}/current-plan/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user