Skip to content
Merged
94 changes: 59 additions & 35 deletions components/src/components/elements/pricing-table/PricingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { useTranslation } from "react-i18next";

import { type CompanyPlanDetailResponseData } from "../../../api/checkoutexternal";
import { type PlanViewPublicResponseData } from "../../../api/componentspublic";
import { TEXT_BASE_SIZE, VISIBLE_ENTITLEMENT_COUNT } from "../../../const";
import {
PriceInterval,
TEXT_BASE_SIZE,
VISIBLE_ENTITLEMENT_COUNT,
} from "../../../const";
import { type FontStyle } from "../../../context";
import { useAvailablePlans, useEmbed } from "../../../hooks";
import type { DeepPartial, ElementProps } from "../../../types";
Expand Down Expand Up @@ -131,7 +135,7 @@ export const PricingTable = forwardRef<

const { data, settings, isPending, hydratePublic } = useEmbed();

const { currentPeriod, isStandalone } = useMemo(() => {
const { currentPeriod, showPeriodToggle, isStandalone } = useMemo(() => {
if (isCheckoutData(data)) {
const billingSubscription = data.company?.billingSubscription;
const isTrialSubscription = billingSubscription?.status === "trialing";
Expand All @@ -141,6 +145,7 @@ export const PricingTable = forwardRef<
currentPeriod: data.company?.plan?.planPeriod || "month",
currentAddOns: data.company?.addOns || [],
canCheckout: data.capabilities?.checkout ?? true,
showPeriodToggle: data.showPeriodToggle ?? props.showPeriodToggle,
isTrialSubscription,
willSubscriptionCancel,
isStandalone: false,
Expand All @@ -151,15 +156,18 @@ export const PricingTable = forwardRef<
currentPeriod: "month",
currentAddOns: [],
canCheckout: true,
showPeriodToggle: props.showPeriodToggle,
isTrialSubscription: false,
willSubscriptionCancel: false,
isStandalone: true,
};
}, [data]);
}, [props.showPeriodToggle, data]);

const [selectedPeriod, setSelectedPeriod] = useState(currentPeriod);

const { plans, addOns, periods } = useAvailablePlans(selectedPeriod);
const { plans, addOns, periods } = useAvailablePlans(selectedPeriod, {
useSelectedPeriod: showPeriodToggle,
});

const [entitlementCounts, setEntitlementCounts] = useState(() =>
plans.reduce(entitlementCountsReducer, {}),
Expand Down Expand Up @@ -245,7 +253,7 @@ export const PricingTable = forwardRef<
t("Plans")}
</Text>

{props.showPeriodToggle && periods.length > 1 && (
{showPeriodToggle && periods.length > 1 && (
<PeriodToggle
options={periods}
selectedOption={selectedPeriod}
Expand All @@ -264,23 +272,31 @@ export const PricingTable = forwardRef<
$gridTemplateColumns="repeat(auto-fill, minmax(320px, 1fr))"
$gap="1rem"
>
{plans.map((plan, index, self) => (
<Plan
key={index}
plan={plan}
index={index}
sharedProps={{
layout: props,
callToActionUrl,
callToActionTarget,
onCallToAction,
}}
plans={self}
selectedPeriod={selectedPeriod}
entitlementCounts={entitlementCounts}
handleToggleShowAll={handleToggleShowAll}
/>
))}
{plans.map((plan, index, self) => {
const planPeriod = showPeriodToggle
? selectedPeriod
: plan.yearlyPrice && !plan.monthlyPrice
? PriceInterval.Year
: PriceInterval.Month;

return (
<Plan
key={index}
plan={plan}
index={index}
sharedProps={{
layout: props,
callToActionUrl,
callToActionTarget,
onCallToAction,
}}
plans={self}
selectedPeriod={planPeriod}
entitlementCounts={entitlementCounts}
handleToggleShowAll={handleToggleShowAll}
/>
);
})}
</Box>
)}
</Box>
Expand Down Expand Up @@ -308,19 +324,27 @@ export const PricingTable = forwardRef<
$gridTemplateColumns="repeat(auto-fill, minmax(320px, 1fr))"
$gap="1rem"
>
{addOns.map((addOn, index) => (
<AddOn
key={index}
addOn={addOn}
sharedProps={{
layout: props,
callToActionUrl,
callToActionTarget,
onCallToAction,
}}
selectedPeriod={selectedPeriod}
/>
))}
{addOns.map((addOn, index) => {
const addOnPeriod = showPeriodToggle
? selectedPeriod
: addOn.yearlyPrice && !addOn.monthlyPrice
? PriceInterval.Year
: PriceInterval.Month;

return (
<AddOn
key={index}
addOn={addOn}
sharedProps={{
layout: props,
callToActionUrl,
callToActionTarget,
onCallToAction,
}}
selectedPeriod={addOnPeriod}
/>
);
})}
</Box>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,30 @@ export const CheckoutDialog = ({ top = 0 }: CheckoutDialogProps) => {
periods: availablePeriods,
} = useAvailablePlans(planPeriod);

const { currentPlanId, currentEntitlements, trialPaymentMethodRequired } =
useMemo(() => {
if (isCheckoutData(data)) {
return {
currentPlanId: data.company?.plan?.id,
currentEntitlements: data.featureUsage
? data.featureUsage.features
: [],
trialPaymentMethodRequired: data.trialPaymentMethodRequired === true,
};
}

const {
currentPlanId,
currentEntitlements,
showPeriodToggle,
trialPaymentMethodRequired,
} = useMemo(() => {
if (isCheckoutData(data)) {
return {
currentPlanId: undefined,
currentEntitlements: [],
trialPaymentMethodRequired: false,
currentPlanId: data.company?.plan?.id,
currentEntitlements: data.featureUsage
? data.featureUsage.features
: [],
showPeriodToggle: data.showPeriodToggle,
trialPaymentMethodRequired: data.trialPaymentMethodRequired === true,
};
}, [data]);
}

return {
currentPlanId: undefined,
currentEntitlements: [],
showPeriodToggle: true,
trialPaymentMethodRequired: false,
};
}, [data]);

const [selectedPlan, setSelectedPlan] = useState<SelectedPlan | undefined>(
() => {
Expand Down Expand Up @@ -867,14 +873,16 @@ export const CheckoutDialog = ({ top = 0 }: CheckoutDialogProps) => {
</Flex>
)}

{checkoutStage === "plan" && availablePeriods.length > 1 && (
<PeriodToggle
options={availablePeriods}
selectedOption={planPeriod}
selectedPlan={selectedPlan}
onSelect={changePlanPeriod}
/>
)}
{checkoutStage === "plan" &&
showPeriodToggle &&
availablePeriods.length > 1 && (
<PeriodToggle
options={availablePeriods}
selectedOption={planPeriod}
selectedPlan={selectedPlan}
onSelect={changePlanPeriod}
/>
)}
</Flex>

{checkoutStage === "plan" && (
Expand All @@ -885,6 +893,7 @@ export const CheckoutDialog = ({ top = 0 }: CheckoutDialogProps) => {
selectedPlan={selectedPlan}
selectPlan={selectPlan}
shouldTrial={shouldTrial}
showPeriodToggle={showPeriodToggle}
/>
)}

Expand Down
22 changes: 15 additions & 7 deletions components/src/components/shared/checkout-dialog/Plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EntitlementValueType,
FeatureType,
PriceBehavior,
PriceInterval,
TEXT_BASE_SIZE,
VISIBLE_ENTITLEMENT_COUNT,
} from "../../../const";
Expand Down Expand Up @@ -252,6 +253,7 @@ interface PlanProps {
shouldTrial?: boolean;
}) => void;
shouldTrial: boolean;
showPeriodToggle: boolean;
}

export const Plan = ({
Expand All @@ -261,6 +263,7 @@ export const Plan = ({
period,
selectPlan,
shouldTrial,
showPeriodToggle,
}: PlanProps) => {
const { t } = useTranslation();

Expand Down Expand Up @@ -312,8 +315,13 @@ export const Plan = ({
$flexGrow={1}
>
{plans.map((plan, planIndex) => {
const planPeriod = showPeriodToggle
? period
: plan.yearlyPrice && !plan.monthlyPrice
? PriceInterval.Year
: PriceInterval.Month;
const { price: planPrice, currency: planCurrency } =
getPlanPrice(plan, period) || {};
getPlanPrice(plan, planPeriod) || {};
const credits = groupPlanCreditGrants(plan.includedCreditGrants);
const hasUsageBasedEntitlements = plan.entitlements.some(
(entitlement) => !!entitlement.priceBehavior,
Expand Down Expand Up @@ -395,7 +403,7 @@ export const Plan = ({
(16 / 30) * settings.theme.typography.heading2.fontSize
}
>
/{period}
/{planPeriod}
</Text>
)}
</Box>
Expand Down Expand Up @@ -502,7 +510,7 @@ export const Plan = ({
priceTier: entitlementPriceTiers,
currency: entitlementCurrency,
packageSize: entitlementPackageSize = 1,
} = getEntitlementPrice(entitlement, period) || {};
} = getEntitlementPrice(entitlement, planPeriod) || {};

const metricPeriodName = getMetricPeriodName(entitlement);

Expand Down Expand Up @@ -557,15 +565,15 @@ export const Plan = ({
PriceBehavior.PayInAdvance && (
<>
{" "}
{t("per")} {period}
{t("per")} {planPeriod}
</>
)}
</>
) : entitlement.priceBehavior ===
PriceBehavior.Tiered ? (
<TieredPricingDetails
entitlement={entitlement}
period={period}
period={planPeriod}
/>
) : entitlement.priceBehavior ===
PriceBehavior.Credit &&
Expand Down Expand Up @@ -640,7 +648,7 @@ export const Plan = ({
)}
{entitlement.feature.featureType ===
FeatureType.Trait && (
<>/{shortenPeriod(period)}</>
<>/{shortenPeriod(planPeriod)}</>
)}
</Text>
) : (
Expand All @@ -649,7 +657,7 @@ export const Plan = ({
<Flex $alignItems="center">
<PricingTiersTooltip
feature={entitlement.feature}
period={period}
period={planPeriod}
currency={entitlementCurrency}
priceTiers={entitlementPriceTiers}
/>
Expand Down
7 changes: 7 additions & 0 deletions components/src/const/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export enum PriceInterval {
OneTime = "one-time",
Day = "day",
Month = "month",
Year = "year",
}

export enum PriceBehavior {
PayInAdvance = "pay_in_advance",
PayAsYouGo = "pay_as_you_go",
Expand Down
32 changes: 24 additions & 8 deletions components/src/hooks/useAvailablePlans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { ChargeType } from "../utils";

import { useEmbed } from ".";

export function useAvailablePlans(activePeriod: string) {
interface AvailablePlanOptions {
useSelectedPeriod?: boolean;
}

export function useAvailablePlans(
activePeriod: string,
options: AvailablePlanOptions = { useSelectedPeriod: true },
) {
const { data, settings } = useEmbed();

const getAvailablePeriods = useCallback((): string[] => {
Expand Down Expand Up @@ -35,16 +42,25 @@ export function useAvailablePlans(activePeriod: string) {
const activePlans =
settings.mode === "edit"
? plans.slice()
: plans.filter(
(plan) =>
(activePeriod === "month" && plan.monthlyPrice) ||
(activePeriod === "year" && plan.yearlyPrice) ||
plan.chargeType === ChargeType.oneTime,
);
: plans.filter((plan) => {
if (options.useSelectedPeriod) {
return (
(activePeriod === "month" && plan.monthlyPrice) ||
(activePeriod === "year" && plan.yearlyPrice) ||
plan.chargeType === ChargeType.oneTime
);
}

return (
plan.monthlyPrice ||
plan.yearlyPrice ||
plan.chargeType === ChargeType.oneTime
);
});

return activePlans.map((plan) => ({ ...plan, isSelected: false }));
},
[activePeriod, settings.mode],
[activePeriod, options.useSelectedPeriod, settings.mode],
);

return useMemo(() => {
Expand Down
Loading
Loading