@stigg/react-sdk 4.3.1 → 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 (186) 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 -4
  71. package/dist/components/utils/calculateDiscountRate.d.ts +1 -0
  72. package/dist/components/utils/currencyUtils.d.ts +1 -1
  73. package/dist/components/utils/getPaidPriceText.d.ts +1 -3
  74. package/dist/components/{paywall/planPriceTier.d.ts → utils/priceTierUtils.d.ts} +3 -1
  75. package/dist/components/utils/priceUtils.d.ts +2 -0
  76. package/dist/index.d.ts +1 -0
  77. package/dist/react-sdk.cjs.development.js +6934 -238
  78. package/dist/react-sdk.cjs.development.js.map +1 -1
  79. package/dist/react-sdk.cjs.production.min.js +1 -1
  80. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  81. package/dist/react-sdk.esm.js +7093 -242
  82. package/dist/react-sdk.esm.js.map +1 -1
  83. package/dist/stories/Checkout.stories.d.ts +3 -0
  84. package/dist/stories/CustomerPortal.stories.d.ts +1 -1
  85. package/dist/theme/getResolvedTheme.d.ts +4 -0
  86. package/dist/theme/types.d.ts +4 -0
  87. package/package.json +11 -5
  88. package/src/assets/arrow-forward.svg +3 -0
  89. package/src/assets/arrow-right.svg +6 -0
  90. package/src/assets/check.svg +5 -0
  91. package/src/assets/close.svg +3 -0
  92. package/src/assets/lottie/checkout-success.json +1 -0
  93. package/src/assets/nyancat.svg +634 -0
  94. package/src/assets/outlined-checked-circle-disabled.svg +6 -0
  95. package/src/assets/outlined-checked-circle.svg +6 -0
  96. package/src/assets/outlined-circle.svg +3 -0
  97. package/src/assets/payment-method.svg +11 -0
  98. package/src/assets/plus-icon.svg +6 -0
  99. package/src/assets/trash.svg +8 -0
  100. package/src/components/checkout/Checkout.tsx +30 -0
  101. package/src/components/checkout/CheckoutContainer.style.ts +35 -0
  102. package/src/components/checkout/CheckoutContainer.tsx +99 -0
  103. package/src/components/checkout/CheckoutProvider.tsx +149 -0
  104. package/src/components/checkout/components/Button.tsx +51 -0
  105. package/src/components/checkout/components/ContentLoadingSkeleton.tsx +41 -0
  106. package/src/components/checkout/components/InputField.tsx +22 -0
  107. package/src/components/checkout/components/Skeletons.style.ts +27 -0
  108. package/src/components/checkout/components/index.ts +3 -0
  109. package/src/components/checkout/formatting.ts +12 -0
  110. package/src/components/checkout/hooks/index.ts +8 -0
  111. package/src/components/checkout/hooks/useAddonsStepModel.ts +96 -0
  112. package/src/components/checkout/hooks/useCheckoutModel.ts +32 -0
  113. package/src/components/checkout/hooks/useCouponModel.ts +28 -0
  114. package/src/components/checkout/hooks/useLoadCheckout.ts +39 -0
  115. package/src/components/checkout/hooks/usePaymentStepModel.ts +49 -0
  116. package/src/components/checkout/hooks/usePlanStepModel.ts +169 -0
  117. package/src/components/checkout/hooks/usePreviewSubscription.ts +82 -0
  118. package/src/components/checkout/hooks/useProgressBarModel.ts +89 -0
  119. package/src/components/checkout/hooks/useSubscriptionModel.ts +16 -0
  120. package/src/components/checkout/hooks/useSubscriptionState.ts +26 -0
  121. package/src/components/checkout/index.ts +3 -0
  122. package/src/components/checkout/planHeader/PlanHeader.style.tsx +23 -0
  123. package/src/components/checkout/planHeader/PlanHeader.tsx +59 -0
  124. package/src/components/checkout/planHeader/index.ts +1 -0
  125. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +34 -0
  126. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +53 -0
  127. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +85 -0
  128. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +39 -0
  129. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +37 -0
  130. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +27 -0
  131. package/src/components/checkout/promotionCode/index.ts +1 -0
  132. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +24 -0
  133. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +125 -0
  134. package/src/components/checkout/steps/addons/addon.utils.ts +68 -0
  135. package/src/components/checkout/steps/addons/index.ts +1 -0
  136. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +26 -0
  137. package/src/components/checkout/steps/payment/PaymentMethods.tsx +86 -0
  138. package/src/components/checkout/steps/payment/PaymentStep.tsx +50 -0
  139. package/src/components/checkout/steps/payment/index.ts +1 -0
  140. package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +45 -0
  141. package/src/components/checkout/steps/payment/stripe/index.ts +3 -0
  142. package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +109 -0
  143. package/src/components/checkout/steps/payment/stripe/useStripeIntegration.ts +27 -0
  144. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +104 -0
  145. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +46 -0
  146. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +63 -0
  147. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +138 -0
  148. package/src/components/checkout/steps/plan/CheckoutPlanStep.style.tsx +6 -0
  149. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +20 -0
  150. package/src/components/checkout/steps/plan/index.ts +1 -0
  151. package/src/components/checkout/steps/surprise/SurpriseStep.tsx +27 -0
  152. package/src/components/checkout/summary/CheckoutSuccess.tsx +32 -0
  153. package/src/components/checkout/summary/CheckoutSummary.tsx +288 -0
  154. package/src/components/checkout/summary/CheckoutSummarySkeleton.tsx +40 -0
  155. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +120 -0
  156. package/src/components/checkout/summary/components/LineItems.tsx +177 -0
  157. package/src/components/checkout/summary/components/WithSkeleton.tsx +16 -0
  158. package/src/components/checkout/summary/index.ts +2 -0
  159. package/src/components/checkout/textOverrides.ts +62 -0
  160. package/src/components/checkout/theme.ts +43 -0
  161. package/src/components/common/Icon.tsx +17 -22
  162. package/src/components/common/PoweredByStigg.tsx +1 -1
  163. package/src/components/{paywall/TiersLayout.tsx → common/TiersSelectContainer.tsx} +8 -7
  164. package/src/components/common/Typography.tsx +11 -1
  165. package/src/components/common/customIcons.ts +19 -28
  166. package/src/components/common/mapExternalTheme.ts +29 -9
  167. package/src/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.tsx +6 -12
  168. package/src/components/hooks/useChargeSort.ts +17 -0
  169. package/src/components/paywall/Paywall.tsx +1 -1
  170. package/src/components/paywall/PlanOffering.tsx +1 -1
  171. package/src/components/paywall/PlanOfferingButton.tsx +1 -1
  172. package/src/components/paywall/PlanPrice.tsx +5 -13
  173. package/src/components/paywall/paywallTextOverrides.ts +0 -3
  174. package/src/components/paywall/utils/calculateUnitQuantityText.ts +9 -4
  175. package/src/components/utils/calculateDiscountRate.ts +1 -1
  176. package/src/components/utils/currencyUtils.ts +1 -1
  177. package/src/components/utils/getPaidPriceText.ts +4 -10
  178. package/src/components/utils/getPlanPrice.ts +1 -1
  179. package/src/components/{paywall/planPriceTier.ts → utils/priceTierUtils.ts} +25 -3
  180. package/src/components/utils/priceUtils.ts +10 -0
  181. package/src/index.ts +1 -0
  182. package/src/stories/Checkout.stories.tsx +61 -0
  183. package/src/stories/CustomerPortal.stories.tsx +1 -1
  184. package/src/theme/Theme.tsx +6 -6
  185. package/src/theme/getResolvedTheme.ts +7 -1
  186. package/src/theme/types.ts +4 -0
@@ -0,0 +1,85 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import styled from '@emotion/styled/macro';
4
+ import { CircularProgress, Grid } from '@mui/material';
5
+
6
+ import { Icon } from '../../common/Icon';
7
+ import { Typography } from '../../common/Typography';
8
+ import { Button, InputField } from '../components';
9
+ import { usePreviewSubscriptionAction } from '../hooks';
10
+ import { usePromotionCodeModel } from '../hooks/useCouponModel';
11
+ import { AddPromotionCodeButton } from './AddPromotionCodeButton';
12
+ import { CheckoutLocalization } from '../textOverrides';
13
+
14
+ const CouponCodeAddButton = styled(Button)`
15
+ padding: 4px 10px;
16
+ min-width: unset;
17
+ border-radius: ${({ theme }) => theme.stigg.border.radius};
18
+ display: flex;
19
+ align-items: center;
20
+ `;
21
+
22
+ export const AddPromotionCode = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
23
+ const { setPromotionCode: persistPromotionCode } = usePromotionCodeModel();
24
+ const [showInput, setShowInput] = useState(false);
25
+ const [promotionCode, setPromotionCode] = React.useState('');
26
+ const [isLoading, setIsLoading] = React.useState(false);
27
+ const [errorMessage, setErrorMessage] = React.useState('');
28
+ const { previewSubscriptionAction } = usePreviewSubscriptionAction();
29
+
30
+ const handlePromotionCode = async () => {
31
+ setIsLoading(true);
32
+ setErrorMessage('');
33
+
34
+ const { subscriptionPreview, errorMessage } = await previewSubscriptionAction({ promotionCode });
35
+
36
+ if (!errorMessage && subscriptionPreview?.discount) {
37
+ persistPromotionCode(promotionCode.toUpperCase());
38
+ setShowInput(false);
39
+ } else if (!!errorMessage) {
40
+ setErrorMessage(errorMessage);
41
+ }
42
+ setIsLoading(false);
43
+ };
44
+
45
+ if (!showInput) {
46
+ return <AddPromotionCodeButton onAddClick={() => setShowInput(true)} checkoutLocalization={checkoutLocalization} />;
47
+ }
48
+
49
+ return (
50
+ <Grid>
51
+ <Typography variant="body1" color={errorMessage ? 'error' : 'primary.main'}>
52
+ {checkoutLocalization.couponCodeTitle}
53
+ </Typography>
54
+
55
+ <InputField
56
+ autoFocus
57
+ variant="outlined"
58
+ fullWidth
59
+ error={!!errorMessage}
60
+ value={promotionCode}
61
+ onChange={(e) => {
62
+ setPromotionCode(e.target.value);
63
+ }}
64
+ inputProps={{ maxLength: 20 }}
65
+ InputProps={{
66
+ endAdornment: (
67
+ <CouponCodeAddButton
68
+ variant="contained"
69
+ disableRipple={isLoading}
70
+ $isLoading={isLoading}
71
+ onClick={handlePromotionCode}
72
+ >
73
+ {isLoading ? <CircularProgress size={18} /> : <Icon style={{ display: 'flex' }} icon="ArrowForward" />}
74
+ </CouponCodeAddButton>
75
+ ),
76
+ }}
77
+ />
78
+ {!!errorMessage && (
79
+ <Typography variant="body1" color="error">
80
+ {errorMessage}
81
+ </Typography>
82
+ )}
83
+ </Grid>
84
+ );
85
+ };
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+
4
+ import PlusIcon from '../../../assets/plus-icon.svg';
5
+ import { Typography } from '../../common/Typography';
6
+ import { Button } from '../components';
7
+ import { CheckoutLocalization } from '../textOverrides';
8
+
9
+ const StyledPlusIcon = styled(PlusIcon)`
10
+ path {
11
+ stroke: ${({ theme }) => theme.stigg.palette.primary};
12
+ }
13
+ `;
14
+
15
+ export type AddPromotionCodeButtonProps = {
16
+ onAddClick: () => void;
17
+ checkoutLocalization: CheckoutLocalization;
18
+ };
19
+
20
+ export const AddPromotionCodeButton = ({ onAddClick, checkoutLocalization }: AddPromotionCodeButtonProps) => (
21
+ <Button
22
+ fullWidth
23
+ className="stigg-checkout-summary-add-coupon-code-button"
24
+ sx={{ textTransform: 'none', justifyContent: 'flex-start' }}
25
+ variant="text"
26
+ size="medium"
27
+ onClick={onAddClick}
28
+ >
29
+ <StyledPlusIcon className="stigg-checkout-summary-add-coupon-code-button-icon" />
30
+ <Typography
31
+ className="stigg-checkout-change-plan-button-text"
32
+ color="primary.main"
33
+ style={{ lineHeight: '24px' }}
34
+ variant="body1"
35
+ >
36
+ {checkoutLocalization.addCouponCodeText}
37
+ </Typography>
38
+ </Button>
39
+ );
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+ import { Grid } from '@mui/material';
4
+ import { Icon } from '../../common/Icon';
5
+ import { Typography } from '../../common/Typography';
6
+ import { Button } from '../components';
7
+
8
+ const AppliedCouponContainer = styled(Grid)`
9
+ width: auto;
10
+ display: inline-flex;
11
+ align-items: center;
12
+ flex-direction: row;
13
+ gap: 8px;
14
+ padding: 6px 16px;
15
+ border-radius: ${({ theme }) => theme.stigg.border.radius};
16
+ border: 1px solid ${({ theme }) => theme.stigg.palette.primary};
17
+ `;
18
+
19
+ export type AppliedPromotionCodeProps = {
20
+ promotionCode: string;
21
+ onClearPromotionCode: () => void;
22
+ };
23
+
24
+ export const AppliedPromotionCode = ({ promotionCode, onClearPromotionCode }: AppliedPromotionCodeProps) => (
25
+ <AppliedCouponContainer container>
26
+ <Grid item>
27
+ <Typography variant="body1" color="primary.main">
28
+ {promotionCode}
29
+ </Typography>
30
+ </Grid>
31
+ <Grid item>
32
+ <Button variant="text" sx={{ minWidth: 'unset', padding: 0 }} onClick={onClearPromotionCode}>
33
+ <Icon icon="Close" style={{ display: 'flex' }} />
34
+ </Button>
35
+ </Grid>
36
+ </AppliedCouponContainer>
37
+ );
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+ import { usePromotionCodeModel } from '../hooks/useCouponModel';
4
+ import { AddPromotionCode } from './AddPromotionCode';
5
+ import { AppliedPromotionCode } from './AppliedPromotionCode';
6
+ import { CheckoutLocalization } from '../textOverrides';
7
+
8
+ const PromotionCodeSectionContainer = styled.div`
9
+ margin: 16px 0;
10
+ `;
11
+
12
+ export const PromotionCodeSection = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
13
+ const { promotionCode, setPromotionCode } = usePromotionCodeModel();
14
+
15
+ const onClearPromotionCode = () => {
16
+ setPromotionCode('');
17
+ };
18
+
19
+ let content: React.ReactNode = null;
20
+ if (promotionCode) {
21
+ content = <AppliedPromotionCode promotionCode={promotionCode} onClearPromotionCode={onClearPromotionCode} />;
22
+ } else {
23
+ content = <AddPromotionCode checkoutLocalization={checkoutLocalization} />;
24
+ }
25
+
26
+ return <PromotionCodeSectionContainer>{content}</PromotionCodeSectionContainer>;
27
+ };
@@ -0,0 +1 @@
1
+ export * from './PromotionCodeSection';
@@ -0,0 +1,24 @@
1
+ import styled from '@emotion/styled/macro';
2
+ import { Grid } from '@mui/material';
3
+
4
+ import { Button } from '../../components';
5
+
6
+ export const CheckoutAddonsContainer = styled(Grid)`
7
+ width: 100%;
8
+ margin-top: 32px;
9
+ gap: 8px;
10
+ `;
11
+
12
+ export const AddonListItemContainer = styled(Grid)`
13
+ display: flex;
14
+ justify-content: space-between;
15
+ align-items: center;
16
+ height: 80px;
17
+ `;
18
+
19
+ export const TrashButton = styled(Button)`
20
+ min-width: unset;
21
+ width: 41px;
22
+ height: 41px;
23
+ padding: 0;
24
+ `;
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+
3
+ import { Grid } from '@mui/material';
4
+ import { Addon, BillingPeriod, SubscriptionAddon } from '@stigg/js-client-sdk';
5
+
6
+ import { Icon } from '../../../common/Icon';
7
+ import { Typography } from '../../../common/Typography';
8
+ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
9
+ import { Button, InputField } from '../../components';
10
+ import { useAddonsStepModel } from '../../hooks/useAddonsStepModel';
11
+ import { usePlanStepModel } from '../../hooks/usePlanStepModel';
12
+ import { AddonListItemContainer, CheckoutAddonsContainer, TrashButton } from './CheckoutAddonsStep.style';
13
+ import { CheckoutLocalization } from '../../textOverrides';
14
+ import { useCheckoutModel } from '../../hooks';
15
+
16
+ type UseAddonsStepModel = ReturnType<typeof useAddonsStepModel>;
17
+
18
+ type AddonListItemProps = {
19
+ addon: Addon;
20
+ addonState?: SubscriptionAddon;
21
+ initialAddonState?: SubscriptionAddon;
22
+ billingPeriod: BillingPeriod;
23
+ setAddon: UseAddonsStepModel['setAddon'];
24
+ removeAddon: UseAddonsStepModel['removeAddon'];
25
+ checkoutLocalization: CheckoutLocalization;
26
+ };
27
+
28
+ function AddonListItem({
29
+ addon,
30
+ billingPeriod,
31
+ addonState,
32
+ initialAddonState,
33
+ setAddon,
34
+ removeAddon,
35
+ checkoutLocalization,
36
+ }: AddonListItemProps) {
37
+ const addonPrice = addon.pricePoints.find((pricePoint) => pricePoint.billingPeriod === billingPeriod);
38
+ const isAdded = !!addonState;
39
+ const hasChanges =
40
+ (!!addonState && !!initialAddonState && addonState.quantity !== initialAddonState.quantity) ||
41
+ (!initialAddonState && !!addonState) ||
42
+ (!!initialAddonState && !addonState);
43
+
44
+ const handleQuantityChange = (quantity?: number) => {
45
+ setAddon(addon, quantity || 1);
46
+ };
47
+
48
+ const handleUndo = () => {
49
+ initialAddonState ? setAddon(addon, initialAddonState.quantity) : removeAddon(addon.id);
50
+ };
51
+
52
+ return (
53
+ <AddonListItemContainer container>
54
+ <Grid item>
55
+ <Typography variant="h6" lineHeight="24px">
56
+ {addon.displayName}
57
+ </Typography>
58
+ {!!addonPrice && (
59
+ <Typography variant="body1" lineHeight="20px" color="secondary">
60
+ {`${currencyPriceFormatter({
61
+ amount: addonPrice.amount!,
62
+ currency: addonPrice.currency,
63
+ })}/${billingPeriod === BillingPeriod.Annually ? 'year' : 'month'}`}
64
+ </Typography>
65
+ )}
66
+ </Grid>
67
+ <Grid item>
68
+ {hasChanges && (
69
+ <Button variant="text" size="small" sx={{ padding: '8px', minWidth: 'unset' }} onClick={handleUndo}>
70
+ <Typography color="primary.main" variant="body1">
71
+ Undo
72
+ </Typography>
73
+ </Button>
74
+ )}
75
+ {isAdded && (
76
+ <>
77
+ <InputField
78
+ id={`${addon.id}-input`}
79
+ type="number"
80
+ sx={{ width: 120, marginX: 2 }}
81
+ value={addonState?.quantity || 1}
82
+ onChange={(event) => handleQuantityChange(event?.target?.value ? Number(event?.target?.value) : 1)}
83
+ />
84
+ <TrashButton color="error" onClick={() => removeAddon(addon.id)}>
85
+ <Icon icon="Trash" style={{ display: 'flex' }} />
86
+ </TrashButton>
87
+ </>
88
+ )}
89
+ {!isAdded && (
90
+ <Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange()}>
91
+ {checkoutLocalization.addAddonText}
92
+ </Button>
93
+ )}
94
+ </Grid>
95
+ </AddonListItemContainer>
96
+ );
97
+ }
98
+
99
+ export function CheckoutAddonsStep() {
100
+ const { checkoutLocalization } = useCheckoutModel();
101
+ const { billingPeriod } = usePlanStepModel();
102
+ const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
103
+
104
+ return (
105
+ <CheckoutAddonsContainer container>
106
+ {availableAddons?.map((addon) => {
107
+ const addonState = addons.find((x) => x.addon.id === addon.id);
108
+ const initialAddonState = initialAddons?.find((x) => x.addon.id === addon.id);
109
+
110
+ return (
111
+ <AddonListItem
112
+ key={addon.id}
113
+ addon={addon}
114
+ billingPeriod={billingPeriod}
115
+ addonState={addonState}
116
+ initialAddonState={initialAddonState}
117
+ setAddon={setAddon}
118
+ removeAddon={removeAddon}
119
+ checkoutLocalization={checkoutLocalization}
120
+ />
121
+ );
122
+ })}
123
+ </CheckoutAddonsContainer>
124
+ );
125
+ }
@@ -0,0 +1,68 @@
1
+ import sortBy from 'lodash/sortBy';
2
+ import { Addon, BillingPeriod, Currency, SubscriptionAddon } from '@stigg/js-client-sdk';
3
+
4
+ type filterAddonsBaseProps = {
5
+ billingPeriod: BillingPeriod;
6
+ currency: Currency;
7
+ billingCountryCode?: string;
8
+ };
9
+
10
+ export const sortAddons = (addons: Addon[]) => {
11
+ return sortBy(addons, 'displayName');
12
+ };
13
+
14
+ export const sortSubscriptionAddons = (addons: SubscriptionAddon[]) => {
15
+ return sortBy(addons, 'addon.displayName');
16
+ };
17
+
18
+ export function filterAddons({
19
+ addons,
20
+ billingPeriod,
21
+ billingCountryCode,
22
+ currency,
23
+ }: { addons?: Addon[] } & filterAddonsBaseProps): Addon[] {
24
+ return (
25
+ addons
26
+ ?.filter(addon => filterAddonPricePointsByBillingPeriod(addon, billingPeriod))
27
+ ?.map(addon => mapAddonPricePointsByBillingCountryCode({ addon, currency, billingCountryCode })) || []
28
+ );
29
+ }
30
+
31
+ export function filterSubscriptionAddons({
32
+ addons,
33
+ billingPeriod,
34
+ billingCountryCode,
35
+ currency,
36
+ }: { addons?: SubscriptionAddon[] } & filterAddonsBaseProps): SubscriptionAddon[] {
37
+ return (
38
+ addons
39
+ ?.filter(addon => filterAddonPricePointsByBillingPeriod(addon.addon, billingPeriod))
40
+ ?.map(addon => ({
41
+ ...addon,
42
+ addon: mapAddonPricePointsByBillingCountryCode({ addon: addon.addon, currency, billingCountryCode }),
43
+ })) || []
44
+ );
45
+ }
46
+
47
+ function filterAddonPricePointsByBillingPeriod(addon: Addon, billingPeriod: BillingPeriod) {
48
+ return addon.pricePoints.some(pricePoint => pricePoint.billingPeriod === billingPeriod);
49
+ }
50
+
51
+ function mapAddonPricePointsByBillingCountryCode({
52
+ addon,
53
+ currency,
54
+ billingCountryCode,
55
+ }: {
56
+ addon: Addon;
57
+ currency: Currency;
58
+ billingCountryCode?: string;
59
+ }) {
60
+ return {
61
+ ...addon,
62
+ pricePoints: addon.pricePoints.filter(
63
+ pricePoint =>
64
+ pricePoint.currency === currency &&
65
+ (billingCountryCode ? pricePoint.billingCountryCode === billingCountryCode : true),
66
+ ),
67
+ };
68
+ }
@@ -0,0 +1 @@
1
+ export * from './CheckoutAddonsStep';
@@ -0,0 +1,26 @@
1
+ import styled from '@emotion/styled';
2
+ import { Grid } from '@mui/material';
3
+
4
+ export const PaymentMethodContainer = styled(Grid)<{ $disabled?: boolean }>`
5
+ padding: 8px;
6
+ border-radius: 10px;
7
+ border: 1px solid ${({ theme }) => theme.stigg.palette.outlinedBorder};
8
+ cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
9
+ `;
10
+
11
+ export const NewPaymentMethodContainer = styled(PaymentMethodContainer)`
12
+ flex-direction: column;
13
+ align-items: unset;
14
+ `;
15
+
16
+ export const PaymentMethodLayoutContainer = styled(Grid)`
17
+ display: flex;
18
+ align-items: center;
19
+ flex: 1;
20
+ gap: 12px;
21
+ `;
22
+
23
+ export const PaymentMethodTextContainer = styled(Grid)`
24
+ display: flex;
25
+ flex-direction: column;
26
+ `;
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+
3
+ import { Collapse, Grid, Radio } from '@mui/material';
4
+ import { Customer } from '@stigg/js-client-sdk';
5
+
6
+ import { Icon, Icons } from '../../../common/Icon';
7
+ import { Typography } from '../../../common/Typography';
8
+ import {
9
+ NewPaymentMethodContainer,
10
+ PaymentMethodContainer,
11
+ PaymentMethodLayoutContainer,
12
+ PaymentMethodTextContainer,
13
+ } from './PaymentMethods.style';
14
+ import { StripePaymentForm } from './stripe';
15
+ import { CheckoutLocalization } from '../../textOverrides';
16
+
17
+ export type PaymentMethodLayoutProps = {
18
+ checked: boolean;
19
+ icon: Icons;
20
+ text: React.ReactNode;
21
+ subtitle?: React.ReactNode;
22
+ readOnly?: boolean;
23
+ };
24
+
25
+ export type PaymentMethodProps = Pick<Customer, 'paymentMethodDetails'> &
26
+ Pick<PaymentMethodLayoutProps, 'checked' | 'readOnly'> & { onSelect: () => void };
27
+
28
+ export type NewPaymentMethodProps = Pick<PaymentMethodLayoutProps, 'checked' | 'readOnly'> & {
29
+ onSelect: () => void;
30
+ checkoutLocalization: CheckoutLocalization;
31
+ };
32
+
33
+ function PaymentMethodLayout({ checked, icon, text, subtitle, readOnly }: PaymentMethodLayoutProps) {
34
+ return (
35
+ <PaymentMethodLayoutContainer>
36
+ <Radio checked={checked} disabled={readOnly} />
37
+ <Icon icon={icon} style={{ display: 'contents' }} />
38
+ <PaymentMethodTextContainer container>
39
+ <Grid item>{text}</Grid>
40
+ {subtitle && <Grid item>{subtitle}</Grid>}
41
+ </PaymentMethodTextContainer>
42
+ </PaymentMethodLayoutContainer>
43
+ );
44
+ }
45
+
46
+ export function ExistingPaymentMethod({ checked, paymentMethodDetails, readOnly, onSelect }: PaymentMethodProps) {
47
+ if (!paymentMethodDetails) {
48
+ return null;
49
+ }
50
+ const { last4Digits, expirationMonth, expirationYear } = paymentMethodDetails;
51
+
52
+ return (
53
+ <PaymentMethodContainer item onClick={onSelect} $disabled={readOnly}>
54
+ <PaymentMethodLayout
55
+ checked={checked}
56
+ readOnly={readOnly}
57
+ icon="PaymentMethod"
58
+ text={<Typography variant="h6">{`Ending in ${last4Digits}`}</Typography>}
59
+ subtitle={
60
+ !!expirationMonth &&
61
+ !!expirationYear && (
62
+ <Typography variant="body1">{`Exp. ${expirationMonth
63
+ .toString()
64
+ .padStart(2, '0')}/${expirationYear}`}</Typography>
65
+ )
66
+ }
67
+ />
68
+ </PaymentMethodContainer>
69
+ );
70
+ }
71
+
72
+ export function NewPaymentMethod({ checked, onSelect, readOnly, checkoutLocalization }: NewPaymentMethodProps) {
73
+ return (
74
+ <NewPaymentMethodContainer item onClick={onSelect} $disabled={readOnly}>
75
+ <PaymentMethodLayout
76
+ checked={checked}
77
+ readOnly={readOnly}
78
+ icon="PaymentMethod"
79
+ text={<Typography variant="h6">{checkoutLocalization.newPaymentMethodText}</Typography>}
80
+ />
81
+ <Collapse in={checked}>
82
+ <StripePaymentForm />
83
+ </Collapse>
84
+ </NewPaymentMethodContainer>
85
+ );
86
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled';
3
+ import { Alert, Grid } from '@mui/material';
4
+ import { useCheckoutModel, usePaymentStepModel } from '../../hooks';
5
+ import { ExistingPaymentMethod, NewPaymentMethod } from './PaymentMethods';
6
+ import { Typography } from '../../../common/Typography';
7
+
8
+ const PaymentContainer = styled(Grid)`
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 24px;
12
+ margin: 32px 0;
13
+ `;
14
+
15
+ export function PaymentStep() {
16
+ const { checkoutState, checkoutLocalization, widgetState } = useCheckoutModel();
17
+ const { customer } = checkoutState || {};
18
+ const { errorMessage, useNewPaymentMethod, setUseNewPaymentMethod } = usePaymentStepModel();
19
+ const { readOnly } = widgetState;
20
+
21
+ const handleOnSelect = (openNewPaymentMethod: boolean) => {
22
+ if (readOnly) {
23
+ return;
24
+ }
25
+
26
+ setUseNewPaymentMethod(openNewPaymentMethod);
27
+ };
28
+
29
+ return (
30
+ <PaymentContainer container>
31
+ {!!errorMessage && (
32
+ <Alert severity="error" sx={{ alignItems: 'center' }}>
33
+ <Typography overrideColor="inherit">{errorMessage}</Typography>
34
+ </Alert>
35
+ )}
36
+ <ExistingPaymentMethod
37
+ readOnly={readOnly}
38
+ checked={!useNewPaymentMethod}
39
+ paymentMethodDetails={customer?.paymentMethodDetails}
40
+ onSelect={() => handleOnSelect(false)}
41
+ />
42
+ <NewPaymentMethod
43
+ readOnly={readOnly}
44
+ checked={useNewPaymentMethod}
45
+ checkoutLocalization={checkoutLocalization}
46
+ onSelect={() => handleOnSelect(true)}
47
+ />
48
+ </PaymentContainer>
49
+ );
50
+ }
@@ -0,0 +1 @@
1
+ export * from './PaymentStep';
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Grid } from '@mui/material';
3
+ import { AddressElement, PaymentElement } from '@stripe/react-stripe-js';
4
+ import { Typography } from '../../../../common/Typography';
5
+ import { useCheckoutModel } from '../../../hooks';
6
+
7
+ export function StripePaymentForm() {
8
+ const { checkoutState, checkoutLocalization, widgetState } = useCheckoutModel();
9
+ const { customer, configuration } = checkoutState || {};
10
+ const { readOnly } = widgetState;
11
+
12
+ return (
13
+ <Grid flexDirection="column" container gap={3} padding="16px" sx={{ pointerEvents: readOnly ? 'none' : undefined }}>
14
+ <Grid flexDirection="column" container gap={2}>
15
+ <Typography variant="h6">{checkoutLocalization.newPaymentMethodBillingAddressTitle}</Typography>
16
+ <AddressElement
17
+ options={{
18
+ mode: 'billing',
19
+ fields: { phone: !!configuration?.content?.collectPhoneNumber ? 'always' : 'auto' },
20
+ defaultValues: {
21
+ ...(customer?.name && { name: customer.name }),
22
+ },
23
+ }}
24
+ />
25
+ </Grid>
26
+ <Grid flexDirection="column" container gap={2}>
27
+ <Typography variant="h6">{checkoutLocalization.newPaymentMethodCardTitle}</Typography>
28
+ <PaymentElement
29
+ onChange={() => {}}
30
+ onReady={() => {}}
31
+ options={{
32
+ readOnly,
33
+ terms: { card: 'never' },
34
+ defaultValues: {
35
+ billingDetails: {
36
+ ...(customer?.name && { name: customer.name }),
37
+ ...(customer?.email && { email: customer.email }),
38
+ },
39
+ },
40
+ }}
41
+ />
42
+ </Grid>
43
+ </Grid>
44
+ );
45
+ }
@@ -0,0 +1,3 @@
1
+ export * from './StripePaymentForm';
2
+ export * from './useStripeIntegration';
3
+ export * from './useSubmit';