From 07bd2486ca2da6cd53e2238fdf4f9b7a98a52aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Fri, 21 Mar 2025 17:38:13 +0100 Subject: [PATCH] Add enterprise plan in cloud onboarding (#11100) Adding a way to switch to enterprise plan during onboarding Screenshot 2025-03-21 at 17 03 19 --- .../twenty-front/src/generated/graphql.tsx | 5 +- .../graphql/billingBaseProductPrices.ts | 1 + .../src/pages/onboarding/ChooseYourPlan.tsx | 72 +++++++++++++++---- .../billing/services/billing-plan.service.ts | 2 +- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 555c215a9..5b951d857 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1,5 +1,5 @@ -import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; +import { gql } from '@apollo/client'; export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -2386,7 +2386,7 @@ export type ValidatePasswordResetTokenQuery = { __typename?: 'Query', validatePa export type BillingBaseProductPricesQueryVariables = Exact<{ [key: string]: never; }>; -export type BillingBaseProductPricesQuery = { __typename?: 'Query', plans: Array<{ __typename?: 'BillingPlanOutput', planKey: BillingPlanKey, baseProduct: { __typename?: 'BillingProductDTO', prices: Array<{ __typename?: 'BillingPriceLicensedDTO', unitAmount: number, stripePriceId: string, recurringInterval: SubscriptionInterval } | { __typename?: 'BillingPriceMeteredDTO' }> } }> }; +export type BillingBaseProductPricesQuery = { __typename?: 'Query', plans: Array<{ __typename?: 'BillingPlanOutput', planKey: BillingPlanKey, baseProduct: { __typename?: 'BillingProductDTO', name: string, prices: Array<{ __typename?: 'BillingPriceLicensedDTO', unitAmount: number, stripePriceId: string, recurringInterval: SubscriptionInterval } | { __typename?: 'BillingPriceMeteredDTO' }> } }> }; export type BillingPortalSessionQueryVariables = Exact<{ returnUrlPath?: InputMaybe; @@ -3859,6 +3859,7 @@ export const BillingBaseProductPricesDocument = gql` plans { planKey baseProduct { + name prices { ... on BillingPriceLicensedDTO { unitAmount diff --git a/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts b/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts index 1f0df15fc..c6f6749e1 100644 --- a/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts +++ b/packages/twenty-front/src/modules/billing/graphql/billingBaseProductPrices.ts @@ -5,6 +5,7 @@ export const BILLING_BASE_PRODUCT_PRICES = gql` plans { planKey baseProduct { + name prices { ... on BillingPriceLicensedDTO { unitAmount diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index 9e13063ea..22a624886 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -88,20 +88,43 @@ export const ChooseYourPlan = () => { const billing = useRecoilValue(billingState); const { t } = useLingui(); - const benefits = [ - t`Full access`, - t`Unlimited contacts`, - t`Email integration`, - t`Custom objects`, - t`API & Webhooks`, - t`1 000 workflow node executions`, - ]; + const [billingCheckoutSession, setBillingCheckoutSession] = useRecoilState( + billingCheckoutSessionState, + ); const { data: plans } = useBillingBaseProductPricesQuery(); + const currentPlan = billingCheckoutSession.plan || BillingPlanKey.PRO; + + const getPlanBenefits = (planKey: BillingPlanKey) => { + if (planKey === BillingPlanKey.ENTERPRISE) { + return [ + t`Full access`, + t`Unlimited contacts`, + t`Email integration`, + t`Custom objects`, + t`API & Webhooks`, + t`20 000 workflow node executions`, + t`SSO (SAML / OIDC)`, + ]; + } + + return [ + t`Full access`, + t`Unlimited contacts`, + t`Email integration`, + t`Custom objects`, + t`API & Webhooks`, + t`10 000 workflow node executions`, + ]; + }; + + const benefits = getPlanBenefits(currentPlan); + const baseProduct = plans?.plans.find( - (plan) => plan.planKey === BillingPlanKey.PRO, + (plan) => plan.planKey === currentPlan, )?.baseProduct; + const baseProductPrice = baseProduct?.prices.find( (price): price is BillingPriceLicensedDto => isBillingPriceLicensed(price) && @@ -116,10 +139,6 @@ export const ChooseYourPlan = () => { (trialPeriod) => trialPeriod.isCreditCardRequired, ); - const [billingCheckoutSession, setBillingCheckoutSession] = useRecoilState( - billingCheckoutSessionState, - ); - const { handleCheckoutSession, isSubmitting } = useHandleCheckoutSession({ recurringInterval: billingCheckoutSession.interval, plan: billingCheckoutSession.plan, @@ -133,7 +152,7 @@ export const ChooseYourPlan = () => { billingCheckoutSession.requirePaymentMethod !== withCreditCard ) { setBillingCheckoutSession({ - plan: billingCheckoutSession.plan, + plan: currentPlan, interval: baseProductPrice.recurringInterval, requirePaymentMethod: withCreditCard, }); @@ -141,10 +160,31 @@ export const ChooseYourPlan = () => { }; }; + const handleSwitchPlan = (planKey: BillingPlanKey) => { + return () => { + if (isDefined(baseProductPrice)) { + setBillingCheckoutSession({ + plan: planKey, + interval: baseProductPrice.recurringInterval, + requirePaymentMethod: billingCheckoutSession.requirePaymentMethod, + }); + } + }; + }; + const { signOut } = useAuth(); const withCreditCardTrialPeriodDuration = withCreditCardTrialPeriod?.duration; + const alternatePlan = + currentPlan === BillingPlanKey.PRO + ? BillingPlanKey.ENTERPRISE + : BillingPlanKey.PRO; + + const alternatePlanName = plans?.plans.find( + (plan) => plan.planKey === alternatePlan, + )?.baseProduct.name; + return ( isDefined(baseProductPrice) && isDefined(billing) && ( @@ -213,6 +253,10 @@ export const ChooseYourPlan = () => { Log out + + Switch to {alternatePlanName} + + Book a Call diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts index c56b52377..178296857 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-plan.service.ts @@ -85,7 +85,7 @@ export class BillingPlanService { if (!baseProduct) { throw new BillingException( - 'Base product not found, did you run the billing:sync-products command?', + 'Base product not found, did you run the billing:sync-plans-data command?', BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND, ); }