diff --git a/apps/web/.env.development b/apps/web/.env.development index bd96eb1c2..f17125765 100644 --- a/apps/web/.env.development +++ b/apps/web/.env.development @@ -15,7 +15,7 @@ NEXT_PUBLIC_AUTH_PASSWORD=true NEXT_PUBLIC_AUTH_MAGIC_LINK=false # BILLING -NEXT_PUBLIC_BILLING_PROVIDER=lemon-squeezy +NEXT_PUBLIC_BILLING_PROVIDER=stripe # SUPABASE NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 diff --git a/apps/web/config/billing.sample.config.ts b/apps/web/config/billing.sample.config.ts index ebc105f62..295406d34 100644 --- a/apps/web/config/billing.sample.config.ts +++ b/apps/web/config/billing.sample.config.ts @@ -16,28 +16,6 @@ export default createBillingSchema({ provider, // products configuration products: [ - { - id: 'lifetime', - name: 'Lifetime', - description: 'The perfect plan for a lifetime', - currency: 'USD', - features: ['Feature 1', 'Feature 2', 'Feature 3'], - plans: [ - { - name: 'Lifetime', - id: 'lifetime', - paymentType: 'one-time', - lineItems: [ - { - id: '324643', - name: 'Base', - cost: 999.99, - type: 'flat', - }, - ], - }, - ], - }, { id: 'starter', name: 'Starter', @@ -56,42 +34,7 @@ export default createBillingSchema({ id: '324646', name: 'Addon 2', cost: 9.99, - type: 'metered', - unit: 'GBs', - tiers: [ - { - upTo: 5, - cost: 0, - }, - { - upTo: 10, - cost: 6.99, - }, - { - upTo: 'unlimited', - cost: 0.49, - }, - ], - }, - { - id: '324645', - name: 'Addon 2', - cost: 9.99, - type: 'per-seat', - tiers: [ - { - upTo: 5, - cost: 0, - }, - { - upTo: 10, - cost: 6.99, - }, - { - upTo: 'unlimited', - cost: 0.49, - }, - ], + type: 'flat', }, ], }, diff --git a/packages/billing/gateway/src/components/line-item-details.tsx b/packages/billing/gateway/src/components/line-item-details.tsx index 0c3d9051b..42e3278bf 100644 --- a/packages/billing/gateway/src/components/line-item-details.tsx +++ b/packages/billing/gateway/src/components/line-item-details.tsx @@ -38,114 +38,154 @@ export function LineItemDetails( ); } - const BaseFee = () => ( -
- + const SetupFee = () => ( + +
+ + + + + + + +
+
+ ); + + const FlatFee = () => ( +
+
+ + + + + + + + + + - + + + } + > + + + + + + + {formatCurrency(props?.currency.toLowerCase(), item.cost)} + +
+ + + + + + + + + + + + + + + + +
+ ); + + const PerSeat = () => ( +
+
- + - - + + + {formatCurrency(props.currency.toLowerCase(), item.cost)} + + +
- - } - > - - + + + + + +
+ ); + + const Metered = () => ( +
+
+ + + + + + + + + + - - - {formatCurrency(props?.currency.toLowerCase(), item.cost)} - + {/* If there are no tiers, there is a flat cost for usage */} + + + {formatCurrency(props?.currency.toLowerCase(), item.cost)} + + +
+ + + + {/* If there are tiers, we render them as a list */} + + +
); switch (item.type) { - case 'base': - return ; + case 'flat': + return ; case 'per-seat': - return ( -
-
- - - - - - - - - - - {formatCurrency(props.currency.toLowerCase(), item.cost)} - - -
- - - - -
- ); + return ; case 'metered': { - return ( -
-
- - - - - - - - - - - {(fee) => ( - - - - )} - - - - - - {/* If there are no tiers, there is a flat cost for usage */} - - - {formatCurrency(props?.currency.toLowerCase(), item.cost)} - - -
- - {/* If there are tiers, we render them as a list */} - - - -
- ); + return ; } } })} @@ -160,6 +200,8 @@ function Tiers({ currency: string; item: z.infer; }) { + const unit = item.unit; + const tiers = item.tiers?.map((tier, index) => { const previousTier = item.tiers?.[index - 1]; const isNoLimit = tier.upTo === 'unlimited'; @@ -173,7 +215,6 @@ function Tiers({ const upTo = tier.upTo; const isIncluded = tier.cost === 0; - const unit = item.unit; return ( {formatCurrency( product.currency.toLowerCase(), - baseLineItem.cost, + primaryLineItem.cost, )} @@ -420,19 +420,21 @@ function PlanDetails({

- + 0}> + -
- - - +
+ + + - -
+ +
+
diff --git a/packages/billing/gateway/src/components/pricing-table.tsx b/packages/billing/gateway/src/components/pricing-table.tsx index 27f81bc98..457d9ec92 100644 --- a/packages/billing/gateway/src/components/pricing-table.tsx +++ b/packages/billing/gateway/src/components/pricing-table.tsx @@ -386,13 +386,16 @@ function PlanIntervalSwitcher( {props.intervals.map((plan, index) => { const selected = plan === props.interval; - const className = cn('focus:!ring-0 !outline-none', { - 'rounded-r-none border-r-transparent': index === 0, - 'rounded-l-none': index === props.intervals.length - 1, - ['hover:bg-muted']: !selected, - ['font-semibold cursor-default bg-muted hover:bg-muted hover:text-initial']: - selected, - }); + const className = cn( + 'focus:!ring-0 !outline-none animate-in transition-all fade-in', + { + 'rounded-r-none border-r-transparent': index === 0, + 'rounded-l-none': index === props.intervals.length - 1, + ['hover:text-current hover:bg-muted']: !selected, + ['font-semibold cursor-default hover:text-initial hover:bg-background border-primary']: + selected, + }, + ); return (