@stigg/react-sdk 5.8.0 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/checkout/summary/components/GraduatedPriceBreakdown.d.ts +7 -0
- package/dist/components/checkout/summary/components/LineItems.d.ts +4 -0
- package/dist/components/checkout/summary/components/getPriceBreakdownString.d.ts +11 -4
- package/dist/components/common/CollapsableSectionIcon.d.ts +5 -0
- package/dist/components/common/Typography.d.ts +2 -2
- package/dist/components/utils/numberUtils.d.ts +3 -0
- package/dist/components/utils/priceTierUtils.d.ts +1 -0
- package/dist/react-sdk.cjs.development.js +190 -43
- package/dist/react-sdk.cjs.development.js.map +1 -1
- package/dist/react-sdk.cjs.production.min.js +1 -1
- package/dist/react-sdk.cjs.production.min.js.map +1 -1
- package/dist/react-sdk.esm.js +199 -47
- package/dist/react-sdk.esm.js.map +1 -1
- package/dist/theme/getResolvedTheme.d.ts +1 -0
- package/dist/theme/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/checkout/summary/components/GraduatedPriceBreakdown.tsx +66 -0
- package/src/components/checkout/summary/components/LineItems.tsx +74 -8
- package/src/components/checkout/summary/components/getPriceBreakdownString.ts +28 -17
- package/src/components/common/CollapsableSectionIcon.tsx +9 -0
- package/src/components/common/Typography.tsx +1 -0
- package/src/components/utils/numberUtils.ts +3 -0
- package/src/components/utils/priceTierUtils.ts +4 -0
- package/src/theme/getResolvedTheme.ts +2 -1
- package/src/theme/types.ts +1 -0
package/dist/theme/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import isNil from 'lodash/isNil';
|
|
3
|
+
import { Price, PriceTierFragment } from '@stigg/js-client-sdk';
|
|
4
|
+
import { Typography } from '../../../common/Typography';
|
|
5
|
+
import { calculateTierPriceGraduated } from '../../../utils/priceTierUtils';
|
|
6
|
+
import { formatPricePerUnit } from './getPriceBreakdownString';
|
|
7
|
+
import { LineItemContainer, LineItemRow } from './LineItems';
|
|
8
|
+
import { numberFormatter } from '../../../utils/numberUtils';
|
|
9
|
+
|
|
10
|
+
function getLabel(tiers: PriceTierFragment[], index: number): string {
|
|
11
|
+
const { unitPrice, upTo } = tiers[index];
|
|
12
|
+
if (!unitPrice) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (index === 0) {
|
|
17
|
+
return `First ${upTo}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const previousTierUpTo = tiers[index - 1].upTo || 0;
|
|
21
|
+
const startUnit = previousTierUpTo + 1;
|
|
22
|
+
|
|
23
|
+
return isNil(upTo)
|
|
24
|
+
? `${numberFormatter(startUnit)} and above`
|
|
25
|
+
: `Next ${numberFormatter(startUnit)} to ${numberFormatter(upTo)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type GraduatedPriceBreakdownProps = {
|
|
29
|
+
price: Price;
|
|
30
|
+
unitQuantity: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function GraduatedPriceBreakdown({ price, unitQuantity }: GraduatedPriceBreakdownProps) {
|
|
34
|
+
const tiers = price.tiers || [];
|
|
35
|
+
|
|
36
|
+
const { breakdown } = calculateTierPriceGraduated(tiers, unitQuantity);
|
|
37
|
+
|
|
38
|
+
if (breakdown.length === 1) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{breakdown.map(({ unitQuantity, amount }, index) => (
|
|
45
|
+
<LineItemContainer>
|
|
46
|
+
<LineItemRow key={index} style={{ alignItems: 'flex-start' }}>
|
|
47
|
+
<Typography variant="body1" color="tertiary" style={{ whiteSpace: 'nowrap' }}>
|
|
48
|
+
{getLabel(tiers, index)}
|
|
49
|
+
</Typography>
|
|
50
|
+
<Typography variant="body1" color="tertiary" style={{ whiteSpace: 'nowrap' }}>
|
|
51
|
+
{formatPricePerUnit({
|
|
52
|
+
quantity: unitQuantity,
|
|
53
|
+
totalAmount: amount,
|
|
54
|
+
currency: price.currency,
|
|
55
|
+
pricingModel: price.pricingModel,
|
|
56
|
+
billingPeriod: price.billingPeriod,
|
|
57
|
+
tiers: price.tiers,
|
|
58
|
+
tiersMode: price.tiersMode,
|
|
59
|
+
})}
|
|
60
|
+
</Typography>
|
|
61
|
+
</LineItemRow>
|
|
62
|
+
</LineItemContainer>
|
|
63
|
+
))}
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode, useState } from 'react';
|
|
2
2
|
import styled from '@emotion/styled/macro';
|
|
3
3
|
import Grid from '@mui/material/Grid';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BillingModel,
|
|
6
|
+
Price,
|
|
7
|
+
SubscriptionPreviewInvoice,
|
|
8
|
+
SubscriptionPreviewV2,
|
|
9
|
+
TiersMode,
|
|
10
|
+
} from '@stigg/js-client-sdk';
|
|
11
|
+
import isEmpty from 'lodash/isEmpty';
|
|
12
|
+
import isNil from 'lodash/isNil';
|
|
13
|
+
import Link from '@mui/material/Link';
|
|
14
|
+
import { IconButton } from '@mui/material';
|
|
15
|
+
import Collapse from '@mui/material/Collapse';
|
|
5
16
|
import { Typography } from '../../../common/Typography';
|
|
6
17
|
import { currencyPriceFormatter } from '../../../utils/currencyUtils';
|
|
7
18
|
import { WithSkeleton } from './WithSkeleton';
|
|
@@ -10,6 +21,9 @@ import { CheckoutLocalization } from '../../configurations/textOverrides';
|
|
|
10
21
|
import { Icon } from '../../../common/Icon';
|
|
11
22
|
import { InformationTooltip } from '../../../common/InformationTooltip';
|
|
12
23
|
import { getPriceBreakdownString } from './getPriceBreakdownString';
|
|
24
|
+
import { GraduatedPriceBreakdown } from './GraduatedPriceBreakdown';
|
|
25
|
+
import { CollapsableSectionIcon } from '../../../common/CollapsableSectionIcon';
|
|
26
|
+
import { calculateTierPrice } from '../../../utils/priceTierUtils';
|
|
13
27
|
|
|
14
28
|
export const LineItemContainer = styled.div`
|
|
15
29
|
& + & {
|
|
@@ -17,10 +31,16 @@ export const LineItemContainer = styled.div`
|
|
|
17
31
|
}
|
|
18
32
|
`;
|
|
19
33
|
|
|
34
|
+
export const NestedBreakdownContainer = styled.div`
|
|
35
|
+
margin-top: 16px;
|
|
36
|
+
margin-left: 16px;
|
|
37
|
+
`;
|
|
38
|
+
|
|
20
39
|
export const LineItemRow = styled.div`
|
|
21
40
|
display: flex;
|
|
22
41
|
align-items: center;
|
|
23
42
|
justify-content: space-between;
|
|
43
|
+
gap: 16px;
|
|
24
44
|
`;
|
|
25
45
|
|
|
26
46
|
const PayAsYouGoPriceTooltip = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
|
|
@@ -43,24 +63,70 @@ export const BilledPriceLineItem = ({
|
|
|
43
63
|
quantity: number;
|
|
44
64
|
price: Price;
|
|
45
65
|
}) => {
|
|
66
|
+
const [isNestedBreakdownOpen, setIsNestedBreakdownOpen] = useState(false);
|
|
67
|
+
const toggleNestedBreakdown = () => setIsNestedBreakdownOpen((prev) => !prev);
|
|
68
|
+
|
|
46
69
|
const isPayAsYouGo = price.pricingModel === BillingModel.UsageBased;
|
|
70
|
+
const totalAmount = price.isTieredPrice ? calculateTierPrice(price, quantity) : (price.amount || 0) * quantity;
|
|
71
|
+
|
|
72
|
+
let nestedBreakdown: ReactNode;
|
|
73
|
+
const shouldShowGraduatedPriceBreakdown =
|
|
74
|
+
price.tiersMode === TiersMode.Graduated &&
|
|
75
|
+
!!price.tiers &&
|
|
76
|
+
!isEmpty(price.tiers) &&
|
|
77
|
+
!isNil(price.tiers[0].upTo) &&
|
|
78
|
+
quantity > price.tiers[0].upTo;
|
|
79
|
+
|
|
80
|
+
if (shouldShowGraduatedPriceBreakdown) {
|
|
81
|
+
nestedBreakdown = <GraduatedPriceBreakdown price={price} unitQuantity={quantity} />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let title: ReactNode = (
|
|
85
|
+
<Typography variant="body1" color="secondary">
|
|
86
|
+
{label}
|
|
87
|
+
</Typography>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (nestedBreakdown) {
|
|
91
|
+
title = (
|
|
92
|
+
<Link onClick={toggleNestedBreakdown} underline="none" style={{ cursor: 'pointer' }}>
|
|
93
|
+
{title}
|
|
94
|
+
</Link>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
47
97
|
|
|
48
98
|
return (
|
|
49
99
|
<LineItemContainer>
|
|
50
100
|
<LineItemRow style={{ alignItems: 'flex-start' }}>
|
|
51
|
-
<Grid item>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
101
|
+
<Grid item display="flex" gap={0.5} style={{ whiteSpace: 'nowrap' }}>
|
|
102
|
+
{title}
|
|
103
|
+
{nestedBreakdown && (
|
|
104
|
+
<IconButton onClick={toggleNestedBreakdown} sx={{ padding: 0 }}>
|
|
105
|
+
<CollapsableSectionIcon $isOpen={isNestedBreakdownOpen} $size={16} />
|
|
106
|
+
</IconButton>
|
|
107
|
+
)}
|
|
55
108
|
</Grid>
|
|
56
109
|
<Grid item display="flex" gap={1} alignItems="center">
|
|
57
110
|
{isPayAsYouGo && <PayAsYouGoPriceTooltip checkoutLocalization={checkoutLocalization} />}
|
|
58
|
-
<Typography variant="body1" color="secondary" style={{
|
|
59
|
-
{getPriceBreakdownString({
|
|
111
|
+
<Typography variant="body1" color="secondary" style={{ whiteSpace: 'nowrap' }}>
|
|
112
|
+
{getPriceBreakdownString({
|
|
113
|
+
totalAmount,
|
|
114
|
+
quantity,
|
|
115
|
+
currency: price.currency,
|
|
116
|
+
pricingModel: price.pricingModel,
|
|
117
|
+
billingPeriod: price.billingPeriod,
|
|
118
|
+
tiers: price.tiers,
|
|
119
|
+
tiersMode: price.tiersMode,
|
|
120
|
+
})}
|
|
60
121
|
{isPayAsYouGo && ' / unit'}
|
|
61
122
|
</Typography>
|
|
62
123
|
</Grid>
|
|
63
124
|
</LineItemRow>
|
|
125
|
+
{nestedBreakdown && (
|
|
126
|
+
<Collapse in={isNestedBreakdownOpen}>
|
|
127
|
+
<NestedBreakdownContainer>{nestedBreakdown}</NestedBreakdownContainer>
|
|
128
|
+
</Collapse>
|
|
129
|
+
)}
|
|
64
130
|
</LineItemContainer>
|
|
65
131
|
);
|
|
66
132
|
};
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import { BillingModel, BillingPeriod, Price, TiersMode } from '@stigg/js-client-sdk';
|
|
1
|
+
import { BillingModel, BillingPeriod, Currency, Price, TiersMode } from '@stigg/js-client-sdk';
|
|
2
2
|
import { currencyPriceFormatter } from '../../../utils/currencyUtils';
|
|
3
|
-
import {
|
|
3
|
+
import { isBulkTiers, isQuantityInFirstTier } from '../../../utils/priceTierUtils';
|
|
4
|
+
import { numberFormatter } from '../../../utils/numberUtils';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export type GetPriceBreakdownStringProps = {
|
|
7
|
+
totalAmount: number;
|
|
8
|
+
quantity: number;
|
|
9
|
+
tiersMode: Price['tiersMode'];
|
|
10
|
+
tiers: Price['tiers'];
|
|
11
|
+
currency: Currency;
|
|
12
|
+
pricingModel: BillingModel;
|
|
13
|
+
billingPeriod: BillingPeriod;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function formatPricePerUnit({
|
|
17
|
+
quantity,
|
|
18
|
+
totalAmount,
|
|
19
|
+
pricingModel,
|
|
20
|
+
billingPeriod,
|
|
21
|
+
currency,
|
|
22
|
+
}: GetPriceBreakdownStringProps) {
|
|
7
23
|
const isPerUnit = pricingModel === BillingModel.PerUnit;
|
|
8
|
-
const featureUnits = quantity && (isPerUnit || quantity > 1) ? `${
|
|
24
|
+
const featureUnits = quantity && (isPerUnit || quantity > 1) ? `${numberFormatter(quantity)} x ` : '';
|
|
9
25
|
const billingPeriodString = billingPeriod === BillingPeriod.Annually ? ' x 12 months' : '';
|
|
10
26
|
|
|
11
27
|
const unitPrice = totalAmount / quantity / (billingPeriod === BillingPeriod.Annually ? 12 : 1);
|
|
@@ -25,21 +41,16 @@ function formatPricePerUnit({ price, quantity, totalAmount }: { price: Price; qu
|
|
|
25
41
|
}`;
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
export function getPriceBreakdownString(
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
if (isBulkTiers(price.tiers) || price.tiersMode === TiersMode.Graduated) {
|
|
44
|
+
export function getPriceBreakdownString(params: GetPriceBreakdownStringProps) {
|
|
45
|
+
const { totalAmount, quantity, tiersMode, tiers, currency } = params;
|
|
46
|
+
if (isBulkTiers(tiers) || (tiersMode === TiersMode.Graduated && !isQuantityInFirstTier(tiers, quantity))) {
|
|
32
47
|
const formattedTotalPrice = currencyPriceFormatter({
|
|
33
|
-
amount,
|
|
34
|
-
currency
|
|
48
|
+
amount: totalAmount,
|
|
49
|
+
currency,
|
|
35
50
|
minimumFractionDigits: 2,
|
|
36
51
|
});
|
|
37
|
-
return `${quantity} for ${formattedTotalPrice}`;
|
|
52
|
+
return `${numberFormatter(quantity)} for ${formattedTotalPrice}`;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
return formatPricePerUnit(
|
|
41
|
-
price,
|
|
42
|
-
quantity,
|
|
43
|
-
totalAmount: amount,
|
|
44
|
-
});
|
|
55
|
+
return formatPricePerUnit(params);
|
|
45
56
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChevronRight } from 'react-feather';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export const CollapsableSectionIcon = styled(ChevronRight)<{ $isOpen: boolean; $size?: number }>`
|
|
5
|
+
height: ${({ $size = 24 }) => `${$size}px`};
|
|
6
|
+
width: ${({ $size = 24 }) => `${$size}px`};
|
|
7
|
+
transition: all 0.2s ease-in;
|
|
8
|
+
${({ $isOpen }) => $isOpen && `transform: rotate(90deg)`}
|
|
9
|
+
`;
|
|
@@ -127,6 +127,10 @@ export function isBulkTiers(tiers: PriceTierFragment[] | null | undefined) {
|
|
|
127
127
|
return tiers?.every(({ unitPrice, upTo }) => !unitPrice || isNil(upTo));
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export function isQuantityInFirstTier(tiers: PriceTierFragment[] | null | undefined, quantity: number) {
|
|
131
|
+
return tiers?.[0].upTo && quantity <= tiers[0].upTo;
|
|
132
|
+
}
|
|
133
|
+
|
|
130
134
|
export function getTiersPerUnitQuantities(
|
|
131
135
|
plan: PaywallPlan,
|
|
132
136
|
billingPeriod: BillingPeriod,
|
|
@@ -41,7 +41,8 @@ export const getResolvedTheme = (customizedTheme?: CustomizedTheme): StiggTheme
|
|
|
41
41
|
text: {
|
|
42
42
|
primary: textColor.hex(),
|
|
43
43
|
secondary: textColor.alpha(0.75).toString(),
|
|
44
|
-
|
|
44
|
+
tertiary: textColor.alpha(0.5).toString(),
|
|
45
|
+
disabled: textColor.alpha(0.35).toString(),
|
|
45
46
|
},
|
|
46
47
|
},
|
|
47
48
|
layout: {
|