From 3b9b268809dd6fd3bc3bbf4c827a034ca9da1c85 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 17 Jun 2024 15:38:11 +0530 Subject: [PATCH] fix: cloud pro plan implementation --- packages/types/src/payment.d.ts | 5 ++ .../license/cloud-products-modal.tsx | 9 +++ web/components/license/index.ts | 1 + .../license/pro-plan-details-modal.tsx | 55 +++++++++++++++++++ web/components/workspace/plane-badge.tsx | 55 +++++++++++++++---- web/services/disco.service.ts | 11 +++- 6 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 web/components/license/pro-plan-details-modal.tsx diff --git a/packages/types/src/payment.d.ts b/packages/types/src/payment.d.ts index 3db2957f11..b63f3c1ce5 100644 --- a/packages/types/src/payment.d.ts +++ b/packages/types/src/payment.d.ts @@ -14,3 +14,8 @@ export type IPaymentProduct = { type: "PRO" | "ULTIMATE"; prices: IPaymentProductPrice[]; }; + +export type IWorkspaceProductSubscription = { + product: FREE | PRO | ULTIMATE; + expiry_date: string | null; +}; diff --git a/web/components/license/cloud-products-modal.tsx b/web/components/license/cloud-products-modal.tsx index 340bd84bfc..1134ec3495 100644 --- a/web/components/license/cloud-products-modal.tsx +++ b/web/components/license/cloud-products-modal.tsx @@ -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 = (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); }); diff --git a/web/components/license/index.ts b/web/components/license/index.ts index 01bb9abbca..bc206253ee 100644 --- a/web/components/license/index.ts +++ b/web/components/license/index.ts @@ -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"; diff --git a/web/components/license/pro-plan-details-modal.tsx b/web/components/license/pro-plan-details-modal.tsx new file mode 100644 index 0000000000..9a4fc761cb --- /dev/null +++ b/web/components/license/pro-plan-details-modal.tsx @@ -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 = (props) => { + const { isOpen, handleClose } = props; + return ( + + + +
+ + +
+
+ + + + Thank you for being an early adopter + +
+

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

+
+
+
+
+
+
+
+ ); +}; diff --git a/web/components/workspace/plane-badge.tsx b/web/components/workspace/plane-badge.tsx index 034f0c89c0..e63d9359c7 100644 --- a/web/components/workspace/plane-badge.tsx +++ b/web/components/workspace/plane-badge.tsx @@ -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 ( <> setIsProPlanModalOpen(false)} /> - + setProPlanDetailsModalOpen(false)} /> + {data && data.product === "s" && ( + + )} + {data && data.product === "FREE" && ( +
+ + Pro + +
+ )} ); } @@ -76,4 +109,4 @@ export const PlaneBadge: React.FC = () => { ); -}; +}); diff --git a/web/services/disco.service.ts b/web/services/disco.service.ts index a758104ed6..450db1af05 100644 --- a/web/services/disco.service.ts +++ b/web/services/disco.service.ts @@ -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 { 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 { + return this.get(`/api/payments/workspaces/${workspaceSlug}/current-plan/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } }