@stigg/react-sdk 4.4.0-beta.9 → 4.4.1

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 (92) hide show
  1. package/dist/components/checkout/Checkout.d.ts +3 -2
  2. package/dist/components/checkout/CheckoutContainer.d.ts +4 -2
  3. package/dist/components/checkout/CheckoutProvider.d.ts +3 -2
  4. package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +3 -7
  5. package/dist/components/checkout/components/StyledArrow.d.ts +5 -0
  6. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
  7. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +3 -1
  8. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +17 -8
  9. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +1 -1
  10. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +2 -4
  11. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +2 -1
  12. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +6 -2
  13. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +1 -1
  14. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -1
  15. package/dist/components/checkout/summary/CheckoutSummary.d.ts +1 -1
  16. package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +2 -2
  17. package/dist/components/checkout/summary/components/LineItems.d.ts +12 -8
  18. package/dist/components/checkout/textOverrides.d.ts +63 -20
  19. package/dist/components/checkout/theme.d.ts +0 -1
  20. package/dist/components/checkout/types.d.ts +7 -1
  21. package/dist/components/common/TiersSelectContainer.d.ts +1 -2
  22. package/dist/components/common/Typography.d.ts +2 -2
  23. package/dist/components/common/customIcons.d.ts +2 -0
  24. package/dist/components/customerPortal/subscriptionOverview/subscriptionView/SubscriptionView.style.d.ts +1 -1
  25. package/dist/components/paywall/PlanPrice.d.ts +1 -1
  26. package/dist/components/utils/currencyUtils.d.ts +2 -1
  27. package/dist/components/utils/getFeatureName.d.ts +1 -0
  28. package/dist/react-sdk.cjs.development.js +1030 -560
  29. package/dist/react-sdk.cjs.development.js.map +1 -1
  30. package/dist/react-sdk.cjs.production.min.js +1 -1
  31. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  32. package/dist/react-sdk.esm.js +1051 -590
  33. package/dist/react-sdk.esm.js.map +1 -1
  34. package/dist/stories/mocks/checkout/consts.d.ts +11 -0
  35. package/dist/stories/mocks/checkout/mockCheckoutPreview.d.ts +2 -0
  36. package/dist/stories/mocks/checkout/mockCheckoutState.d.ts +2 -0
  37. package/dist/theme/getResolvedTheme.d.ts +1 -0
  38. package/dist/theme/types.d.ts +1 -0
  39. package/package.json +28 -20
  40. package/src/assets/coupon.svg +6 -0
  41. package/src/assets/pay-as-you-go-charge.svg +11 -0
  42. package/src/components/checkout/Checkout.tsx +5 -2
  43. package/src/components/checkout/CheckoutContainer.tsx +20 -13
  44. package/src/components/checkout/CheckoutProvider.tsx +5 -3
  45. package/src/components/checkout/components/DowngradeToFreeContainer.tsx +33 -36
  46. package/src/components/checkout/components/InputField.tsx +3 -0
  47. package/src/components/checkout/components/StyledArrow.tsx +9 -0
  48. package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
  49. package/src/components/checkout/hooks/useLoadCheckout.ts +10 -2
  50. package/src/components/checkout/hooks/usePreviewSubscription.ts +102 -50
  51. package/src/components/checkout/planHeader/PlanHeader.tsx +18 -25
  52. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +10 -12
  53. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +6 -6
  54. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +32 -9
  55. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +15 -11
  56. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +4 -3
  57. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +21 -7
  58. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +0 -1
  59. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +4 -3
  60. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +4 -1
  61. package/src/components/checkout/steps/payment/PaymentStep.tsx +0 -1
  62. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +3 -2
  63. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +5 -2
  64. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +35 -14
  65. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +10 -5
  66. package/src/components/checkout/summary/CheckoutSuccess.tsx +1 -1
  67. package/src/components/checkout/summary/CheckoutSummary.tsx +143 -46
  68. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +38 -15
  69. package/src/components/checkout/summary/components/LineItems.tsx +77 -28
  70. package/src/components/checkout/textOverrides.ts +107 -27
  71. package/src/components/checkout/theme.ts +0 -4
  72. package/src/components/checkout/types.ts +15 -1
  73. package/src/components/common/Icon.tsx +4 -6
  74. package/src/components/common/TiersSelectContainer.tsx +7 -8
  75. package/src/components/common/Typography.tsx +12 -3
  76. package/src/components/common/customIcons.ts +2 -0
  77. package/src/components/common/mapExternalTheme.ts +1 -2
  78. package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +4 -3
  79. package/src/components/paywall/PlanOfferingButton.tsx +6 -8
  80. package/src/components/paywall/PlanPrice.tsx +14 -17
  81. package/src/components/utils/currencyUtils.ts +4 -2
  82. package/src/components/utils/getFeatureName.ts +13 -5
  83. package/src/stories/Checkout.stories.tsx +37 -6
  84. package/src/stories/CustomerPortal.stories.tsx +2 -2
  85. package/src/stories/mocks/checkout/consts.ts +15 -0
  86. package/src/stories/mocks/checkout/mockCheckoutPreview.ts +138 -0
  87. package/src/stories/mocks/checkout/mockCheckoutState.ts +206 -0
  88. package/src/theme/Theme.tsx +10 -1
  89. package/src/theme/getResolvedTheme.ts +1 -0
  90. package/src/theme/types.ts +1 -0
  91. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +0 -25
  92. package/src/components/checkout/planHeader/PlanHeader.style.tsx +0 -23
@@ -1,10 +1,11 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
2
  import isEmpty from 'lodash/isEmpty';
3
- import { BillingAddress, PreviewSubscription, SubscriptionPreview } from '@stigg/js-client-sdk';
3
+ import { BillingAddress, PreviewSubscription, StiggClient, SubscriptionPreviewV2 } from '@stigg/js-client-sdk';
4
4
  import { useStiggContext } from '../../StiggProvider';
5
5
  import { useCheckoutContext } from '../CheckoutProvider';
6
6
  import { useCheckoutModel } from './useCheckoutModel';
7
- import { useSubscriptionModel } from './useSubscriptionModel';
7
+ import { SubscriptionState, useSubscriptionModel } from './useSubscriptionModel';
8
+ import { MockCheckoutPreviewCallback } from '../types';
8
9
 
9
10
  function mapBillingInformation({
10
11
  billingAddress,
@@ -25,79 +26,121 @@ function mapBillingInformation({
25
26
  };
26
27
  }
27
28
 
28
- 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 = {}) => {
29
87
  const { stigg } = useStiggContext();
30
88
  const subscription = useSubscriptionModel();
31
- const [{ resourceId, planStep }] = useCheckoutContext();
32
- const { checkoutState } = useCheckoutModel();
89
+ const [{ resourceId }] = useCheckoutContext();
90
+ const { checkoutState, widgetState } = useCheckoutModel();
33
91
  const { plan, customer } = checkoutState || {};
34
92
 
35
93
  const previewSubscriptionAction = useCallback(
36
94
  async ({ promotionCode }: { promotionCode?: string | null } = {}) => {
37
- const estimateAddons = subscription.addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
38
- let subscriptionPreview: SubscriptionPreview | null = null;
39
- let errorMessage: string | null = null;
40
-
41
- let isValid = !subscription.billableFeatures.some(({ quantity }) => quantity === null || quantity <= 0);
42
- isValid = isValid && !estimateAddons.some(({ quantity }) => quantity === null || quantity <= 0);
43
- if (!isValid) {
44
- return { subscriptionPreview };
45
- }
46
-
47
- try {
48
- if (customer?.id && plan?.id) {
49
- const previewSubscriptionProps: PreviewSubscription = {
50
- customerId: customer.id,
51
- planId: plan?.id,
52
- billableFeatures: isEmpty(subscription.billableFeatures) ? undefined : subscription.billableFeatures,
53
- addons: estimateAddons,
54
- billingPeriod: subscription.billingPeriod,
55
- promotionCode: promotionCode ?? subscription.promotionCode,
56
- resourceId,
57
- billingCountryCode: planStep.billingCountryCode,
58
- ...mapBillingInformation({
59
- billingAddress: subscription.billingAddress,
60
- taxPercentage: subscription.taxPercentage,
61
- }),
62
- };
63
- subscriptionPreview = await stigg.previewSubscription(previewSubscriptionProps);
64
- }
65
- } catch (error) {
66
- const [, errorMsg] = (error as any)?.message?.split('Error:') || [];
67
-
68
- errorMessage = errorMsg?.trim();
95
+ if (!widgetState.isValid) {
96
+ return { subscriptionPreview: null, errorMessage: null };
69
97
  }
70
98
 
71
- 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
+ });
72
113
  },
73
114
  [
74
- customer,
75
- plan,
76
- resourceId,
77
115
  stigg,
116
+ customer?.id,
117
+ plan?.id,
118
+ resourceId,
78
119
  subscription.addons,
79
- subscription.billingPeriod,
80
120
  subscription.billableFeatures,
81
- subscription.promotionCode,
121
+ subscription.billingCountryCode,
122
+ subscription.billingPeriod,
82
123
  subscription.billingAddress,
83
124
  subscription.taxPercentage,
84
- planStep.billingCountryCode,
125
+ subscription.promotionCode,
126
+ widgetState.isValid,
127
+ onMockCheckoutPreview,
85
128
  ],
86
129
  );
87
130
 
88
131
  return { previewSubscriptionAction };
89
132
  };
90
133
 
91
- export const usePreviewSubscription = () => {
92
- 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);
93
138
  const [isFetchingSubscriptionPreview, setIsFetchingSubscriptionPreview] = useState(false);
94
139
 
95
- const { previewSubscriptionAction } = usePreviewSubscriptionAction();
140
+ const { previewSubscriptionAction } = usePreviewSubscriptionAction({ onMockCheckoutPreview });
96
141
 
97
142
  useEffect(() => {
98
143
  const estimateSubscription = async () => {
99
- setIsFetchingSubscriptionPreview(true);
100
-
101
144
  const { subscriptionPreview } = await previewSubscriptionAction();
102
145
  if (subscriptionPreview) {
103
146
  setSubscriptionPreview(subscriptionPreview);
@@ -106,7 +149,16 @@ export const usePreviewSubscription = () => {
106
149
  setIsFetchingSubscriptionPreview(false);
107
150
  };
108
151
 
109
- 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
+ };
110
162
  }, [previewSubscriptionAction]);
111
163
 
112
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,12 @@ 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
+ $shouldFill?: boolean;
29
+ }>(({ theme, $disabled, $shouldFill }) => ({
30
+ circle: {
31
+ stroke: $disabled ? theme.stigg.palette.primaryLight : theme.stigg.palette.primary,
32
+ fill: $shouldFill ? ($disabled ? theme.stigg.palette.primaryLight : theme.stigg.palette.primary) : undefined,
33
+ },
34
+ }));
@@ -34,15 +34,15 @@ 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
+ $shouldFill={isCompleted}
41
+ />
42
42
  }
43
43
  />
44
44
 
45
- <Typography variant="h6" color={isDisabled ? 'disabled' : 'primary.main'}>
45
+ <Typography variant="h6" color={isDisabled ? 'primary.main.light' : 'primary.main'}>
46
46
  {label}
47
47
  </Typography>
48
48
  </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
  )}
@@ -126,7 +126,7 @@ function AddonListItem({
126
126
  }
127
127
 
128
128
  export function CheckoutAddonsStep() {
129
- const { checkoutLocalization } = useCheckoutModel();
129
+ const { checkoutLocalization, setIsValid } = useCheckoutModel();
130
130
  const { billingPeriod } = usePlanStepModel();
131
131
  const { setIsDisabled } = useProgressBarModel();
132
132
  const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
@@ -140,7 +140,8 @@ export function CheckoutAddonsStep() {
140
140
  useEffect(() => {
141
141
  const isDisabled = Object.values(addonsValidation).some((x) => !x);
142
142
  setIsDisabled(isDisabled);
143
- }, [addonsValidation, setIsDisabled]);
143
+ setIsValid(!isDisabled);
144
+ }, [addonsValidation, setIsDisabled, setIsValid]);
144
145
 
145
146
  return (
146
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'>) {
@@ -30,8 +30,9 @@ export const BillingPeriodButton = styled.button<{
30
30
  background: ${({ theme, $isActive, $isOnlyBillingPeriod }) => {
31
31
  if ($isOnlyBillingPeriod) {
32
32
  return 'transparent';
33
- } else if ($isActive) {
34
- return theme.stigg.palette.backgroundSection;
33
+ }
34
+ if ($isActive) {
35
+ return theme.stigg.palette.primaryLight;
35
36
  }
36
37
  return 'transparent';
37
38
  }};
@@ -49,13 +49,16 @@ type BillingPeriodPickerProps = {
49
49
  checkoutLocalization: CheckoutLocalization;
50
50
  };
51
51
 
52
- export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPeriodPickerProps) => {
52
+ export function BillingPeriodPicker({ plan, checkoutLocalization }: BillingPeriodPickerProps) {
53
53
  const [monthlyPrices, annualPrices] = partition(
54
54
  plan?.pricePoints,
55
55
  (price) => price.billingPeriod === BillingPeriod.Monthly,
56
56
  );
57
57
 
58
58
  const hasBothBillingPeriods = !!monthlyPrices?.length && !!annualPrices?.length;
59
+ if (!hasBothBillingPeriods) {
60
+ return null;
61
+ }
59
62
 
60
63
  return (
61
64
  <BillingPeriodPickerContainer>
@@ -81,4 +84,4 @@ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPerio
81
84
  </BillingPeriodOptions>
82
85
  </BillingPeriodPickerContainer>
83
86
  );
84
- };
87
+ }