From bfa30aa5045bfbe149b6330263a2acb98bf2fd62 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Fri, 19 May 2023 15:22:17 +0530 Subject: [PATCH] Implement UpgradeTip in App install flow (#8968) --- apps/web/components/apps/App.tsx | 8 +++++++- apps/web/pages/apps/[slug]/index.tsx | 1 + .../_components/OmniInstallAppButton.tsx | 1 + packages/app-store/components.tsx | 17 ++++++++++++++--- packages/app-store/routing-forms/config.json | 3 +++ packages/types/App.d.ts | 3 +++ packages/ui/components/apps/AppCard.tsx | 2 ++ 7 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/web/components/apps/App.tsx b/apps/web/components/apps/App.tsx index 62752ba044..5f2306b2b3 100644 --- a/apps/web/components/apps/App.tsx +++ b/apps/web/components/apps/App.tsx @@ -36,6 +36,7 @@ const Component = ({ tos, privacy, isProOnly, + teamsPlanRequired, descriptionItems, isTemplate, dependencies, @@ -152,6 +153,7 @@ const Component = ({ type={type} isProOnly={isProOnly} disableInstall={disableInstall} + teamsPlanRequired={teamsPlanRequired} render={({ useDefaultComponent, ...props }) => { if (useDefaultComponent) { props = { @@ -192,6 +194,7 @@ const Component = ({ type={type} isProOnly={isProOnly} disableInstall={disableInstall} + teamsPlanRequired={teamsPlanRequired} render={({ useDefaultComponent, ...props }) => { if (useDefaultComponent) { props = { @@ -241,7 +244,9 @@ const Component = ({

{t("pricing")}

- {price === 0 ? ( + {teamsPlanRequired ? ( + t("teams_plan_required") + ) : price === 0 ? ( t("free_to_use_apps") ) : ( <> @@ -358,6 +363,7 @@ export default function App(props: { privacy?: string; licenseRequired: AppType["licenseRequired"]; isProOnly: AppType["isProOnly"]; + teamsPlanRequired: AppType["teamsPlanRequired"]; descriptionItems?: Array }>; isTemplate?: boolean; disableInstall?: boolean; diff --git a/apps/web/pages/apps/[slug]/index.tsx b/apps/web/pages/apps/[slug]/index.tsx index 854f022616..841331f5d9 100644 --- a/apps/web/pages/apps/[slug]/index.tsx +++ b/apps/web/pages/apps/[slug]/index.tsx @@ -72,6 +72,7 @@ function SingleAppPage(props: inferSSRProps) { website={data.url} email={data.email} licenseRequired={data.licenseRequired} + teamsPlanRequired={data.teamsPlanRequired} isProOnly={data.isProOnly} descriptionItems={source.data?.items as string[] | undefined} isTemplate={data.isTemplate} diff --git a/packages/app-store/_components/OmniInstallAppButton.tsx b/packages/app-store/_components/OmniInstallAppButton.tsx index 120c2b66ec..e181d4f362 100644 --- a/packages/app-store/_components/OmniInstallAppButton.tsx +++ b/packages/app-store/_components/OmniInstallAppButton.tsx @@ -47,6 +47,7 @@ export default function OmniInstallAppButton({ { if (useDefaultComponent) { diff --git a/packages/app-store/components.tsx b/packages/app-store/components.tsx index 5630b56ffd..673fdcea98 100644 --- a/packages/app-store/components.tsx +++ b/packages/app-store/components.tsx @@ -7,6 +7,7 @@ import classNames from "@calcom/lib/classNames"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { CAL_URL } from "@calcom/lib/constants"; import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType"; +import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -49,14 +50,17 @@ export const InstallAppButtonWithoutPlanCheck = ( export const InstallAppButton = ( props: { isProOnly?: App["isProOnly"]; + teamsPlanRequired?: App["teamsPlanRequired"]; type: App["type"]; wrapperClassName?: string; disableInstall?: boolean; } & InstallAppButtonProps ) => { - const { isLoading, data: user } = trpc.viewer.me.useQuery(); + const { isLoading: isUserLoading, data: user } = trpc.viewer.me.useQuery(); const router = useRouter(); const proProtectionElementRef = useRef(null); + const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan(); + useEffect(() => { const el = proProtectionElementRef.current; if (!el) { @@ -72,12 +76,19 @@ export const InstallAppButton = ( e.stopPropagation(); return; } + + if (props.teamsPlanRequired && !hasTeamPlan) { + // TODO: I think we should show the UpgradeTip in a Dialog here. This would solve the problem of no way to go back to the App page from the UpgradeTip page(except browser's back button) + router.push(props.teamsPlanRequired.upgradeUrl); + e.stopPropagation(); + return; + } }, true ); - }, [isLoading, user, router, props.isProOnly]); + }, [isUserLoading, user, router, props.isProOnly, hasTeamPlan, props.teamsPlanRequired]); - if (isLoading) { + if (isUserLoading || isTeamPlanStatusLoading) { return null; } diff --git a/packages/app-store/routing-forms/config.json b/packages/app-store/routing-forms/config.json index a5dbf1fb39..1dc24b1d2f 100644 --- a/packages/app-store/routing-forms/config.json +++ b/packages/app-store/routing-forms/config.json @@ -11,6 +11,9 @@ "publisher": "Cal.com", "email": "help@cal.com", "licenseRequired": true, + "teamsPlanRequired": { + "upgradeUrl": "/apps/routing-forms/forms" + }, "description": "It would allow a booker to connect with the right person or choose the right event, faster. It would work by taking inputs from the booker and using that data to route to the correct booker/event as configured by Cal user", "__createdUsingCli": true } diff --git a/packages/types/App.d.ts b/packages/types/App.d.ts index 6697345596..49e62fa4b0 100644 --- a/packages/types/App.d.ts +++ b/packages/types/App.d.ts @@ -126,6 +126,9 @@ export interface App { /** only required for "usage-based" billing. % of commission for paid bookings */ commission?: number; licenseRequired?: boolean; + teamsPlanRequired?: { + upgradeUrl: string; + }; isProOnly?: boolean; appData?: AppData; /** diff --git a/packages/ui/components/apps/AppCard.tsx b/packages/ui/components/apps/AppCard.tsx index b851752069..d23a4722f9 100644 --- a/packages/ui/components/apps/AppCard.tsx +++ b/packages/ui/components/apps/AppCard.tsx @@ -97,6 +97,7 @@ export function AppCard({ app, credentials, searchText }: AppCardProps) { !data.installed)} wrapperClassName="[@media(max-width:260px)]:w-full" render={({ useDefaultComponent, ...props }) => { @@ -127,6 +128,7 @@ export function AppCard({ app, credentials, searchText }: AppCardProps) { isProOnly={app.isProOnly} wrapperClassName="[@media(max-width:260px)]:w-full" disableInstall={!!app.dependencies && app.dependencyData?.some((data) => !data.installed)} + teamsPlanRequired={app.teamsPlanRequired} render={({ useDefaultComponent, ...props }) => { if (useDefaultComponent) { props = {