@stigg/react-sdk 4.3.0 → 4.4.0-beta.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 (184) hide show
  1. package/README.md +1 -1
  2. package/dist/components/checkout/Checkout.d.ts +5 -0
  3. package/dist/components/checkout/CheckoutContainer.d.ts +22 -0
  4. package/dist/components/checkout/CheckoutContainer.style.d.ts +26 -0
  5. package/dist/components/checkout/CheckoutProvider.d.ts +34 -0
  6. package/dist/components/checkout/components/Button.d.ts +8 -0
  7. package/dist/components/checkout/components/ContentLoadingSkeleton.d.ts +2 -0
  8. package/dist/components/checkout/components/InputField.d.ts +8 -0
  9. package/dist/components/checkout/components/Skeletons.style.d.ts +97 -0
  10. package/dist/components/checkout/components/index.d.ts +3 -0
  11. package/dist/components/checkout/formatting.d.ts +2 -0
  12. package/dist/components/checkout/hooks/index.d.ts +8 -0
  13. package/dist/components/checkout/hooks/useAddonsStepModel.d.ts +21 -0
  14. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +10 -0
  15. package/dist/components/checkout/hooks/useCouponModel.d.ts +7 -0
  16. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +11 -0
  17. package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +16 -0
  18. package/dist/components/checkout/hooks/usePlanStepModel.d.ts +23 -0
  19. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +13 -0
  20. package/dist/components/checkout/hooks/useProgressBarModel.d.ts +26 -0
  21. package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +5 -0
  22. package/dist/components/checkout/hooks/useSubscriptionState.d.ts +2 -0
  23. package/dist/components/checkout/index.d.ts +3 -0
  24. package/dist/components/checkout/planHeader/PlanHeader.d.ts +7 -0
  25. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +25 -0
  26. package/dist/components/checkout/planHeader/index.d.ts +1 -0
  27. package/dist/components/checkout/progressBar/CheckoutProgressBar.d.ts +2 -0
  28. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +48 -0
  29. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +5 -0
  30. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +7 -0
  31. package/dist/components/checkout/promotionCode/AppliedPromotionCode.d.ts +6 -0
  32. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +5 -0
  33. package/dist/components/checkout/promotionCode/index.d.ts +1 -0
  34. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.d.ts +2 -0
  35. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.style.d.ts +93 -0
  36. package/dist/components/checkout/steps/addons/addon.utils.d.ts +15 -0
  37. package/dist/components/checkout/steps/addons/index.d.ts +1 -0
  38. package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +20 -0
  39. package/dist/components/checkout/steps/payment/PaymentMethods.style.d.ts +117 -0
  40. package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -0
  41. package/dist/components/checkout/steps/payment/index.d.ts +1 -0
  42. package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -0
  43. package/dist/components/checkout/steps/payment/stripe/index.d.ts +3 -0
  44. package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +33 -0
  45. package/dist/components/checkout/steps/payment/stripe/useStripeIntegration.d.ts +5 -0
  46. package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +13 -0
  47. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +9 -0
  48. package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +52 -0
  49. package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +16 -0
  50. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -0
  51. package/dist/components/checkout/steps/plan/CheckoutPlanStep.style.d.ts +12 -0
  52. package/dist/components/checkout/steps/plan/index.d.ts +1 -0
  53. package/dist/components/checkout/steps/surprise/SurpriseStep.d.ts +2 -0
  54. package/dist/components/checkout/summary/CheckoutSuccess.d.ts +3 -0
  55. package/dist/components/checkout/summary/CheckoutSummary.d.ts +16 -0
  56. package/dist/components/checkout/summary/CheckoutSummarySkeleton.d.ts +2 -0
  57. package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +11 -0
  58. package/dist/components/checkout/summary/components/LineItems.d.ts +36 -0
  59. package/dist/components/checkout/summary/components/WithSkeleton.d.ts +6 -0
  60. package/dist/components/checkout/summary/index.d.ts +2 -0
  61. package/dist/components/checkout/textOverrides.d.ts +32 -0
  62. package/dist/components/checkout/theme.d.ts +12 -0
  63. package/dist/components/common/Icon.d.ts +3 -2
  64. package/dist/components/common/PoweredByStigg.d.ts +1 -1
  65. package/dist/components/{paywall/TiersLayout.d.ts → common/TiersSelectContainer.d.ts} +2 -3
  66. package/dist/components/common/customIcons.d.ts +19 -5
  67. package/dist/components/common/mapExternalTheme.d.ts +2 -1
  68. package/dist/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.d.ts +1 -1
  69. package/dist/components/hooks/useChargeSort.d.ts +3 -0
  70. package/dist/components/paywall/paywallTextOverrides.d.ts +0 -2
  71. package/dist/components/utils/calculateDiscountRate.d.ts +1 -0
  72. package/dist/components/utils/currencyUtils.d.ts +1 -1
  73. package/dist/components/{paywall/planPriceTier.d.ts → utils/priceTierUtils.d.ts} +3 -1
  74. package/dist/components/utils/priceUtils.d.ts +2 -0
  75. package/dist/index.d.ts +1 -0
  76. package/dist/react-sdk.cjs.development.js +6930 -228
  77. package/dist/react-sdk.cjs.development.js.map +1 -1
  78. package/dist/react-sdk.cjs.production.min.js +1 -1
  79. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  80. package/dist/react-sdk.esm.js +7089 -232
  81. package/dist/react-sdk.esm.js.map +1 -1
  82. package/dist/stories/Checkout.stories.d.ts +3 -0
  83. package/dist/stories/CustomerPortal.stories.d.ts +1 -1
  84. package/dist/theme/getResolvedTheme.d.ts +4 -0
  85. package/dist/theme/types.d.ts +4 -0
  86. package/package.json +11 -5
  87. package/src/assets/arrow-forward.svg +3 -0
  88. package/src/assets/arrow-right.svg +6 -0
  89. package/src/assets/check.svg +5 -0
  90. package/src/assets/close.svg +3 -0
  91. package/src/assets/lottie/checkout-success.json +1 -0
  92. package/src/assets/nyancat.svg +634 -0
  93. package/src/assets/outlined-checked-circle-disabled.svg +6 -0
  94. package/src/assets/outlined-checked-circle.svg +6 -0
  95. package/src/assets/outlined-circle.svg +3 -0
  96. package/src/assets/payment-method.svg +11 -0
  97. package/src/assets/plus-icon.svg +6 -0
  98. package/src/assets/trash.svg +8 -0
  99. package/src/components/checkout/Checkout.tsx +30 -0
  100. package/src/components/checkout/CheckoutContainer.style.ts +35 -0
  101. package/src/components/checkout/CheckoutContainer.tsx +99 -0
  102. package/src/components/checkout/CheckoutProvider.tsx +149 -0
  103. package/src/components/checkout/components/Button.tsx +51 -0
  104. package/src/components/checkout/components/ContentLoadingSkeleton.tsx +41 -0
  105. package/src/components/checkout/components/InputField.tsx +22 -0
  106. package/src/components/checkout/components/Skeletons.style.ts +27 -0
  107. package/src/components/checkout/components/index.ts +3 -0
  108. package/src/components/checkout/formatting.ts +12 -0
  109. package/src/components/checkout/hooks/index.ts +8 -0
  110. package/src/components/checkout/hooks/useAddonsStepModel.ts +96 -0
  111. package/src/components/checkout/hooks/useCheckoutModel.ts +32 -0
  112. package/src/components/checkout/hooks/useCouponModel.ts +28 -0
  113. package/src/components/checkout/hooks/useLoadCheckout.ts +39 -0
  114. package/src/components/checkout/hooks/usePaymentStepModel.ts +49 -0
  115. package/src/components/checkout/hooks/usePlanStepModel.ts +169 -0
  116. package/src/components/checkout/hooks/usePreviewSubscription.ts +82 -0
  117. package/src/components/checkout/hooks/useProgressBarModel.ts +89 -0
  118. package/src/components/checkout/hooks/useSubscriptionModel.ts +16 -0
  119. package/src/components/checkout/hooks/useSubscriptionState.ts +26 -0
  120. package/src/components/checkout/index.ts +3 -0
  121. package/src/components/checkout/planHeader/PlanHeader.style.tsx +23 -0
  122. package/src/components/checkout/planHeader/PlanHeader.tsx +59 -0
  123. package/src/components/checkout/planHeader/index.ts +1 -0
  124. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +34 -0
  125. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +53 -0
  126. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +85 -0
  127. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +39 -0
  128. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +37 -0
  129. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +27 -0
  130. package/src/components/checkout/promotionCode/index.ts +1 -0
  131. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +24 -0
  132. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +125 -0
  133. package/src/components/checkout/steps/addons/addon.utils.ts +68 -0
  134. package/src/components/checkout/steps/addons/index.ts +1 -0
  135. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +26 -0
  136. package/src/components/checkout/steps/payment/PaymentMethods.tsx +86 -0
  137. package/src/components/checkout/steps/payment/PaymentStep.tsx +50 -0
  138. package/src/components/checkout/steps/payment/index.ts +1 -0
  139. package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +45 -0
  140. package/src/components/checkout/steps/payment/stripe/index.ts +3 -0
  141. package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +109 -0
  142. package/src/components/checkout/steps/payment/stripe/useStripeIntegration.ts +27 -0
  143. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +104 -0
  144. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +46 -0
  145. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +63 -0
  146. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +138 -0
  147. package/src/components/checkout/steps/plan/CheckoutPlanStep.style.tsx +6 -0
  148. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +20 -0
  149. package/src/components/checkout/steps/plan/index.ts +1 -0
  150. package/src/components/checkout/steps/surprise/SurpriseStep.tsx +27 -0
  151. package/src/components/checkout/summary/CheckoutSuccess.tsx +32 -0
  152. package/src/components/checkout/summary/CheckoutSummary.tsx +288 -0
  153. package/src/components/checkout/summary/CheckoutSummarySkeleton.tsx +40 -0
  154. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +120 -0
  155. package/src/components/checkout/summary/components/LineItems.tsx +177 -0
  156. package/src/components/checkout/summary/components/WithSkeleton.tsx +16 -0
  157. package/src/components/checkout/summary/index.ts +2 -0
  158. package/src/components/checkout/textOverrides.ts +62 -0
  159. package/src/components/checkout/theme.ts +43 -0
  160. package/src/components/common/Icon.tsx +17 -22
  161. package/src/components/common/PoweredByStigg.tsx +1 -1
  162. package/src/components/{paywall/TiersLayout.tsx → common/TiersSelectContainer.tsx} +8 -7
  163. package/src/components/common/Typography.tsx +11 -1
  164. package/src/components/common/customIcons.ts +19 -28
  165. package/src/components/common/mapExternalTheme.ts +29 -9
  166. package/src/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.tsx +6 -12
  167. package/src/components/hooks/useChargeSort.ts +17 -0
  168. package/src/components/paywall/Paywall.tsx +1 -1
  169. package/src/components/paywall/PlanOffering.tsx +1 -1
  170. package/src/components/paywall/PlanOfferingButton.tsx +1 -1
  171. package/src/components/paywall/PlanPrice.tsx +5 -13
  172. package/src/components/paywall/paywallTextOverrides.ts +0 -1
  173. package/src/components/paywall/utils/calculateUnitQuantityText.ts +9 -4
  174. package/src/components/utils/calculateDiscountRate.ts +1 -1
  175. package/src/components/utils/currencyUtils.ts +1 -1
  176. package/src/components/utils/getPaidPriceText.ts +2 -2
  177. package/src/components/{paywall/planPriceTier.ts → utils/priceTierUtils.ts} +25 -3
  178. package/src/components/utils/priceUtils.ts +10 -0
  179. package/src/index.ts +1 -0
  180. package/src/stories/Checkout.stories.tsx +61 -0
  181. package/src/stories/CustomerPortal.stories.tsx +1 -1
  182. package/src/theme/Theme.tsx +6 -6
  183. package/src/theme/getResolvedTheme.ts +7 -1
  184. package/src/theme/types.ts +4 -0
@@ -0,0 +1,288 @@
1
+ import React, { useState } from 'react';
2
+ import partition from 'lodash/partition';
3
+ import styled from '@emotion/styled/macro';
4
+ import { Box, CircularProgress, Divider, Grid, Paper } from '@mui/material';
5
+ import { BillingModel, SubscriptionPreview } from '@stigg/js-client-sdk';
6
+ import { PoweredByStigg } from '../../common/PoweredByStigg';
7
+ import { Typography } from '../../common/Typography';
8
+ import { useChargesSort } from '../../hooks/useChargeSort';
9
+ import { currencyPriceFormatter } from '../../utils/currencyUtils';
10
+ import { Button } from '../components';
11
+ import {
12
+ useCheckoutModel,
13
+ useProgressBarModel,
14
+ usePreviewSubscription,
15
+ useSubscriptionModel,
16
+ usePaymentStepModel,
17
+ } from '../hooks';
18
+ import { PromotionCodeSection } from '../promotionCode';
19
+ import { useSubmit } from '../steps/payment/stripe';
20
+ import { CheckoutContainerProps } from '../CheckoutContainer';
21
+ import { CheckoutCaptions } from './components/CheckoutCaptions';
22
+ import {
23
+ AppliedCreditsLineItem,
24
+ BilledPriceLineItem,
25
+ DiscountLineItem,
26
+ LineItemContainer,
27
+ LineItemRow,
28
+ TaxLineItem,
29
+ } from './components/LineItems';
30
+ import { WithSkeleton } from './components/WithSkeleton';
31
+ import { Icon } from '../../common/Icon';
32
+ import { CheckoutLocalization } from '../textOverrides';
33
+ import { CheckoutSuccess } from './CheckoutSuccess';
34
+
35
+ export const SummaryCard = styled(Paper)`
36
+ border-radius: 10px;
37
+ background: ${({ theme }) => theme.stigg.palette.backgroundHighlight};
38
+ padding: 16px;
39
+ `;
40
+
41
+ SummaryCard.defaultProps = {
42
+ elevation: 0,
43
+ };
44
+
45
+ const PlanName = styled(Typography)`
46
+ margin-bottom: 16px;
47
+ `;
48
+
49
+ const StyledDivider = styled(Divider)`
50
+ margin: 16px 0;
51
+ `;
52
+
53
+ const SubtotalText = styled(Typography)`
54
+ margin-top: 24px;
55
+ `;
56
+
57
+ const TotalDueText = styled(Typography)`
58
+ margin-bottom: 8px;
59
+ `;
60
+
61
+ function resolveCheckoutButtonText({
62
+ isLastStep,
63
+ subscriptionPreview,
64
+ checkoutLocalization,
65
+ isFirstSubscription,
66
+ }: {
67
+ isLastStep?: boolean;
68
+ isFirstSubscription?: boolean;
69
+ subscriptionPreview?: SubscriptionPreview | null;
70
+ checkoutLocalization: CheckoutLocalization;
71
+ }) {
72
+ if (!isLastStep) {
73
+ return checkoutLocalization.checkoutButton.nextText;
74
+ }
75
+
76
+ if (subscriptionPreview?.isPlanDowngrade) {
77
+ return checkoutLocalization.checkoutButton.downgradeText;
78
+ }
79
+
80
+ if (!isFirstSubscription) {
81
+ return checkoutLocalization.checkoutButton.upgradeText;
82
+ }
83
+
84
+ return checkoutLocalization.checkoutButton.purchaseText;
85
+ }
86
+
87
+ export const CheckoutSummary = ({ onCheckout, onCheckoutCompleted }: CheckoutContainerProps) => {
88
+ const [isCheckoutCompletedSuccessfully, setIsCheckoutCompletedSuccessfully] = useState(false);
89
+ const { setErrorMessage } = usePaymentStepModel();
90
+ const progressBar = useProgressBarModel();
91
+ const subscription = useSubscriptionModel();
92
+ const { checkoutState, checkoutLocalization } = useCheckoutModel();
93
+ const { plan, activeSubscription } = checkoutState || {};
94
+ const planPrices = useChargesSort(
95
+ plan?.pricePoints?.filter((price) => price.billingPeriod === subscription.billingPeriod) || [],
96
+ );
97
+ const [baseCharges, usageCharges] = partition(planPrices, (price) => price.pricingModel === BillingModel.FlatFee);
98
+ const [baseCharge] = baseCharges || [];
99
+ const isFirstSubscription = !activeSubscription?.id;
100
+ const isLastStep = progressBar.isCheckoutComplete && progressBar.isLastStep;
101
+
102
+ const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription();
103
+
104
+ const { handleSubmit, isLoading } = useSubmit({
105
+ onCheckout,
106
+ onCheckoutCompleted,
107
+ onSuccess: () => {
108
+ setIsCheckoutCompletedSuccessfully(true);
109
+ progressBar.markStepAsCompleted(progressBar.progressBarState.activeStep);
110
+ },
111
+ });
112
+
113
+ const handleCheckout = async (e: any) => {
114
+ if (isCheckoutCompletedSuccessfully) {
115
+ return;
116
+ }
117
+
118
+ const { errorMessage } = (await handleSubmit(e)) || {};
119
+ if (errorMessage) {
120
+ setErrorMessage(errorMessage);
121
+ setIsCheckoutCompletedSuccessfully(false);
122
+ return;
123
+ }
124
+
125
+ setErrorMessage(undefined);
126
+ setIsCheckoutCompletedSuccessfully(true);
127
+ };
128
+
129
+ const onCheckoutClick = async (e: any): Promise<void> => {
130
+ if (isLoading) {
131
+ return;
132
+ }
133
+
134
+ if (isLastStep) {
135
+ await handleCheckout(e);
136
+ } else {
137
+ progressBar.goNext();
138
+ }
139
+ };
140
+
141
+ return (
142
+ <Box flex={1}>
143
+ <SummaryCard>
144
+ <PlanName variant="h6">{plan?.displayName}</PlanName>
145
+
146
+ {baseCharge && (
147
+ <LineItemContainer>
148
+ <LineItemRow>
149
+ <Typography variant="h6" color="primary">
150
+ {typeof checkoutLocalization.baseChargeText === 'function'
151
+ ? checkoutLocalization.baseChargeText({ billingPeriod: subscription.billingPeriod })
152
+ : checkoutLocalization.baseChargeText}
153
+ </Typography>
154
+ <Typography variant="body1" color="secondary">
155
+ {currencyPriceFormatter({ amount: baseCharge.amount!, currency: baseCharge.currency })}
156
+ </Typography>
157
+ </LineItemRow>
158
+ </LineItemContainer>
159
+ )}
160
+
161
+ {usageCharges.map((price) => {
162
+ const priceBillableFeature = subscription.billableFeatures?.find(
163
+ (billableFeature) => billableFeature.featureId === price.feature?.featureId,
164
+ );
165
+
166
+ return (
167
+ <BilledPriceLineItem
168
+ key={price.feature?.featureId}
169
+ label={price.feature?.displayName || ''}
170
+ quantity={priceBillableFeature?.quantity || 1}
171
+ price={price}
172
+ />
173
+ );
174
+ })}
175
+
176
+ {!!subscription.addons?.length && (
177
+ <>
178
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={1}>
179
+ <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
180
+ {checkoutLocalization.addonsSectionTitle}
181
+ </Typography>
182
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1 }} />
183
+ </Grid>
184
+ {subscription.addons.map((addon) => {
185
+ const addonPrice = addon.addon.pricePoints?.find(
186
+ (price) => price.billingPeriod === subscription.billingPeriod,
187
+ );
188
+
189
+ if (!addonPrice) return null;
190
+
191
+ return (
192
+ <BilledPriceLineItem
193
+ key={addon?.addon?.id}
194
+ label={addon.addon.displayName}
195
+ quantity={addon.quantity}
196
+ price={addonPrice}
197
+ />
198
+ );
199
+ })}
200
+ </>
201
+ )}
202
+
203
+ <LineItemRow>
204
+ <SubtotalText variant="h6">{checkoutLocalization.subTotalText}</SubtotalText>
205
+ <SubtotalText variant="h6">
206
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
207
+ {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.subTotal })}
208
+ </WithSkeleton>
209
+ </SubtotalText>
210
+ </LineItemRow>
211
+
212
+ <StyledDivider className="stigg-checkout-summary-divider" />
213
+
214
+ {isFirstSubscription && (
215
+ <>
216
+ <PromotionCodeSection checkoutLocalization={checkoutLocalization} />
217
+
218
+ <StyledDivider className="stigg-checkout-summary-divider" />
219
+ </>
220
+ )}
221
+
222
+ <AppliedCreditsLineItem
223
+ subscriptionPreview={subscriptionPreview}
224
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
225
+ checkoutLocalization={checkoutLocalization}
226
+ />
227
+
228
+ <DiscountLineItem
229
+ subscriptionPreview={subscriptionPreview}
230
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
231
+ />
232
+
233
+ <TaxLineItem
234
+ subscriptionPreview={subscriptionPreview}
235
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
236
+ checkoutLocalization={checkoutLocalization}
237
+ />
238
+
239
+ <LineItemContainer>
240
+ <LineItemRow>
241
+ <TotalDueText variant="h6">{checkoutLocalization.totalText}</TotalDueText>
242
+ <TotalDueText variant="h6">
243
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
244
+ {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total })}
245
+ </WithSkeleton>
246
+ </TotalDueText>
247
+ </LineItemRow>
248
+ </LineItemContainer>
249
+
250
+ <CheckoutCaptions
251
+ plan={plan}
252
+ activeSubscription={activeSubscription}
253
+ subscriptionPreview={subscriptionPreview}
254
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
255
+ checkoutLocalization={checkoutLocalization}
256
+ />
257
+
258
+ <Button
259
+ disableRipple={isLoading}
260
+ $isLoading={isLoading}
261
+ $success={isCheckoutCompletedSuccessfully}
262
+ $error={isLastStep && subscriptionPreview?.isPlanDowngrade}
263
+ className="stigg-checkout-summary-cta-button"
264
+ sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px' }}
265
+ variant="contained"
266
+ size="medium"
267
+ onClick={(e: any) => {
268
+ void onCheckoutClick(e);
269
+ }}
270
+ fullWidth>
271
+ <Typography className="stigg-checkout-summary-cta-button-text" color="white" style={{ display: 'flex' }}>
272
+ {isCheckoutCompletedSuccessfully && <Icon icon="Check" style={{ display: 'contents' }} />}
273
+ {isLoading && <CircularProgress size={20} sx={{ color: 'white' }} />}
274
+ {!isLoading &&
275
+ !isCheckoutCompletedSuccessfully &&
276
+ resolveCheckoutButtonText({ isLastStep, subscriptionPreview, checkoutLocalization })}
277
+ </Typography>
278
+ </Button>
279
+ </SummaryCard>
280
+ <PoweredByStigg
281
+ source="checkout"
282
+ showWatermark
283
+ style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}
284
+ />
285
+ {isCheckoutCompletedSuccessfully && <CheckoutSuccess />}
286
+ </Box>
287
+ );
288
+ };
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { Box } from '@mui/material';
3
+ import { PoweredByStigg } from '../../common/PoweredByStigg';
4
+ import { SummaryCard } from './CheckoutSummary';
5
+ import { FlexedSkeleton, Skeleton, SkeletonsContainer } from '../components/Skeletons.style';
6
+
7
+ export const CheckoutSummarySkeleton = () => {
8
+ return (
9
+ <Box flex={1}>
10
+ <SummaryCard>
11
+ <SkeletonsContainer $flexDirection="column" $gap={24}>
12
+ <Skeleton width={120} height={20} />
13
+
14
+ <Skeleton width={120} height={20} />
15
+
16
+ <Skeleton width={120} height={20} />
17
+
18
+ <Skeleton width={120} height={20} />
19
+ </SkeletonsContainer>
20
+
21
+ <SkeletonsContainer $flexDirection="column" $gap={24} marginTop={5}>
22
+ <Skeleton height={20} />
23
+
24
+ <Skeleton height={20} />
25
+
26
+ <Skeleton height={20} />
27
+ </SkeletonsContainer>
28
+
29
+ <FlexedSkeleton flex={1} marginTop={5}>
30
+ <Skeleton height={34} />
31
+ </FlexedSkeleton>
32
+ </SummaryCard>
33
+ <PoweredByStigg
34
+ source="checkout"
35
+ showWatermark
36
+ style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}
37
+ />
38
+ </Box>
39
+ );
40
+ };
@@ -0,0 +1,120 @@
1
+ import React from 'react';
2
+ import moment from 'moment';
3
+ import { Plan, Subscription, SubscriptionPreview } from '@stigg/js-client-sdk';
4
+ import { Typography } from '../../../common/Typography';
5
+ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
6
+ import { CheckoutLocalization } from '../../textOverrides';
7
+ import { WithSkeleton } from './WithSkeleton';
8
+
9
+ export type CheckoutCaptionProps = {
10
+ subscriptionPreview?: SubscriptionPreview | null;
11
+ isFetchingSubscriptionPreview: boolean;
12
+ activeSubscription?: Subscription | null;
13
+ plan?: Plan;
14
+ checkoutLocalization: CheckoutLocalization;
15
+ };
16
+
17
+ // TODO: move to localization
18
+
19
+ const RemainingCreditsCaption = ({ subscriptionPreview, isFetchingSubscriptionPreview }: CheckoutCaptionProps) => {
20
+ if (!subscriptionPreview?.proration?.netAmount?.amount || subscriptionPreview?.proration?.netAmount?.amount >= 0) {
21
+ return null;
22
+ }
23
+
24
+ const credits = currencyPriceFormatter(subscriptionPreview?.proration?.netAmount);
25
+
26
+ return (
27
+ <Typography variant="caption" style={{ marginTop: 14 }}>
28
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview} width="100%">
29
+ {`Your remaining credits, which are ${credits}, will be kept for you for future use.`}
30
+ </WithSkeleton>
31
+ </Typography>
32
+ );
33
+ };
34
+
35
+ const ScheduledUpdatesCaption = ({
36
+ subscriptionPreview,
37
+ activeSubscription,
38
+ isFetchingSubscriptionPreview,
39
+ checkoutLocalization,
40
+ }: CheckoutCaptionProps) => {
41
+ if (!subscriptionPreview?.hasScheduledUpdates || !activeSubscription?.currentBillingPeriodEnd) {
42
+ return null;
43
+ }
44
+
45
+ const { currentBillingPeriodEnd } = activeSubscription;
46
+ const changesWillApplyText =
47
+ typeof checkoutLocalization.changesWillApplyAtEndOfBillingPeriod === 'function'
48
+ ? checkoutLocalization.changesWillApplyAtEndOfBillingPeriod({ billingPeriodEnd: currentBillingPeriodEnd })
49
+ : checkoutLocalization.changesWillApplyAtEndOfBillingPeriod;
50
+
51
+ return (
52
+ <Typography variant="caption" style={{ marginTop: 14 }}>
53
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview} width="100%">
54
+ {changesWillApplyText}
55
+ </WithSkeleton>
56
+ </Typography>
57
+ );
58
+ };
59
+
60
+ const ChargeDueTodayCaption = ({ subscriptionPreview, plan, isFetchingSubscriptionPreview }: CheckoutCaptionProps) => {
61
+ if (!subscriptionPreview?.total) {
62
+ return null;
63
+ }
64
+
65
+ const total = currencyPriceFormatter(subscriptionPreview?.total);
66
+ const usedCredits = !!subscriptionPreview.credits?.used;
67
+
68
+ return (
69
+ <Typography variant="caption" style={{ marginTop: 14 }}>
70
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview} width="100%">
71
+ {`Today we’ll charge you ${total} (incl. taxes) for your new ${plan?.displayName} plan${
72
+ usedCredits ? ' minus credits left from your existing plan.' : ''
73
+ } `}
74
+ </WithSkeleton>
75
+ </Typography>
76
+ );
77
+ };
78
+
79
+ const NextBillingCaption = ({
80
+ subscriptionPreview,
81
+ activeSubscription,
82
+ plan,
83
+ isFetchingSubscriptionPreview,
84
+ }: CheckoutCaptionProps) => {
85
+ if (!subscriptionPreview?.subscription?.total) {
86
+ return null;
87
+ }
88
+
89
+ const isUpdatingSubscription = activeSubscription?.plan?.id && plan?.id && activeSubscription.plan.id === plan.id;
90
+ const isScheduledPlanDowngrade = subscriptionPreview.isPlanDowngrade && subscriptionPreview.hasScheduledUpdates;
91
+ const currentBillingPeriodEnd = subscriptionPreview.hasScheduledUpdates
92
+ ? activeSubscription?.currentBillingPeriodEnd
93
+ : subscriptionPreview?.billingPeriodRange.end;
94
+ const billingDate = moment(currentBillingPeriodEnd).format('MMM D, YYYY');
95
+ const total = currencyPriceFormatter(subscriptionPreview?.subscription.total);
96
+
97
+ return (
98
+ <Typography variant="caption" style={{ marginTop: 14 }}>
99
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview} width="100%">
100
+ {`Your${isUpdatingSubscription ? '' : ' new'} ${plan?.displayName} plan will ${
101
+ isScheduledPlanDowngrade ? 'start' : 'renew'
102
+ } on ${billingDate} for ${total} (incl. taxes) unless you cancel it.`}
103
+ </WithSkeleton>
104
+ </Typography>
105
+ );
106
+ };
107
+
108
+ export function CheckoutCaptions(props: CheckoutCaptionProps) {
109
+ return (
110
+ <>
111
+ <RemainingCreditsCaption {...props} />
112
+
113
+ <ScheduledUpdatesCaption {...props} />
114
+
115
+ <ChargeDueTodayCaption {...props} />
116
+
117
+ <NextBillingCaption {...props} />
118
+ </>
119
+ );
120
+ }
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+ import { Grid } from '@mui/material';
4
+ import { BillingPeriod, DiscountType, Price, SubscriptionPreview } from '@stigg/js-client-sdk';
5
+ import { Typography } from '../../../common/Typography';
6
+ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
7
+ import { getTierByQuantity } from '../../../utils/priceTierUtils';
8
+ import { WithSkeleton } from './WithSkeleton';
9
+ import { Skeleton } from '../../components/Skeletons.style';
10
+ import { CheckoutLocalization } from '../../textOverrides';
11
+
12
+ export const LineItemContainer = styled.div`
13
+ & + & {
14
+ margin-top: 8px;
15
+ }
16
+ `;
17
+
18
+ export const LineItemRow = styled.div`
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ `;
23
+
24
+ export const getPriceString = ({
25
+ amountPerMonth,
26
+ price,
27
+ quantity,
28
+ }: {
29
+ amountPerMonth: number;
30
+ price: Price;
31
+ quantity: number;
32
+ }) => {
33
+ const { billingPeriod } = price;
34
+ let billingPeriodString = null;
35
+
36
+ if (billingPeriod === BillingPeriod.Annually) {
37
+ amountPerMonth /= 12;
38
+ billingPeriodString = '12 months';
39
+ }
40
+
41
+ const addonPriceFormat = currencyPriceFormatter({ amount: amountPerMonth, currency: price.currency });
42
+
43
+ return `${quantity > 1 ? `${quantity} x ${addonPriceFormat} each` : addonPriceFormat}${
44
+ billingPeriodString ? ` x ${billingPeriodString}` : ''
45
+ }`;
46
+ };
47
+
48
+ export const BilledPriceLineItem = ({ label, quantity, price }: { label: string; quantity: number; price: Price }) => {
49
+ const { billingPeriod } = price;
50
+
51
+ let amountPerMonth;
52
+ if (price.isTieredPrice) {
53
+ const tier = getTierByQuantity(price.tiers!, quantity);
54
+ amountPerMonth = tier!.unitPrice.amount!;
55
+ } else {
56
+ amountPerMonth = price.amount!;
57
+ }
58
+
59
+ return (
60
+ <LineItemContainer>
61
+ <LineItemRow style={{ alignItems: 'flex-end' }}>
62
+ <Grid item>
63
+ <Typography variant="h6" color="primary">
64
+ {label}
65
+ </Typography>
66
+ {(quantity > 1 || billingPeriod === BillingPeriod.Annually) && (
67
+ <Typography variant="body1" color="secondary">
68
+ {getPriceString({ amountPerMonth, price, quantity })}
69
+ </Typography>
70
+ )}
71
+ </Grid>
72
+ <Grid item>
73
+ <Typography variant="body1" color="secondary" style={{ wordBreak: 'break-word' }}>
74
+ {currencyPriceFormatter({ amount: quantity * amountPerMonth, currency: price.currency })}
75
+ </Typography>
76
+ </Grid>
77
+ </LineItemRow>
78
+ </LineItemContainer>
79
+ );
80
+ };
81
+
82
+ export const DiscountLineItem = ({
83
+ subscriptionPreview,
84
+ isFetchingSubscriptionPreview,
85
+ }: {
86
+ subscriptionPreview: SubscriptionPreview | null;
87
+ isFetchingSubscriptionPreview: boolean;
88
+ }) => {
89
+ const { subTotal, discount } = subscriptionPreview || {};
90
+ if (!subTotal || !discount) {
91
+ return null;
92
+ }
93
+
94
+ let discountAmount: number;
95
+ if (discount.type === DiscountType.Percentage) {
96
+ discountAmount = -1 * Math.abs((discount.value / 100) * subTotal.amount);
97
+ } else {
98
+ discountAmount = -1 * Math.abs(discount.value);
99
+ }
100
+
101
+ return (
102
+ <LineItemContainer>
103
+ <LineItemRow>
104
+ <Typography variant="body1" color="secondary">
105
+ {`Discount${discount.type === DiscountType.Percentage ? ` (${discount.value}% off)` : ''}`}
106
+ </Typography>
107
+ <Typography variant="body1" color="secondary">
108
+ {isFetchingSubscriptionPreview ? (
109
+ <Skeleton width={50} height={16} />
110
+ ) : (
111
+ currencyPriceFormatter({ amount: discountAmount, currency: subTotal.currency })
112
+ )}
113
+ </Typography>
114
+ </LineItemRow>
115
+ </LineItemContainer>
116
+ );
117
+ };
118
+
119
+ export const AppliedCreditsLineItem = ({
120
+ subscriptionPreview,
121
+ isFetchingSubscriptionPreview,
122
+ checkoutLocalization,
123
+ }: {
124
+ subscriptionPreview: SubscriptionPreview | null;
125
+ isFetchingSubscriptionPreview: boolean;
126
+ checkoutLocalization: CheckoutLocalization;
127
+ }) => {
128
+ const { credits } = subscriptionPreview || {};
129
+ if (!credits || credits.used.amount <= 0) {
130
+ return null;
131
+ }
132
+
133
+ return (
134
+ <LineItemContainer>
135
+ <LineItemRow>
136
+ <Typography variant="body1" color="secondary">
137
+ {checkoutLocalization.appliedCreditsTitle}
138
+ </Typography>
139
+ <Typography variant="body1" color="secondary">
140
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
141
+ {currencyPriceFormatter({ amount: -1 * credits.used.amount, currency: credits.used.currency })}
142
+ </WithSkeleton>
143
+ </Typography>
144
+ </LineItemRow>
145
+ </LineItemContainer>
146
+ );
147
+ };
148
+
149
+ export const TaxLineItem = ({
150
+ subscriptionPreview,
151
+ isFetchingSubscriptionPreview,
152
+ checkoutLocalization,
153
+ }: {
154
+ subscriptionPreview: SubscriptionPreview | null;
155
+ isFetchingSubscriptionPreview: boolean;
156
+ checkoutLocalization: CheckoutLocalization;
157
+ }) => {
158
+ const { tax, taxDetails } = subscriptionPreview || {};
159
+ if (!taxDetails || !tax) {
160
+ return null;
161
+ }
162
+
163
+ return (
164
+ <LineItemContainer>
165
+ <LineItemRow>
166
+ <Typography variant="body1" color="secondary">
167
+ {checkoutLocalization.taxTitle({ taxDetails })}
168
+ </Typography>
169
+ <Typography variant="body1" color="secondary">
170
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
171
+ {currencyPriceFormatter({ amount: tax?.amount, currency: tax?.currency })}
172
+ </WithSkeleton>
173
+ </Typography>
174
+ </LineItemRow>
175
+ </LineItemContainer>
176
+ );
177
+ };
@@ -0,0 +1,16 @@
1
+ import React, { FC, PropsWithChildren } from 'react';
2
+ import { Skeleton } from '../../components/Skeletons.style';
3
+
4
+ export const WithSkeleton: FC<
5
+ PropsWithChildren<{
6
+ isLoading: boolean;
7
+ width?: number | string;
8
+ height?: number | string;
9
+ }>
10
+ > = ({ isLoading, width = 50, height = 16, children }) => {
11
+ if (isLoading) {
12
+ return <Skeleton width={width} height={height} />;
13
+ }
14
+
15
+ return <>{children}</>;
16
+ };
@@ -0,0 +1,2 @@
1
+ export * from './CheckoutSummary';
2
+ export * from './CheckoutSummarySkeleton';