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 = {