diff --git a/packages/billing/gateway/src/components/line-item-details.tsx b/packages/billing/gateway/src/components/line-item-details.tsx index e8fb2589c..1931f3d08 100644 --- a/packages/billing/gateway/src/components/line-item-details.tsx +++ b/packages/billing/gateway/src/components/line-item-details.tsx @@ -242,12 +242,8 @@ function Tiers({ const isIncluded = tier.cost === 0; return ( - - - - + + -{' '} {formatCurrency({ @@ -255,8 +251,7 @@ function Tiers({ value: tier.cost, locale, })} - - + {' '} 1}> - - - + {' '} - - + {' '} {formatCurrency({ @@ -295,8 +287,7 @@ function Tiers({ value: tier.cost, locale, })} - - + {' '} ; + currencyCode: string; + interval?: string; + alwaysDisplayMonthlyPrice?: boolean; + className?: string; +}; + +/** + * @name PlanCostDisplay + * @description + * This component is used to display the cost of a plan. It will handle + * the display of the cost for metered plans by using the lowest tier using the format "Starting at {price} {unit}" + */ +export function PlanCostDisplay({ + primaryLineItem, + currencyCode, + interval, + alwaysDisplayMonthlyPrice = true, + className, +}: PlanCostDisplayProps) { + const { i18n } = useTranslation(); + + const { shouldDisplayTier, lowestTier, tierTranslationKey, displayCost } = + useMemo(() => { + const shouldDisplayTier = + primaryLineItem.type === 'metered' && + Array.isArray(primaryLineItem.tiers) && + primaryLineItem.tiers.length > 0; + + const isMultiTier = + Array.isArray(primaryLineItem.tiers) && + primaryLineItem.tiers.length > 1; + + const lowestTier = primaryLineItem.tiers?.reduce((acc, curr) => { + if (acc && acc.cost < curr.cost) { + return acc; + } + return curr; + }, primaryLineItem.tiers?.[0]); + + const isYearlyPricing = interval === 'year'; + + const cost = + isYearlyPricing && alwaysDisplayMonthlyPrice + ? Number(primaryLineItem.cost / 12) + : primaryLineItem.cost; + + return { + shouldDisplayTier, + isMultiTier, + lowestTier, + tierTranslationKey: isMultiTier + ? 'billing:startingAtPriceUnit' + : 'billing:priceUnit', + displayCost: cost, + }; + }, [primaryLineItem, interval, alwaysDisplayMonthlyPrice]); + + if (shouldDisplayTier) { + const formattedCost = formatCurrency({ + currencyCode: currencyCode.toLowerCase(), + value: lowestTier?.cost ?? 0, + locale: i18n.language, + }); + + return ( + + + + ); + } + + const formattedCost = formatCurrency({ + currencyCode: currencyCode.toLowerCase(), + value: displayCost, + locale: i18n.language, + }); + + return {formattedCost}; +} diff --git a/packages/billing/gateway/src/components/plan-picker.tsx b/packages/billing/gateway/src/components/plan-picker.tsx index 6c06e3302..cbb291ba4 100644 --- a/packages/billing/gateway/src/components/plan-picker.tsx +++ b/packages/billing/gateway/src/components/plan-picker.tsx @@ -15,7 +15,6 @@ import { getPrimaryLineItem, getProductPlanPair, } from '@kit/billing'; -import { formatCurrency } from '@kit/shared/utils'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { @@ -38,6 +37,7 @@ import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; import { LineItemDetails } from './line-item-details'; +import { PlanCostDisplay } from './plan-cost-display'; export function PlanPicker( props: React.PropsWithChildren<{ @@ -108,8 +108,6 @@ export function PlanPicker( const isRecurringPlan = selectedPlan?.paymentType === 'recurring' || !selectedPlan; - const locale = useTranslation().i18n.language; - return (
0; - - const isMultiTier = - Array.isArray(primaryLineItem.tiers) && - primaryLineItem.tiers.length > 1; - - const lowestTier = primaryLineItem.tiers?.reduce( - (acc, curr) => { - if (acc && acc.cost < curr.cost) { - return acc; - } - - return curr; - }, - primaryLineItem.tiers[0], - ); - - const tierTranslationKey = isMultiTier - ? 'billing:startingAtPriceUnit' - : 'billing:priceUnit'; - return (
- - - {formatCurrency({ - currencyCode: - product.currency.toLowerCase(), - value: primaryLineItem.cost, - locale, - })} - - - } - > - + - +
diff --git a/packages/billing/gateway/src/components/pricing-table.tsx b/packages/billing/gateway/src/components/pricing-table.tsx index eca6eb16c..3ca196b65 100644 --- a/packages/billing/gateway/src/components/pricing-table.tsx +++ b/packages/billing/gateway/src/components/pricing-table.tsx @@ -14,7 +14,6 @@ import { getPlanIntervals, getPrimaryLineItem, } from '@kit/billing'; -import { formatCurrency } from '@kit/shared/utils'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { If } from '@kit/ui/if'; @@ -23,6 +22,7 @@ import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; import { LineItemDetails } from './line-item-details'; +import { PlanCostDisplay } from './plan-cost-display'; interface Paths { signUp: string; @@ -152,8 +152,7 @@ function PricingItem( }>, ) { const highlighted = props.product.highlighted ?? false; - - const lineItem = props.primaryLineItem; + const lineItem = props.primaryLineItem!; // we exclude flat line items from the details since // it doesn't need further explanation @@ -218,11 +217,10 @@ function PricingItem(
- @@ -492,46 +490,3 @@ function DefaultCheckoutButton( ); } - -function LineItemPrice({ - lineItem, - plan, - interval, - product, - alwaysDisplayMonthlyPrice = true, -}: { - lineItem: z.infer | undefined; - plan: { - label?: string; - }; - interval: Interval | undefined; - product: { - currency: string; - }; - alwaysDisplayMonthlyPrice?: boolean; -}) { - const { i18n } = useTranslation(); - const isYearlyPricing = interval === 'year'; - - const cost = lineItem - ? isYearlyPricing - ? alwaysDisplayMonthlyPrice - ? Number(lineItem.cost / 12).toFixed(2) - : lineItem.cost - : lineItem?.cost - : 0; - - const costString = - lineItem && - formatCurrency({ - currencyCode: product.currency, - locale: i18n.language, - value: cost, - }); - - const labelString = plan.label && ( - - ); - - return costString ?? labelString ?? ; -}