@stigg/react-sdk 4.4.0-beta.8 → 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 (95) hide show
  1. package/dist/components/checkout/Checkout.d.ts +3 -2
  2. package/dist/components/checkout/CheckoutContainer.d.ts +5 -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/payment/stripe/useSubmit.d.ts +2 -2
  14. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +1 -1
  15. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -1
  16. package/dist/components/checkout/summary/CheckoutSuccess.d.ts +4 -1
  17. package/dist/components/checkout/summary/CheckoutSummary.d.ts +1 -1
  18. package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +3 -2
  19. package/dist/components/checkout/summary/components/LineItems.d.ts +12 -8
  20. package/dist/components/checkout/textOverrides.d.ts +65 -21
  21. package/dist/components/checkout/theme.d.ts +0 -1
  22. package/dist/components/checkout/types.d.ts +7 -1
  23. package/dist/components/common/TiersSelectContainer.d.ts +1 -2
  24. package/dist/components/common/Typography.d.ts +2 -2
  25. package/dist/components/common/customIcons.d.ts +2 -0
  26. package/dist/components/customerPortal/subscriptionOverview/subscriptionView/SubscriptionView.style.d.ts +1 -1
  27. package/dist/components/paywall/PlanPrice.d.ts +1 -1
  28. package/dist/components/utils/currencyUtils.d.ts +2 -1
  29. package/dist/components/utils/getFeatureName.d.ts +1 -0
  30. package/dist/react-sdk.cjs.development.js +1153 -624
  31. package/dist/react-sdk.cjs.development.js.map +1 -1
  32. package/dist/react-sdk.cjs.production.min.js +1 -1
  33. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  34. package/dist/react-sdk.esm.js +1176 -651
  35. package/dist/react-sdk.esm.js.map +1 -1
  36. package/dist/stories/mocks/checkout/consts.d.ts +11 -0
  37. package/dist/stories/mocks/checkout/mockCheckoutPreview.d.ts +2 -0
  38. package/dist/stories/mocks/checkout/mockCheckoutState.d.ts +2 -0
  39. package/dist/theme/getResolvedTheme.d.ts +1 -0
  40. package/dist/theme/types.d.ts +1 -0
  41. package/package.json +28 -20
  42. package/src/assets/coupon.svg +6 -0
  43. package/src/assets/pay-as-you-go-charge.svg +11 -0
  44. package/src/components/checkout/Checkout.tsx +5 -2
  45. package/src/components/checkout/CheckoutContainer.tsx +21 -12
  46. package/src/components/checkout/CheckoutProvider.tsx +5 -3
  47. package/src/components/checkout/components/DowngradeToFreeContainer.tsx +33 -36
  48. package/src/components/checkout/components/StyledArrow.tsx +9 -0
  49. package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
  50. package/src/components/checkout/hooks/useLoadCheckout.ts +10 -2
  51. package/src/components/checkout/hooks/usePlanStepModel.ts +22 -7
  52. package/src/components/checkout/hooks/usePreviewSubscription.ts +103 -50
  53. package/src/components/checkout/planHeader/PlanHeader.tsx +18 -25
  54. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +11 -12
  55. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +7 -6
  56. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +32 -9
  57. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +15 -11
  58. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +4 -3
  59. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +21 -7
  60. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +0 -1
  61. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +7 -4
  62. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +4 -1
  63. package/src/components/checkout/steps/payment/PaymentStep.tsx +0 -1
  64. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +8 -4
  65. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +3 -2
  66. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +11 -8
  67. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +35 -14
  68. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +10 -5
  69. package/src/components/checkout/summary/CheckoutSuccess.tsx +52 -6
  70. package/src/components/checkout/summary/CheckoutSummary.tsx +166 -59
  71. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +63 -39
  72. package/src/components/checkout/summary/components/LineItems.tsx +77 -28
  73. package/src/components/checkout/textOverrides.ts +112 -30
  74. package/src/components/checkout/theme.ts +0 -4
  75. package/src/components/checkout/types.ts +15 -1
  76. package/src/components/common/Icon.tsx +4 -6
  77. package/src/components/common/TiersSelectContainer.tsx +7 -8
  78. package/src/components/common/Typography.tsx +12 -3
  79. package/src/components/common/customIcons.ts +2 -0
  80. package/src/components/common/mapExternalTheme.ts +1 -2
  81. package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +4 -3
  82. package/src/components/paywall/PlanOfferingButton.tsx +6 -8
  83. package/src/components/paywall/PlanPrice.tsx +14 -17
  84. package/src/components/utils/currencyUtils.ts +4 -2
  85. package/src/components/utils/getFeatureName.ts +13 -5
  86. package/src/stories/Checkout.stories.tsx +37 -5
  87. package/src/stories/CustomerPortal.stories.tsx +2 -2
  88. package/src/stories/mocks/checkout/consts.ts +15 -0
  89. package/src/stories/mocks/checkout/mockCheckoutPreview.ts +138 -0
  90. package/src/stories/mocks/checkout/mockCheckoutState.ts +206 -0
  91. package/src/theme/Theme.tsx +10 -1
  92. package/src/theme/getResolvedTheme.ts +1 -0
  93. package/src/theme/types.ts +1 -0
  94. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +0 -25
  95. package/src/components/checkout/planHeader/PlanHeader.style.tsx +0 -23
@@ -1,9 +1,11 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
- import { BillingAddress, PreviewSubscription, SubscriptionPreview } from '@stigg/js-client-sdk';
2
+ import isEmpty from 'lodash/isEmpty';
3
+ import { BillingAddress, PreviewSubscription, StiggClient, SubscriptionPreviewV2 } from '@stigg/js-client-sdk';
3
4
  import { useStiggContext } from '../../StiggProvider';
4
5
  import { useCheckoutContext } from '../CheckoutProvider';
5
6
  import { useCheckoutModel } from './useCheckoutModel';
6
- import { useSubscriptionModel } from './useSubscriptionModel';
7
+ import { SubscriptionState, useSubscriptionModel } from './useSubscriptionModel';
8
+ import { MockCheckoutPreviewCallback } from '../types';
7
9
 
8
10
  function mapBillingInformation({
9
11
  billingAddress,
@@ -24,79 +26,121 @@ function mapBillingInformation({
24
26
  };
25
27
  }
26
28
 
27
- export const usePreviewSubscriptionAction = () => {
29
+ type UsePreviewSubscriptionProps = {
30
+ onMockCheckoutPreview?: MockCheckoutPreviewCallback;
31
+ };
32
+
33
+ export type PreviewSubscriptionProps = {
34
+ customerId?: string;
35
+ planId?: string;
36
+ resourceId?: string;
37
+ stigg: StiggClient;
38
+ } & SubscriptionState &
39
+ UsePreviewSubscriptionProps;
40
+
41
+ const previewSubscription = async ({
42
+ stigg,
43
+ customerId,
44
+ planId,
45
+ resourceId,
46
+ promotionCode,
47
+ addons,
48
+ billableFeatures,
49
+ billingCountryCode,
50
+ billingPeriod,
51
+ billingAddress,
52
+ taxPercentage,
53
+ onMockCheckoutPreview,
54
+ }: PreviewSubscriptionProps) => {
55
+ const estimateAddons = addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
56
+ let subscriptionPreview: SubscriptionPreviewV2 | null = null;
57
+ let errorMessage: string | null = null;
58
+
59
+ try {
60
+ if (customerId && planId) {
61
+ const previewSubscriptionProps: PreviewSubscription = {
62
+ customerId,
63
+ planId,
64
+ resourceId,
65
+ billingCountryCode,
66
+ addons: estimateAddons,
67
+ billingPeriod,
68
+ promotionCode,
69
+ billableFeatures: isEmpty(billableFeatures) ? undefined : billableFeatures,
70
+ ...mapBillingInformation({ billingAddress, taxPercentage }),
71
+ };
72
+
73
+ subscriptionPreview = onMockCheckoutPreview
74
+ ? onMockCheckoutPreview(previewSubscriptionProps)
75
+ : await stigg.previewSubscription(previewSubscriptionProps);
76
+ }
77
+ } catch (error) {
78
+ const [, errorMsg] = (error as any)?.message?.split('Error:') || [];
79
+
80
+ errorMessage = errorMsg?.trim();
81
+ }
82
+
83
+ return { subscriptionPreview, errorMessage };
84
+ };
85
+
86
+ export const usePreviewSubscriptionAction = ({ onMockCheckoutPreview }: UsePreviewSubscriptionProps = {}) => {
28
87
  const { stigg } = useStiggContext();
29
88
  const subscription = useSubscriptionModel();
30
- const [{ resourceId, planStep }] = useCheckoutContext();
31
- const { checkoutState } = useCheckoutModel();
89
+ const [{ resourceId }] = useCheckoutContext();
90
+ const { checkoutState, widgetState } = useCheckoutModel();
32
91
  const { plan, customer } = checkoutState || {};
33
92
 
34
93
  const previewSubscriptionAction = useCallback(
35
94
  async ({ promotionCode }: { promotionCode?: string | null } = {}) => {
36
- const estimateAddons = subscription.addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
37
- let subscriptionPreview: SubscriptionPreview | null = null;
38
- let errorMessage: string | null = null;
39
-
40
- let isValid = !subscription.billableFeatures.some(({ quantity }) => quantity === null || quantity <= 0);
41
- isValid = isValid && !estimateAddons.some(({ quantity }) => quantity === null || quantity <= 0);
42
- if (!isValid) {
43
- return { subscriptionPreview };
44
- }
45
-
46
- try {
47
- if (customer?.id && plan?.id) {
48
- const previewSubscriptionProps: PreviewSubscription = {
49
- customerId: customer.id,
50
- planId: plan?.id,
51
- billableFeatures: subscription.billableFeatures,
52
- addons: estimateAddons,
53
- billingPeriod: subscription.billingPeriod,
54
- promotionCode: promotionCode ?? subscription.promotionCode,
55
- resourceId,
56
- billingCountryCode: planStep.billingCountryCode,
57
- ...mapBillingInformation({
58
- billingAddress: subscription.billingAddress,
59
- taxPercentage: subscription.taxPercentage,
60
- }),
61
- };
62
- subscriptionPreview = await stigg.previewSubscription(previewSubscriptionProps);
63
- }
64
- } catch (error) {
65
- const [, errorMsg] = (error as any)?.message?.split('Error:') || [];
66
-
67
- errorMessage = errorMsg?.trim();
95
+ if (!widgetState.isValid) {
96
+ return { subscriptionPreview: null, errorMessage: null };
68
97
  }
69
98
 
70
- return { subscriptionPreview, errorMessage };
99
+ return previewSubscription({
100
+ stigg,
101
+ customerId: customer?.id,
102
+ planId: plan?.id,
103
+ resourceId,
104
+ addons: subscription.addons,
105
+ billableFeatures: subscription.billableFeatures,
106
+ billingCountryCode: subscription.billingCountryCode,
107
+ billingPeriod: subscription.billingPeriod,
108
+ billingAddress: subscription.billingAddress,
109
+ taxPercentage: subscription.taxPercentage,
110
+ promotionCode: promotionCode ?? subscription.promotionCode,
111
+ onMockCheckoutPreview,
112
+ });
71
113
  },
72
114
  [
73
- customer,
74
- plan,
75
- resourceId,
76
115
  stigg,
116
+ customer?.id,
117
+ plan?.id,
118
+ resourceId,
77
119
  subscription.addons,
78
- subscription.billingPeriod,
79
120
  subscription.billableFeatures,
80
- subscription.promotionCode,
121
+ subscription.billingCountryCode,
122
+ subscription.billingPeriod,
81
123
  subscription.billingAddress,
82
124
  subscription.taxPercentage,
83
- planStep.billingCountryCode,
125
+ subscription.promotionCode,
126
+ widgetState.isValid,
127
+ onMockCheckoutPreview,
84
128
  ],
85
129
  );
86
130
 
87
131
  return { previewSubscriptionAction };
88
132
  };
89
133
 
90
- export const usePreviewSubscription = () => {
91
- const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreview | null>(null);
134
+ const SUBSCRIPTION_PREVIEW_DEBOUNCE_TIME = 500;
135
+
136
+ export const usePreviewSubscription = ({ onMockCheckoutPreview }: UsePreviewSubscriptionProps = {}) => {
137
+ const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreviewV2 | null>(null);
92
138
  const [isFetchingSubscriptionPreview, setIsFetchingSubscriptionPreview] = useState(false);
93
139
 
94
- const { previewSubscriptionAction } = usePreviewSubscriptionAction();
140
+ const { previewSubscriptionAction } = usePreviewSubscriptionAction({ onMockCheckoutPreview });
95
141
 
96
142
  useEffect(() => {
97
143
  const estimateSubscription = async () => {
98
- setIsFetchingSubscriptionPreview(true);
99
-
100
144
  const { subscriptionPreview } = await previewSubscriptionAction();
101
145
  if (subscriptionPreview) {
102
146
  setSubscriptionPreview(subscriptionPreview);
@@ -105,7 +149,16 @@ export const usePreviewSubscription = () => {
105
149
  setIsFetchingSubscriptionPreview(false);
106
150
  };
107
151
 
108
- void estimateSubscription();
152
+ setIsFetchingSubscriptionPreview(true);
153
+
154
+ const timer = setTimeout(() => {
155
+ setIsFetchingSubscriptionPreview(true);
156
+ void estimateSubscription();
157
+ }, SUBSCRIPTION_PREVIEW_DEBOUNCE_TIME);
158
+
159
+ return () => {
160
+ clearTimeout(timer);
161
+ };
109
162
  }, [previewSubscriptionAction]);
110
163
 
111
164
  return { subscriptionPreview, isFetchingSubscriptionPreview };
@@ -1,10 +1,9 @@
1
1
  import React from 'react';
2
2
 
3
- import { Divider } from '@mui/material';
3
+ import { Box, Divider } from '@mui/material';
4
4
 
5
5
  import { Typography } from '../../common/Typography';
6
6
  import { useCheckoutModel } from '../hooks/useCheckoutModel';
7
- import { PlanHeaderContainer, PlanPathContainer, StyledArrowRightIcon } from './PlanHeader.style';
8
7
  import { CheckoutContainerProps } from '../CheckoutContainer';
9
8
  import { ChangePlanButton } from '../components/ChangePlanButton';
10
9
 
@@ -14,37 +13,31 @@ type PlanHeaderProps = {
14
13
 
15
14
  export function PlanHeader({ allowChangePlan = false, onChangePlan }: PlanHeaderProps) {
16
15
  const { checkoutState, checkoutLocalization } = useCheckoutModel();
17
- const { plan, activeSubscription } = checkoutState || {};
18
- const isPlanChanged = plan?.id !== activeSubscription?.plan.id;
16
+ const { plan } = checkoutState || {};
19
17
 
20
18
  return (
21
19
  <>
22
- <PlanHeaderContainer>
23
- <PlanPathContainer>
24
- {activeSubscription && isPlanChanged && (
25
- <>
26
- <Typography variant="h6" color="secondary">
27
- {activeSubscription.plan.displayName}
28
- </Typography>
29
- <StyledArrowRightIcon />
30
- </>
31
- )}
20
+ <Box sx={{ marginBottom: '16px' }}>
21
+ <Typography variant="body1" color="disabled" style={{ opacity: 0.6, marginBottom: '8px' }}>
22
+ Selected plan
23
+ </Typography>
32
24
 
25
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
33
26
  <Typography variant="h3" bold>
34
27
  {plan?.displayName}
35
28
  </Typography>
36
- </PlanPathContainer>
37
29
 
38
- {allowChangePlan && onChangePlan && (
39
- <ChangePlanButton
40
- onClick={() => {
41
- onChangePlan({ currentPlan: plan });
42
- }}
43
- checkoutLocalization={checkoutLocalization}
44
- size="medium"
45
- />
46
- )}
47
- </PlanHeaderContainer>
30
+ {allowChangePlan && onChangePlan && (
31
+ <ChangePlanButton
32
+ onClick={() => {
33
+ onChangePlan({ currentPlan: plan });
34
+ }}
35
+ checkoutLocalization={checkoutLocalization}
36
+ size="medium"
37
+ />
38
+ )}
39
+ </Box>
40
+ </Box>
48
41
  <Divider className="stigg-checkout-plan-header-divider" />
49
42
  </>
50
43
  );
@@ -1,7 +1,5 @@
1
1
  import styled from '@emotion/styled/macro';
2
2
  import { Button, buttonClasses, LinearProgress, linearProgressClasses } from '@mui/material';
3
- import Color from 'color';
4
-
5
3
  import { Icon } from '../../common/Icon';
6
4
 
7
5
  export const StyledProgress = styled(LinearProgress, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{
@@ -12,9 +10,7 @@ export const StyledProgress = styled(LinearProgress, { shouldForwardProp: (prop)
12
10
  backgroundColor: theme.stigg.palette.outlinedBorder,
13
11
  },
14
12
  [`& .${linearProgressClasses.bar}`]: {
15
- backgroundColor: $disabled
16
- ? Color(theme.stigg.palette.outlinedBorder).darken(0.2).hex()
17
- : theme.stigg.palette.primary,
13
+ backgroundColor: $disabled ? theme.stigg.palette.primaryLight : theme.stigg.palette.primary,
18
14
  },
19
15
  }));
20
16
 
@@ -27,10 +23,13 @@ export const StyledStepButton = styled(Button)(() => ({
27
23
  },
28
24
  }));
29
25
 
30
- export const StyledIcon = styled(Icon)<{ $disabled?: boolean; $shouldStroke?: boolean }>(
31
- ({ theme, $disabled, $shouldStroke = true }) => ({
32
- circle: {
33
- stroke: $shouldStroke ? ($disabled ? theme.stigg.palette.text.disabled : theme.stigg.palette.primary) : undefined,
34
- },
35
- }),
36
- );
26
+ export const StyledIcon = styled(Icon, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{
27
+ $disabled?: boolean;
28
+ $shouldStroke?: boolean;
29
+ $shouldFill?: boolean;
30
+ }>(({ theme, $disabled, $shouldStroke = true, $shouldFill }) => ({
31
+ circle: {
32
+ stroke: $shouldStroke ? ($disabled ? theme.stigg.palette.primaryLight : theme.stigg.palette.primary) : undefined,
33
+ fill: $shouldFill ? ($disabled ? theme.stigg.palette.primaryLight : theme.stigg.palette.primary) : undefined,
34
+ },
35
+ }));
@@ -34,15 +34,16 @@ export const CheckoutProgressBar = () => {
34
34
  <Grid item display="flex" flexDirection="row" alignItems="center" gap={1}>
35
35
  <StepIcon
36
36
  icon={
37
- isCompleted ? (
38
- <StyledIcon icon={checkedIcon} $shouldStroke={!isDisabled} />
39
- ) : (
40
- <StyledIcon icon="OutlinedCircle" $disabled={isDisabled} />
41
- )
37
+ <StyledIcon
38
+ icon={isCompleted ? checkedIcon : 'OutlinedCircle'}
39
+ $disabled={isDisabled}
40
+ $shouldStroke={(isCompleted && !isDisabled) || isDisabled}
41
+ $shouldFill={isCompleted}
42
+ />
42
43
  }
43
44
  />
44
45
 
45
- <Typography variant="h6" color={isDisabled ? 'disabled' : 'primary.main'}>
46
+ <Typography variant="h6" color={isDisabled ? 'primary.main.light' : 'primary.main'}>
46
47
  {label}
47
48
  </Typography>
48
49
  </Grid>
@@ -9,7 +9,7 @@ import { Button, InputField } from '../components';
9
9
  import { usePreviewSubscriptionAction } from '../hooks';
10
10
  import { usePromotionCodeModel } from '../hooks/useCouponModel';
11
11
  import { AddPromotionCodeButton } from './AddPromotionCodeButton';
12
- import { CheckoutLocalization } from '../textOverrides';
12
+ import { PromotionCodeSectionProps } from './PromotionCodeSection';
13
13
 
14
14
  const CouponCodeAddButton = styled(Button)`
15
15
  padding: 4px 10px;
@@ -19,13 +19,17 @@ const CouponCodeAddButton = styled(Button)`
19
19
  align-items: center;
20
20
  `;
21
21
 
22
- export const AddPromotionCode = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
22
+ export const AddPromotionCode = ({
23
+ disabled,
24
+ checkoutLocalization,
25
+ onMockCheckoutPreview,
26
+ }: PromotionCodeSectionProps) => {
23
27
  const { setPromotionCode: persistPromotionCode } = usePromotionCodeModel();
24
28
  const [showInput, setShowInput] = useState(false);
25
29
  const [promotionCode, setPromotionCode] = React.useState('');
26
30
  const [isLoading, setIsLoading] = React.useState(false);
27
31
  const [errorMessage, setErrorMessage] = React.useState('');
28
- const { previewSubscriptionAction } = usePreviewSubscriptionAction();
32
+ const { previewSubscriptionAction } = usePreviewSubscriptionAction({ onMockCheckoutPreview });
29
33
 
30
34
  const handlePromotionCode = async () => {
31
35
  setIsLoading(true);
@@ -33,26 +37,33 @@ export const AddPromotionCode = ({ checkoutLocalization }: { checkoutLocalizatio
33
37
 
34
38
  const { subscriptionPreview, errorMessage } = await previewSubscriptionAction({ promotionCode });
35
39
 
36
- if (!errorMessage && subscriptionPreview?.discount) {
40
+ if (!errorMessage && subscriptionPreview?.discountDetails) {
37
41
  persistPromotionCode(promotionCode.toUpperCase());
38
42
  setShowInput(false);
39
- } else if (!!errorMessage) {
43
+ } else if (errorMessage) {
40
44
  setErrorMessage(errorMessage);
41
45
  }
42
46
  setIsLoading(false);
43
47
  };
44
48
 
45
49
  if (!showInput) {
46
- return <AddPromotionCodeButton onAddClick={() => setShowInput(true)} checkoutLocalization={checkoutLocalization} />;
50
+ return (
51
+ <AddPromotionCodeButton
52
+ disabled={disabled}
53
+ onAddClick={() => setShowInput(true)}
54
+ checkoutLocalization={checkoutLocalization}
55
+ />
56
+ );
47
57
  }
48
58
 
49
59
  return (
50
60
  <Grid>
51
- <Typography variant="body1" color={errorMessage ? 'error' : 'primary.main'}>
52
- {checkoutLocalization.couponCodeTitle}
61
+ <Typography variant="body1" color={errorMessage ? 'error' : disabled ? 'disabled' : 'primary.main'}>
62
+ {checkoutLocalization.summary.couponCodeTitle}
53
63
  </Typography>
54
64
 
55
65
  <InputField
66
+ disabled={disabled}
56
67
  autoFocus
57
68
  variant="outlined"
58
69
  fullWidth
@@ -62,9 +73,21 @@ export const AddPromotionCode = ({ checkoutLocalization }: { checkoutLocalizatio
62
73
  setPromotionCode(e.target.value);
63
74
  }}
64
75
  inputProps={{ maxLength: 20 }}
76
+ onKeyDown={(e: any) => {
77
+ if (e.key === 'Enter') {
78
+ void handlePromotionCode();
79
+ e.preventDefault();
80
+ }
81
+ }}
82
+ // eslint-disable-next-line react/jsx-no-duplicate-props
65
83
  InputProps={{
66
84
  endAdornment: (
67
- <CouponCodeAddButton variant="contained" disabled={isLoading} onClick={handlePromotionCode}>
85
+ <CouponCodeAddButton
86
+ variant="contained"
87
+ disabled={disabled}
88
+ onClick={() => {
89
+ void handlePromotionCode();
90
+ }}>
68
91
  {isLoading ? (
69
92
  <CircularProgress size={18} sx={{ color: 'white' }} />
70
93
  ) : (
@@ -6,34 +6,38 @@ import { Typography } from '../../common/Typography';
6
6
  import { Button } from '../components';
7
7
  import { CheckoutLocalization } from '../textOverrides';
8
8
 
9
- const StyledPlusIcon = styled(PlusIcon)`
9
+ const StyledPlusIcon = styled(PlusIcon, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{ $disabled: boolean }>`
10
10
  path {
11
- stroke: ${({ theme }) => theme.stigg.palette.primary};
11
+ stroke: ${({ theme, $disabled }) => ($disabled ? theme.stigg.palette.text.disabled : theme.stigg.palette.primary)};
12
12
  }
13
13
  `;
14
14
 
15
15
  export type AddPromotionCodeButtonProps = {
16
16
  onAddClick: () => void;
17
17
  checkoutLocalization: CheckoutLocalization;
18
+ disabled: boolean;
18
19
  };
19
20
 
20
- export const AddPromotionCodeButton = ({ onAddClick, checkoutLocalization }: AddPromotionCodeButtonProps) => (
21
+ export const AddPromotionCodeButton = ({ onAddClick, checkoutLocalization, disabled }: AddPromotionCodeButtonProps) => (
21
22
  <Button
23
+ disabled={disabled}
22
24
  fullWidth
23
25
  className="stigg-checkout-summary-add-coupon-code-button"
24
- sx={{ textTransform: 'none', justifyContent: 'flex-start' }}
26
+ sx={{
27
+ textTransform: 'none',
28
+ justifyContent: 'flex-start',
29
+ color: disabled ? 'text.disabled' : 'primary.main',
30
+ }}
25
31
  variant="text"
26
32
  size="medium"
27
- onClick={onAddClick}
28
- >
29
- <StyledPlusIcon className="stigg-checkout-summary-add-coupon-code-button-icon" />
33
+ onClick={onAddClick}>
34
+ <StyledPlusIcon $disabled={disabled} className="stigg-checkout-summary-add-coupon-code-button-icon" />
30
35
  <Typography
31
36
  className="stigg-checkout-change-plan-button-text"
32
- color="primary.main"
37
+ color={disabled ? 'disabled' : 'primary.main'}
33
38
  style={{ lineHeight: '24px' }}
34
- variant="body1"
35
- >
36
- {checkoutLocalization.addCouponCodeText}
39
+ variant="body1">
40
+ {checkoutLocalization.summary.addCouponCodeText}
37
41
  </Typography>
38
42
  </Button>
39
43
  );
@@ -11,7 +11,7 @@ const AppliedCouponContainer = styled(Grid)`
11
11
  align-items: center;
12
12
  flex-direction: row;
13
13
  gap: 8px;
14
- padding: 6px 16px;
14
+ padding: 6px 8px;
15
15
  border-radius: ${({ theme }) => theme.stigg.border.radius};
16
16
  border: 1px solid ${({ theme }) => theme.stigg.palette.primary};
17
17
  `;
@@ -23,8 +23,9 @@ export type AppliedPromotionCodeProps = {
23
23
 
24
24
  export const AppliedPromotionCode = ({ promotionCode, onClearPromotionCode }: AppliedPromotionCodeProps) => (
25
25
  <AppliedCouponContainer container>
26
- <Grid item>
27
- <Typography variant="body1" color="primary.main">
26
+ <Grid item display="flex" gap={1} alignItems="center">
27
+ <Icon icon="Coupon" style={{ display: 'flex' }} />
28
+ <Typography variant="body1" color="primary.main" lineHeight="auto">
28
29
  {promotionCode}
29
30
  </Typography>
30
31
  </Grid>
@@ -4,24 +4,38 @@ import { usePromotionCodeModel } from '../hooks/useCouponModel';
4
4
  import { AddPromotionCode } from './AddPromotionCode';
5
5
  import { AppliedPromotionCode } from './AppliedPromotionCode';
6
6
  import { CheckoutLocalization } from '../textOverrides';
7
+ import { MockCheckoutPreviewCallback } from '../types';
7
8
 
8
9
  const PromotionCodeSectionContainer = styled.div`
9
10
  margin: 16px 0;
10
11
  `;
11
12
 
12
- export const PromotionCodeSection = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
13
+ export type PromotionCodeSectionProps = {
14
+ disabled: boolean;
15
+ checkoutLocalization: CheckoutLocalization;
16
+ onMockCheckoutPreview?: MockCheckoutPreviewCallback;
17
+ };
18
+
19
+ export const PromotionCodeSection = ({
20
+ disabled,
21
+ checkoutLocalization,
22
+ onMockCheckoutPreview,
23
+ }: PromotionCodeSectionProps) => {
13
24
  const { promotionCode, setPromotionCode } = usePromotionCodeModel();
14
25
 
15
26
  const onClearPromotionCode = () => {
16
27
  setPromotionCode('');
17
28
  };
18
29
 
19
- let content: React.ReactNode = null;
20
- if (promotionCode) {
21
- content = <AppliedPromotionCode promotionCode={promotionCode} onClearPromotionCode={onClearPromotionCode} />;
22
- } else {
23
- content = <AddPromotionCode checkoutLocalization={checkoutLocalization} />;
24
- }
30
+ const content = promotionCode ? (
31
+ <AppliedPromotionCode promotionCode={promotionCode} onClearPromotionCode={onClearPromotionCode} />
32
+ ) : (
33
+ <AddPromotionCode
34
+ disabled={disabled}
35
+ checkoutLocalization={checkoutLocalization}
36
+ onMockCheckoutPreview={onMockCheckoutPreview}
37
+ />
38
+ );
25
39
 
26
40
  return <PromotionCodeSectionContainer>{content}</PromotionCodeSectionContainer>;
27
41
  };
@@ -5,7 +5,6 @@ import { Button } from '../../components';
5
5
 
6
6
  export const CheckoutAddonsContainer = styled(Grid)`
7
7
  width: 100%;
8
- margin-top: 32px;
9
8
  gap: 8px;
10
9
  `;
11
10
 
@@ -42,7 +42,6 @@ function AddonListItem({
42
42
  const isAdded = !!addonState;
43
43
  const hasChanges =
44
44
  (!!addonState && !!initialAddonState && addonState.quantity !== initialAddonState.quantity) ||
45
- (!initialAddonState && !!addonState) ||
46
45
  (!!initialAddonState && !addonState);
47
46
 
48
47
  const handleQuantityChange = (quantity: number | null) => {
@@ -79,6 +78,7 @@ function AddonListItem({
79
78
  {`${currencyPriceFormatter({
80
79
  amount: addonPrice.amount!,
81
80
  currency: addonPrice.currency,
81
+ minimumFractionDigits: 2,
82
82
  })}/${billingPeriod === BillingPeriod.Annually ? 'year' : 'month'}`}
83
83
  </Typography>
84
84
  )}
@@ -115,7 +115,9 @@ function AddonListItem({
115
115
  )}
116
116
  {!isAdded && (
117
117
  <Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange(1)}>
118
- {checkoutLocalization.addAddonText}
118
+ <Typography color="primary.main" variant="body1">
119
+ {checkoutLocalization.addAddonText}
120
+ </Typography>
119
121
  </Button>
120
122
  )}
121
123
  </Grid>
@@ -124,7 +126,7 @@ function AddonListItem({
124
126
  }
125
127
 
126
128
  export function CheckoutAddonsStep() {
127
- const { checkoutLocalization } = useCheckoutModel();
129
+ const { checkoutLocalization, setIsValid } = useCheckoutModel();
128
130
  const { billingPeriod } = usePlanStepModel();
129
131
  const { setIsDisabled } = useProgressBarModel();
130
132
  const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
@@ -138,7 +140,8 @@ export function CheckoutAddonsStep() {
138
140
  useEffect(() => {
139
141
  const isDisabled = Object.values(addonsValidation).some((x) => !x);
140
142
  setIsDisabled(isDisabled);
141
- }, [addonsValidation, setIsDisabled]);
143
+ setIsValid(!isDisabled);
144
+ }, [addonsValidation, setIsDisabled, setIsValid]);
142
145
 
143
146
  return (
144
147
  <CheckoutAddonsContainer container>
@@ -1,11 +1,14 @@
1
1
  import styled from '@emotion/styled';
2
2
  import { Grid } from '@mui/material';
3
3
 
4
- export const PaymentMethodContainer = styled(Grid)<{ $disabled?: boolean }>`
4
+ export const PaymentMethodContainer = styled(Grid, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{
5
+ $disabled?: boolean;
6
+ }>`
5
7
  padding: 8px;
6
8
  border-radius: 10px;
7
9
  border: 1px solid ${({ theme }) => theme.stigg.palette.outlinedBorder};
8
10
  cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
11
+ opacity: ${({ $disabled }) => ($disabled ? 0.6 : 1)};
9
12
  `;
10
13
 
11
14
  export const NewPaymentMethodContainer = styled(PaymentMethodContainer)`
@@ -10,7 +10,6 @@ const PaymentContainer = styled(Grid)`
10
10
  display: flex;
11
11
  flex-direction: column;
12
12
  gap: 24px;
13
- margin: 32px 0;
14
13
  `;
15
14
 
16
15
  export function PaymentStep({ onBillingAddressChange }: Pick<CheckoutContainerProps, 'onBillingAddressChange'>) {
@@ -14,9 +14,9 @@ export type HandleSubmitResult = { results?: ApplySubscriptionResults; success:
14
14
 
15
15
  export type UseSubmitProps = {
16
16
  onSuccess?: () => void;
17
- } & Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted'>;
17
+ } & Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted' | 'disableSuccessAnimation'>;
18
18
 
19
- export function useSubmit({ onCheckout, onCheckoutCompleted, onSuccess }: UseSubmitProps) {
19
+ export function useSubmit({ onCheckout, onCheckoutCompleted, onSuccess, disableSuccessAnimation }: UseSubmitProps) {
20
20
  const { stigg } = useStiggContext();
21
21
  const { useNewPaymentMethod } = usePaymentStepModel();
22
22
  const subscriptionState = useSubscriptionState();
@@ -83,25 +83,29 @@ export function useSubmit({ onCheckout, onCheckoutCompleted, onSuccess }: UseSub
83
83
 
84
84
  setWidgetReadOnly(true);
85
85
 
86
+ let success = false;
86
87
  if (onCheckout) {
87
88
  const externalCheckoutResults = await onCheckout({ checkoutParams, checkoutAction });
88
89
  if (!externalCheckoutResults.success && externalCheckoutResults.errorMessage) {
89
90
  errorMessage = externalCheckoutResults.errorMessage;
90
91
  }
92
+ success = externalCheckoutResults.success && !errorMessage;
91
93
  } else {
92
94
  const checkoutActionResults = await checkoutAction();
93
95
  if (!checkoutActionResults.success && checkoutActionResults.errorMessage) {
94
96
  errorMessage = checkoutActionResults.errorMessage;
95
97
  }
98
+ success = checkoutActionResults.success && !errorMessage;
96
99
  }
97
100
 
98
101
  setWidgetReadOnly(false);
99
102
 
100
- const success = !errorMessage && !!checkoutResults?.subscription;
101
103
  if (success && onSuccess) {
102
104
  onSuccess();
103
105
 
104
- await delay(ANIMATION_DURATION); // Wait for animation to finish
106
+ if (!disableSuccessAnimation) {
107
+ await delay(ANIMATION_DURATION); // Wait for animation to finish
108
+ }
105
109
  }
106
110
 
107
111
  await onCheckoutCompleted({ success, error: errorMessage });