@stigg/react-sdk 4.4.0-beta.9 → 4.4.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.
Files changed (91) hide show
  1. package/dist/components/checkout/Checkout.d.ts +3 -2
  2. package/dist/components/checkout/CheckoutContainer.d.ts +4 -2
  3. package/dist/components/checkout/CheckoutProvider.d.ts +3 -2
  4. package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +3 -7
  5. package/dist/components/checkout/components/StyledArrow.d.ts +5 -0
  6. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
  7. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +3 -1
  8. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +17 -8
  9. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +1 -0
  10. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +2 -4
  11. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +2 -1
  12. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +6 -2
  13. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +1 -1
  14. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -1
  15. package/dist/components/checkout/summary/CheckoutSummary.d.ts +1 -1
  16. package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +2 -2
  17. package/dist/components/checkout/summary/components/LineItems.d.ts +12 -8
  18. package/dist/components/checkout/textOverrides.d.ts +63 -20
  19. package/dist/components/checkout/theme.d.ts +0 -1
  20. package/dist/components/checkout/types.d.ts +7 -1
  21. package/dist/components/common/TiersSelectContainer.d.ts +1 -2
  22. package/dist/components/common/Typography.d.ts +2 -2
  23. package/dist/components/common/customIcons.d.ts +2 -0
  24. package/dist/components/customerPortal/subscriptionOverview/subscriptionView/SubscriptionView.style.d.ts +1 -1
  25. package/dist/components/paywall/PlanPrice.d.ts +1 -1
  26. package/dist/components/utils/currencyUtils.d.ts +2 -1
  27. package/dist/components/utils/getFeatureName.d.ts +1 -0
  28. package/dist/react-sdk.cjs.development.js +1027 -559
  29. package/dist/react-sdk.cjs.development.js.map +1 -1
  30. package/dist/react-sdk.cjs.production.min.js +1 -1
  31. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  32. package/dist/react-sdk.esm.js +1041 -582
  33. package/dist/react-sdk.esm.js.map +1 -1
  34. package/dist/stories/mocks/checkout/consts.d.ts +11 -0
  35. package/dist/stories/mocks/checkout/mockCheckoutPreview.d.ts +2 -0
  36. package/dist/stories/mocks/checkout/mockCheckoutState.d.ts +2 -0
  37. package/dist/theme/getResolvedTheme.d.ts +1 -0
  38. package/dist/theme/types.d.ts +1 -0
  39. package/package.json +28 -20
  40. package/src/assets/coupon.svg +6 -0
  41. package/src/assets/pay-as-you-go-charge.svg +11 -0
  42. package/src/components/checkout/Checkout.tsx +5 -2
  43. package/src/components/checkout/CheckoutContainer.tsx +18 -12
  44. package/src/components/checkout/CheckoutProvider.tsx +5 -3
  45. package/src/components/checkout/components/DowngradeToFreeContainer.tsx +33 -36
  46. package/src/components/checkout/components/StyledArrow.tsx +9 -0
  47. package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
  48. package/src/components/checkout/hooks/useLoadCheckout.ts +10 -2
  49. package/src/components/checkout/hooks/usePreviewSubscription.ts +102 -50
  50. package/src/components/checkout/planHeader/PlanHeader.tsx +18 -25
  51. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +11 -12
  52. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +7 -6
  53. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +32 -9
  54. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +15 -11
  55. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +4 -3
  56. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +21 -7
  57. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +0 -1
  58. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +4 -3
  59. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +4 -1
  60. package/src/components/checkout/steps/payment/PaymentStep.tsx +0 -1
  61. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +3 -2
  62. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +5 -2
  63. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +35 -14
  64. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +10 -5
  65. package/src/components/checkout/summary/CheckoutSuccess.tsx +1 -1
  66. package/src/components/checkout/summary/CheckoutSummary.tsx +143 -46
  67. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +38 -15
  68. package/src/components/checkout/summary/components/LineItems.tsx +77 -28
  69. package/src/components/checkout/textOverrides.ts +107 -27
  70. package/src/components/checkout/theme.ts +0 -4
  71. package/src/components/checkout/types.ts +15 -1
  72. package/src/components/common/Icon.tsx +4 -6
  73. package/src/components/common/TiersSelectContainer.tsx +7 -8
  74. package/src/components/common/Typography.tsx +12 -3
  75. package/src/components/common/customIcons.ts +2 -0
  76. package/src/components/common/mapExternalTheme.ts +1 -2
  77. package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +4 -3
  78. package/src/components/paywall/PlanOfferingButton.tsx +6 -8
  79. package/src/components/paywall/PlanPrice.tsx +14 -17
  80. package/src/components/utils/currencyUtils.ts +4 -2
  81. package/src/components/utils/getFeatureName.ts +13 -5
  82. package/src/stories/Checkout.stories.tsx +37 -6
  83. package/src/stories/CustomerPortal.stories.tsx +2 -2
  84. package/src/stories/mocks/checkout/consts.ts +15 -0
  85. package/src/stories/mocks/checkout/mockCheckoutPreview.ts +138 -0
  86. package/src/stories/mocks/checkout/mockCheckoutState.ts +206 -0
  87. package/src/theme/Theme.tsx +10 -1
  88. package/src/theme/getResolvedTheme.ts +1 -0
  89. package/src/theme/types.ts +1 -0
  90. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +0 -25
  91. package/src/components/checkout/planHeader/PlanHeader.style.tsx +0 -23
@@ -10,10 +10,11 @@ import { useChargesSort } from '../../../hooks/useChargeSort';
10
10
  import { calculateUnitQuantityText } from '../../../paywall/utils/calculateUnitQuantityText';
11
11
  import { currencyPriceFormatter } from '../../../utils/currencyUtils';
12
12
  import { InputField } from '../../components';
13
- import { usePlanStepModel, useProgressBarModel } from '../../hooks';
13
+ import { useCheckoutModel, usePlanStepModel, useProgressBarModel } from '../../hooks';
14
14
  import { TiersSelectContainer } from '../../../common/TiersSelectContainer';
15
- import { getPriceFeatureUnit, getTierByQuantity } from '../../../utils/priceTierUtils';
15
+ import { getTierByQuantity } from '../../../utils/priceTierUtils';
16
16
  import { getValidPriceQuantity } from '../../../utils/priceUtils';
17
+ import { getFeatureDisplayNameText } from '../../../utils/getFeatureName';
17
18
 
18
19
  export type UsePlanStepModel = ReturnType<typeof usePlanStepModel>;
19
20
 
@@ -31,6 +32,19 @@ const StyledPlanCharge = styled.div`
31
32
  margin-top: 16px;
32
33
  `;
33
34
 
35
+ const getValidationText = (charge: Price, quantity?: number) => {
36
+ const { minUnitQuantity, maxUnitQuantity } = charge;
37
+ if (!quantity || quantity < (minUnitQuantity || 1)) {
38
+ return `Minimum ${minUnitQuantity || 1}`;
39
+ }
40
+
41
+ if (maxUnitQuantity && quantity > maxUnitQuantity) {
42
+ return `Maximum ${maxUnitQuantity}`;
43
+ }
44
+
45
+ return '';
46
+ };
47
+
34
48
  export function PlanCharge({
35
49
  charge,
36
50
  isValid,
@@ -47,14 +61,22 @@ export function PlanCharge({
47
61
  const featureId = charge.feature?.featureId;
48
62
  const isBaseCharge = !featureId;
49
63
  const isPayAsYouGo = charge.pricingModel === BillingModel.UsageBased;
50
- const displayName = isBaseCharge ? 'Base charge' : charge.feature?.displayName;
64
+ const displayName = isBaseCharge
65
+ ? 'Base charge'
66
+ : getFeatureDisplayNameText(charge.feature?.displayName || '', charge.feature?.units, charge.feature?.unitsPlural);
51
67
  const hasQuantityRestrictions = !!(charge?.minUnitQuantity || charge?.maxUnitQuantity);
52
68
 
53
69
  const handleQuantityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
54
70
  if (isBaseCharge || !featureId) return;
55
71
 
56
- const value = event?.target?.value ? Number(event?.target?.value) : charge.minUnitQuantity;
57
- if (!value || value <= 0) {
72
+ const { minUnitQuantity, maxUnitQuantity } = charge;
73
+ const value = event?.target?.value ? Number(event?.target?.value) : null;
74
+ if (
75
+ !value ||
76
+ value <= 0 ||
77
+ (minUnitQuantity && value < minUnitQuantity) ||
78
+ (maxUnitQuantity && value > maxUnitQuantity)
79
+ ) {
58
80
  onValidationChange({ featureId, isValid: false });
59
81
  // Reset the input value to null
60
82
  // @ts-ignore
@@ -74,22 +96,22 @@ export function PlanCharge({
74
96
  amount: charge.amount!,
75
97
  currency: charge.currency,
76
98
  locale: 'en-us',
99
+ minimumFractionDigits: 2,
77
100
  });
78
101
 
79
102
  chargeRow = `${formattedAmount}`;
80
103
  if (isPayAsYouGo) {
81
104
  chargeRow += ' / unit';
82
105
  }
83
- } else if (charge.isTieredPrice) {
106
+ } else if (charge.isTieredPrice && featureId) {
84
107
  const tier = getTierByQuantity(charge.tiers!, billableFeature!.quantity || 1);
85
108
  chargeRow = (
86
109
  <TiersSelectContainer
87
110
  componentId={`${featureId}-tiers`}
88
111
  tiers={charge.tiers}
89
- tierUnits={getPriceFeatureUnit(charge)}
90
112
  selectedTier={tier}
91
113
  handleTierChange={(tier: PriceTierFragment) => {
92
- setBillableFeature(featureId!, tier.upTo);
114
+ setBillableFeature(featureId, tier.upTo);
93
115
  }}
94
116
  />
95
117
  );
@@ -99,13 +121,10 @@ export function PlanCharge({
99
121
  sx={{ width: 120 }}
100
122
  id={`${featureId}-input`}
101
123
  type="number"
102
- InputProps={
103
- hasQuantityRestrictions ? { inputProps: { min: charge.minUnitQuantity, max: charge.maxUnitQuantity } } : {}
104
- }
105
124
  error={!isValid}
106
- helperText={!isValid ? 'Minimum 1' : undefined}
125
+ helperText={!isValid ? getValidationText(charge, billableFeature?.quantity) : undefined}
107
126
  FormHelperTextProps={{ sx: { margin: '4px' } }}
108
- value={billableFeature?.quantity ?? charge.minUnitQuantity ?? ''}
127
+ value={billableFeature?.quantity ?? ''}
109
128
  onChange={handleQuantityChange}
110
129
  onWheel={(e: React.WheelEvent<HTMLInputElement>) => (e.target as HTMLElement).blur()}
111
130
  />
@@ -135,6 +154,7 @@ export function PlanCharge({
135
154
  export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListProps) {
136
155
  const { billableFeatures, setBillableFeature } = usePlanStepModel();
137
156
  const { setIsDisabled } = useProgressBarModel();
157
+ const { setIsValid } = useCheckoutModel();
138
158
  const planCharges = useChargesSort(plan?.pricePoints?.filter((p) => p.billingPeriod === billingPeriod) || []);
139
159
  const [chargesValidation, setChargesValidation] = useState(
140
160
  planCharges?.reduce<Record<string, boolean>>((acc, curr) => {
@@ -146,7 +166,8 @@ export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListPr
146
166
  useEffect(() => {
147
167
  const isDisabled = Object.values(chargesValidation).some((x) => !x);
148
168
  setIsDisabled(isDisabled);
149
- }, [chargesValidation, setIsDisabled]);
169
+ setIsValid(!isDisabled);
170
+ }, [chargesValidation, setIsDisabled, setIsValid]);
150
171
 
151
172
  return (
152
173
  <div>
@@ -1,20 +1,25 @@
1
1
  import React from 'react';
2
+ import { CheckoutContainerProps } from '../../CheckoutContainer';
2
3
 
3
4
  import { useCheckoutModel } from '../../hooks/useCheckoutModel';
4
5
  import { usePlanStepModel } from '../../hooks/usePlanStepModel';
6
+ import { PlanHeader } from '../../planHeader';
5
7
  import { BillingPeriodPicker } from './BillingPeriodPicker';
6
8
  import { CheckoutChargeList } from './CheckoutChargeList';
7
9
  import { CheckoutPlanContainer } from './CheckoutPlanStep.style';
8
10
 
9
- export const CheckoutPlanStep = () => {
11
+ export const CheckoutPlanStep = ({ onChangePlan }: Pick<CheckoutContainerProps, 'onChangePlan'>) => {
10
12
  const { checkoutState, checkoutLocalization } = useCheckoutModel();
11
13
  const { plan } = checkoutState || {};
12
14
  const { billingPeriod } = usePlanStepModel();
13
15
 
14
16
  return (
15
- <CheckoutPlanContainer>
16
- <BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
17
- <CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
18
- </CheckoutPlanContainer>
17
+ <>
18
+ <PlanHeader allowChangePlan onChangePlan={onChangePlan} />
19
+ <CheckoutPlanContainer>
20
+ <BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
21
+ <CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
22
+ </CheckoutPlanContainer>
23
+ </>
19
24
  );
20
25
  };
@@ -69,7 +69,7 @@ export function CheckoutSuccess({ checkoutLocalization }: { checkoutLocalization
69
69
  options={{ loop: false, autoplay: true, animationData }}
70
70
  />
71
71
  <CheckoutSuccessText variant="h1" color="primary.main">
72
- {checkoutLocalization.checkoutSuccessText}
72
+ {checkoutLocalization.summary.checkoutSuccessText}
73
73
  </CheckoutSuccessText>
74
74
  </CheckoutSuccessContainer>
75
75
  );
@@ -23,6 +23,7 @@ import {
23
23
  AppliedCreditsLineItem,
24
24
  BilledPriceLineItem,
25
25
  DiscountLineItem,
26
+ FreeChargeLineItem,
26
27
  LineItemContainer,
27
28
  LineItemRow,
28
29
  TaxLineItem,
@@ -31,6 +32,7 @@ import { WithSkeleton } from './components/WithSkeleton';
31
32
  import { Icon } from '../../common/Icon';
32
33
  import { CheckoutLocalization } from '../textOverrides';
33
34
  import { CheckoutSuccess } from './CheckoutSuccess';
35
+ import { getFeatureDisplayNameText } from '../../utils/getFeatureName';
34
36
 
35
37
  export const SummaryCard = styled(Paper)`
36
38
  border-radius: 10px;
@@ -42,29 +44,28 @@ SummaryCard.defaultProps = {
42
44
  elevation: 0,
43
45
  };
44
46
 
45
- const PlanName = styled(Typography)`
47
+ const SummaryTitle = styled(Typography)`
46
48
  margin-bottom: 16px;
49
+ font-weight: 500;
47
50
  `;
48
51
 
49
52
  const StyledDivider = styled(Divider)`
50
53
  margin: 16px 0;
51
54
  `;
52
55
 
53
- const SubtotalText = styled(Typography)`
54
- margin-top: 24px;
55
- `;
56
-
57
56
  const TotalDueText = styled(Typography)`
58
57
  margin-bottom: 8px;
59
58
  `;
60
59
 
61
60
  function resolveCheckoutButtonText({
62
61
  isLastStep,
62
+ checkoutHasChanges,
63
63
  isFreeDowngrade,
64
64
  checkoutLocalization,
65
65
  isPlanUpdate,
66
66
  }: {
67
67
  isLastStep?: boolean;
68
+ checkoutHasChanges: boolean;
68
69
  isFreeDowngrade: boolean;
69
70
  checkoutLocalization: CheckoutLocalization;
70
71
  isPlanUpdate?: boolean;
@@ -73,6 +74,10 @@ function resolveCheckoutButtonText({
73
74
  return checkoutLocalization.checkoutButton.nextText;
74
75
  }
75
76
 
77
+ if (!checkoutHasChanges) {
78
+ return checkoutLocalization.checkoutButton.noChangesText;
79
+ }
80
+
76
81
  if (isPlanUpdate) {
77
82
  return checkoutLocalization.checkoutButton.updateText;
78
83
  }
@@ -90,6 +95,7 @@ export const CheckoutSummary = ({
90
95
  disablePromotionCode,
91
96
  disableSuccessAnimation,
92
97
  isFreeDowngrade,
98
+ onMockCheckoutPreview,
93
99
  }: CheckoutContainerProps & { isFreeDowngrade: boolean }) => {
94
100
  const [isCheckoutCompletedSuccessfully, setIsCheckoutCompletedSuccessfully] = useState(false);
95
101
  const { setErrorMessage } = usePaymentStepModel();
@@ -104,7 +110,7 @@ export const CheckoutSummary = ({
104
110
  const [baseCharge] = baseCharges || [];
105
111
  const isLastStep = isFreeDowngrade || (progressBar.isCheckoutComplete && progressBar.isLastStep);
106
112
 
107
- const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription();
113
+ const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription({ onMockCheckoutPreview });
108
114
 
109
115
  const { handleSubmit, isLoading } = useSubmit({
110
116
  disableSuccessAnimation,
@@ -142,25 +148,47 @@ export const CheckoutSummary = ({
142
148
  }
143
149
  };
144
150
 
151
+ const checkoutHasChanges =
152
+ !!subscriptionPreview && (!!subscriptionPreview.proration || !!subscriptionPreview.hasScheduledUpdates);
153
+ const showPromotionCodeLine = !disablePromotionCode && !isFreeDowngrade;
154
+ const showDiscountLine =
155
+ !!subscriptionPreview?.recurringSubscription &&
156
+ !!subscriptionPreview?.recurringSubscription?.discountDetails &&
157
+ !isFreeDowngrade;
158
+ const hasDiscounts = showPromotionCodeLine || showDiscountLine;
159
+
160
+ const hasPayAsYouGoCharges = usageCharges.some((price) => price.pricingModel === BillingModel.UsageBased);
161
+ const onlyPayAsYouGoCharges =
162
+ hasPayAsYouGoCharges &&
163
+ !baseCharge &&
164
+ usageCharges.every((price) => price.pricingModel === BillingModel.UsageBased);
165
+
166
+ const baseChargeLabel =
167
+ typeof checkoutLocalization.summary.baseChargeText === 'function'
168
+ ? checkoutLocalization.summary.baseChargeText({ billingPeriod: subscription.billingPeriod })
169
+ : checkoutLocalization.summary.baseChargeText;
170
+
145
171
  return (
146
172
  <Box flex={1}>
147
173
  <SummaryCard>
148
- <PlanName variant="h6">{plan?.displayName}</PlanName>
174
+ <SummaryTitle variant="h6">{checkoutLocalization.summary.title}</SummaryTitle>
175
+
176
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
177
+ <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
178
+ {checkoutLocalization.summary.planName({ plan: plan! })}
179
+ </Typography>
180
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
181
+ </Grid>
149
182
 
150
183
  {baseCharge && (
151
- <LineItemContainer>
152
- <LineItemRow>
153
- <Typography variant="h6" color="primary">
154
- {typeof checkoutLocalization.baseChargeText === 'function'
155
- ? checkoutLocalization.baseChargeText({ billingPeriod: subscription.billingPeriod })
156
- : checkoutLocalization.baseChargeText}
157
- </Typography>
158
- <Typography variant="body1" color="secondary">
159
- {currencyPriceFormatter({ amount: baseCharge.amount!, currency: baseCharge.currency })}
160
- </Typography>
161
- </LineItemRow>
162
- </LineItemContainer>
184
+ <BilledPriceLineItem
185
+ checkoutLocalization={checkoutLocalization}
186
+ label={baseChargeLabel}
187
+ quantity={1}
188
+ price={baseCharge}
189
+ />
163
190
  )}
191
+ {!baseCharge && isFreeDowngrade ? <FreeChargeLineItem label={baseChargeLabel} /> : null}
164
192
 
165
193
  {usageCharges.map((price) => {
166
194
  const priceBillableFeature = subscription.billableFeatures?.find(
@@ -169,8 +197,13 @@ export const CheckoutSummary = ({
169
197
 
170
198
  return (
171
199
  <BilledPriceLineItem
200
+ checkoutLocalization={checkoutLocalization}
172
201
  key={price.feature?.featureId}
173
- label={price.feature?.displayName || ''}
202
+ label={getFeatureDisplayNameText(
203
+ price.feature?.displayName || '',
204
+ price.feature?.units,
205
+ price.feature?.unitsPlural,
206
+ )}
174
207
  quantity={priceBillableFeature?.quantity || 1}
175
208
  price={price}
176
209
  />
@@ -179,11 +212,11 @@ export const CheckoutSummary = ({
179
212
 
180
213
  {!!subscription.addons?.length && (
181
214
  <>
182
- <Grid display="flex" flexDirection="row" alignItems="center" marginY={1}>
215
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
183
216
  <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
184
- {checkoutLocalization.addonsSectionTitle}
217
+ {checkoutLocalization.summary.addonsSectionTitle}
185
218
  </Typography>
186
- <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1 }} />
219
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
187
220
  </Grid>
188
221
  {subscription.addons.map((addon) => {
189
222
  const addonPrice = addon.addon.pricePoints?.find(
@@ -196,6 +229,7 @@ export const CheckoutSummary = ({
196
229
 
197
230
  return (
198
231
  <BilledPriceLineItem
232
+ checkoutLocalization={checkoutLocalization}
199
233
  key={addon?.addon?.id}
200
234
  label={addon.addon.displayName}
201
235
  quantity={addonQuantity}
@@ -206,36 +240,93 @@ export const CheckoutSummary = ({
206
240
  </>
207
241
  )}
208
242
 
209
- <LineItemRow>
210
- <SubtotalText variant="h6">{checkoutLocalization.subTotalText}</SubtotalText>
211
- <SubtotalText variant="h6">
212
- <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
213
- {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.subTotal })}
214
- </WithSkeleton>
215
- </SubtotalText>
216
- </LineItemRow>
217
-
218
- <StyledDivider className="stigg-checkout-summary-divider" />
243
+ {!hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
244
+ {hasDiscounts && (
245
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
246
+ <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
247
+ {checkoutLocalization.summary.discountsSectionTitle}
248
+ </Typography>
249
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
250
+ </Grid>
251
+ )}
219
252
 
220
- {!disablePromotionCode && !isFreeDowngrade && (
221
- <>
222
- <PromotionCodeSection checkoutLocalization={checkoutLocalization} />
253
+ {showPromotionCodeLine && (
254
+ <PromotionCodeSection
255
+ disabled={isLoading || isFetchingSubscriptionPreview}
256
+ checkoutLocalization={checkoutLocalization}
257
+ onMockCheckoutPreview={onMockCheckoutPreview}
258
+ />
259
+ )}
223
260
 
224
- <StyledDivider className="stigg-checkout-summary-divider" />
225
- </>
261
+ {showDiscountLine && (
262
+ <DiscountLineItem
263
+ subscriptionPreview={subscriptionPreview!}
264
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
265
+ checkoutLocalization={checkoutLocalization}
266
+ />
226
267
  )}
227
268
 
228
- <DiscountLineItem
229
- subscriptionPreview={subscriptionPreview}
230
- isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
231
- />
269
+ {hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
232
270
 
233
271
  <TaxLineItem
234
- subscriptionPreview={subscriptionPreview}
272
+ tax={subscriptionPreview?.recurringSubscription?.tax}
273
+ taxDetails={subscriptionPreview?.recurringSubscription?.taxDetails}
235
274
  isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
236
275
  checkoutLocalization={checkoutLocalization}
237
276
  />
238
277
 
278
+ {!isFreeDowngrade ? (
279
+ <>
280
+ <LineItemRow style={{ marginTop: 16 }}>
281
+ <Grid display="flex" flexDirection="column" container>
282
+ <Grid item display="flex" justifyContent="space-between">
283
+ <Typography variant="h6">{checkoutLocalization.summary.totalText}</Typography>
284
+ <Typography variant="h6">
285
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
286
+ {onlyPayAsYouGoCharges ? checkoutLocalization.summary.onlyPayAsYouGoText : null}
287
+ {!onlyPayAsYouGoCharges && hasPayAsYouGoCharges
288
+ ? checkoutLocalization.summary.startsAtText
289
+ : null}
290
+ {!onlyPayAsYouGoCharges
291
+ ? currencyPriceFormatter({
292
+ amount: 0,
293
+ ...subscriptionPreview?.recurringSubscription?.total,
294
+ minimumFractionDigits: 2,
295
+ })
296
+ : null}
297
+ </WithSkeleton>
298
+ </Typography>
299
+ </Grid>
300
+ <Grid item>
301
+ <Typography variant="body1" color="secondary">
302
+ {checkoutLocalization.summary.totalBillingPeriodText({ billingPeriod: subscription.billingPeriod })}
303
+ </Typography>
304
+ </Grid>
305
+ </Grid>
306
+ </LineItemRow>
307
+ <StyledDivider className="stigg-checkout-summary-divider" />
308
+ </>
309
+ ) : null}
310
+
311
+ {subscriptionPreview?.subTotal && subscriptionPreview?.subTotal.amount > 0 ? (
312
+ <LineItemContainer>
313
+ <LineItemRow>
314
+ <Typography variant="body1" color="secondary">
315
+ {checkoutLocalization.summary.proratedTotalDueText}
316
+ </Typography>
317
+ <Typography variant="body1" color="secondary">
318
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
319
+ {currencyPriceFormatter({
320
+ amount: subscriptionPreview?.subTotal.amount + (subscriptionPreview?.tax?.amount || 0),
321
+ currency: subscriptionPreview?.subTotal.currency,
322
+ minimumFractionDigits: 2,
323
+ })}
324
+ </WithSkeleton>
325
+ </Typography>
326
+ </LineItemRow>
327
+ </LineItemContainer>
328
+ ) : null}
329
+
239
330
  <AppliedCreditsLineItem
240
331
  subscriptionPreview={subscriptionPreview}
241
332
  isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
@@ -244,10 +335,10 @@ export const CheckoutSummary = ({
244
335
 
245
336
  <LineItemContainer>
246
337
  <LineItemRow>
247
- <TotalDueText variant="h6">{checkoutLocalization.totalText}</TotalDueText>
338
+ <TotalDueText variant="h6">{checkoutLocalization.summary.totalDueText}</TotalDueText>
248
339
  <TotalDueText variant="h6">
249
340
  <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
250
- {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total })}
341
+ {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total, minimumFractionDigits: 2 })}
251
342
  </WithSkeleton>
252
343
  </TotalDueText>
253
344
  </LineItemRow>
@@ -265,7 +356,12 @@ export const CheckoutSummary = ({
265
356
  <Button
266
357
  $success={isCheckoutCompletedSuccessfully}
267
358
  $error={isLastStep && isFreeDowngrade}
268
- disabled={isLoading || isFetchingSubscriptionPreview || progressBar.progressBarState.isDisabled}
359
+ disabled={
360
+ isLoading ||
361
+ isFetchingSubscriptionPreview ||
362
+ progressBar.progressBarState.isDisabled ||
363
+ (isLastStep && !checkoutHasChanges)
364
+ }
269
365
  className="stigg-checkout-summary-cta-button"
270
366
  sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px', height: '36px' }}
271
367
  variant="contained"
@@ -282,6 +378,7 @@ export const CheckoutSummary = ({
282
378
  ) : (
283
379
  resolveCheckoutButtonText({
284
380
  isLastStep,
381
+ checkoutHasChanges,
285
382
  isFreeDowngrade,
286
383
  checkoutLocalization,
287
384
  isPlanUpdate: !!activeSubscription && activeSubscription?.plan.id === plan?.id,
@@ -1,13 +1,14 @@
1
+ /* eslint-disable react/no-unused-prop-types */
1
2
  import React from 'react';
2
3
  import moment from 'moment';
3
- import { BillingModel, BillingPeriod, Plan, Subscription, SubscriptionPreview } from '@stigg/js-client-sdk';
4
+ import { BillingModel, BillingPeriod, Plan, Subscription, SubscriptionPreviewV2 } from '@stigg/js-client-sdk';
4
5
  import { Typography } from '../../../common/Typography';
5
6
  import { currencyPriceFormatter } from '../../../utils/currencyUtils';
6
7
  import { CheckoutLocalization } from '../../textOverrides';
7
8
  import { WithSkeleton } from './WithSkeleton';
8
9
 
9
10
  export type CheckoutCaptionProps = {
10
- subscriptionPreview?: SubscriptionPreview | null;
11
+ subscriptionPreview?: SubscriptionPreviewV2 | null;
11
12
  isFetchingSubscriptionPreview: boolean;
12
13
  activeSubscription?: Subscription | null;
13
14
  plan?: Plan;
@@ -15,9 +16,11 @@ export type CheckoutCaptionProps = {
15
16
  billingPeriod: BillingPeriod;
16
17
  };
17
18
 
18
- // TODO: move to localization
19
-
20
- const RemainingCreditsCaption = ({ subscriptionPreview, isFetchingSubscriptionPreview }: CheckoutCaptionProps) => {
19
+ const RemainingCreditsCaption = ({
20
+ subscriptionPreview,
21
+ isFetchingSubscriptionPreview,
22
+ checkoutLocalization,
23
+ }: CheckoutCaptionProps) => {
21
24
  if (!subscriptionPreview?.proration?.netAmount?.amount || subscriptionPreview?.proration?.netAmount?.amount >= 0) {
22
25
  return null;
23
26
  }
@@ -27,12 +30,13 @@ const RemainingCreditsCaption = ({ subscriptionPreview, isFetchingSubscriptionPr
27
30
  const credits = currencyPriceFormatter({
28
31
  amount: positiveAmount,
29
32
  currency: subscriptionPreview.proration.netAmount.currency,
33
+ minimumFractionDigits: 2,
30
34
  });
31
35
 
32
36
  return (
33
37
  <Typography variant="caption" style={{ marginTop: 14 }}>
34
38
  <WithSkeleton isLoading={isFetchingSubscriptionPreview} width="100%">
35
- {`Your account will be granted credits worth ${credits} for unused time, which will be automatically applied to future payments.`}
39
+ {checkoutLocalization.summary.creditsForUnusedTimeText({ credits })}
36
40
  </WithSkeleton>
37
41
  </Typography>
38
42
  );
@@ -50,9 +54,9 @@ const ScheduledUpdatesCaption = ({
50
54
 
51
55
  const { currentBillingPeriodEnd } = activeSubscription;
52
56
  const changesWillApplyText =
53
- typeof checkoutLocalization.changesWillApplyAtEndOfBillingPeriod === 'function'
54
- ? checkoutLocalization.changesWillApplyAtEndOfBillingPeriod({ billingPeriodEnd: currentBillingPeriodEnd })
55
- : checkoutLocalization.changesWillApplyAtEndOfBillingPeriod;
57
+ typeof checkoutLocalization.summary.changesWillApplyAtEndOfBillingPeriod === 'function'
58
+ ? checkoutLocalization.summary.changesWillApplyAtEndOfBillingPeriod({ billingPeriodEnd: currentBillingPeriodEnd })
59
+ : checkoutLocalization.summary.changesWillApplyAtEndOfBillingPeriod;
56
60
 
57
61
  return (
58
62
  <Typography variant="caption" style={{ marginTop: 14 }}>
@@ -70,15 +74,18 @@ const NextBillingCaption = ({
70
74
  isFetchingSubscriptionPreview,
71
75
  billingPeriod,
72
76
  }: CheckoutCaptionProps) => {
73
- if (!subscriptionPreview?.subscription?.total) {
77
+ if (!subscriptionPreview?.recurringSubscription?.total) {
74
78
  return null;
75
79
  }
76
80
 
77
81
  const currentBillingPeriodEnd = subscriptionPreview.hasScheduledUpdates
78
82
  ? activeSubscription?.currentBillingPeriodEnd
79
83
  : subscriptionPreview?.billingPeriodRange.end;
80
- const billingDate = moment(currentBillingPeriodEnd).format('MMM D, YYYY');
81
- const total = currencyPriceFormatter(subscriptionPreview?.subscription.total);
84
+
85
+ const total = currencyPriceFormatter({
86
+ ...subscriptionPreview?.recurringSubscription.total,
87
+ minimumFractionDigits: 2,
88
+ });
82
89
 
83
90
  const hasUnitBasedPricing = plan?.pricePoints.some((price) => price.pricingModel === BillingModel.UsageBased);
84
91
  const hasNonUnitBasedPricing = plan?.pricePoints.some((price) => price.pricingModel !== BillingModel.UsageBased);
@@ -93,11 +100,27 @@ const NextBillingCaption = ({
93
100
  totalAmountText += '+';
94
101
  }
95
102
 
96
- totalAmountText += ' applicable usage-based fees for this subscription ';
103
+ totalAmountText += ' applicable usage-based charges for this subscription ';
104
+ }
105
+
106
+ let nextBillingDate;
107
+ let billingPeriodText;
108
+
109
+ switch (billingPeriod) {
110
+ case BillingPeriod.Monthly:
111
+ billingPeriodText = 'month';
112
+ nextBillingDate = `the ${moment(currentBillingPeriodEnd).format('Do')}`;
113
+ break;
114
+ case BillingPeriod.Annually:
115
+ billingPeriodText = 'year';
116
+ nextBillingDate = moment(currentBillingPeriodEnd).format('MMMM Do');
117
+ break;
118
+ default: {
119
+ break;
120
+ }
97
121
  }
98
122
 
99
- const billingPeriodText = billingPeriod === BillingPeriod.Monthly ? 'month' : 'year';
100
- text += `${totalAmountText}for this subscription every ${billingPeriodText} on ${billingDate}, unless you cancel.`;
123
+ text += `${totalAmountText}for this subscription every ${billingPeriodText} on ${nextBillingDate}, unless you cancel.`;
101
124
 
102
125
  return (
103
126
  <Typography variant="caption" style={{ marginTop: 14 }}>