@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.
- package/dist/components/checkout/Checkout.d.ts +3 -2
- package/dist/components/checkout/CheckoutContainer.d.ts +5 -2
- package/dist/components/checkout/CheckoutProvider.d.ts +3 -2
- package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +3 -7
- package/dist/components/checkout/components/StyledArrow.d.ts +5 -0
- package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
- package/dist/components/checkout/hooks/useLoadCheckout.d.ts +3 -1
- package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +17 -8
- package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +1 -0
- package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +2 -4
- package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +2 -1
- package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +6 -2
- package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +2 -2
- package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +1 -1
- package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -1
- package/dist/components/checkout/summary/CheckoutSuccess.d.ts +4 -1
- package/dist/components/checkout/summary/CheckoutSummary.d.ts +1 -1
- package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +3 -2
- package/dist/components/checkout/summary/components/LineItems.d.ts +12 -8
- package/dist/components/checkout/textOverrides.d.ts +65 -21
- package/dist/components/checkout/theme.d.ts +0 -1
- package/dist/components/checkout/types.d.ts +7 -1
- package/dist/components/common/TiersSelectContainer.d.ts +1 -2
- package/dist/components/common/Typography.d.ts +2 -2
- package/dist/components/common/customIcons.d.ts +2 -0
- package/dist/components/customerPortal/subscriptionOverview/subscriptionView/SubscriptionView.style.d.ts +1 -1
- package/dist/components/paywall/PlanPrice.d.ts +1 -1
- package/dist/components/utils/currencyUtils.d.ts +2 -1
- package/dist/components/utils/getFeatureName.d.ts +1 -0
- package/dist/react-sdk.cjs.development.js +1153 -624
- package/dist/react-sdk.cjs.development.js.map +1 -1
- package/dist/react-sdk.cjs.production.min.js +1 -1
- package/dist/react-sdk.cjs.production.min.js.map +1 -1
- package/dist/react-sdk.esm.js +1176 -651
- package/dist/react-sdk.esm.js.map +1 -1
- package/dist/stories/mocks/checkout/consts.d.ts +11 -0
- package/dist/stories/mocks/checkout/mockCheckoutPreview.d.ts +2 -0
- package/dist/stories/mocks/checkout/mockCheckoutState.d.ts +2 -0
- package/dist/theme/getResolvedTheme.d.ts +1 -0
- package/dist/theme/types.d.ts +1 -0
- package/package.json +28 -20
- package/src/assets/coupon.svg +6 -0
- package/src/assets/pay-as-you-go-charge.svg +11 -0
- package/src/components/checkout/Checkout.tsx +5 -2
- package/src/components/checkout/CheckoutContainer.tsx +21 -12
- package/src/components/checkout/CheckoutProvider.tsx +5 -3
- package/src/components/checkout/components/DowngradeToFreeContainer.tsx +33 -36
- package/src/components/checkout/components/StyledArrow.tsx +9 -0
- package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
- package/src/components/checkout/hooks/useLoadCheckout.ts +10 -2
- package/src/components/checkout/hooks/usePlanStepModel.ts +22 -7
- package/src/components/checkout/hooks/usePreviewSubscription.ts +103 -50
- package/src/components/checkout/planHeader/PlanHeader.tsx +18 -25
- package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +11 -12
- package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +7 -6
- package/src/components/checkout/promotionCode/AddPromotionCode.tsx +32 -9
- package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +15 -11
- package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +4 -3
- package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +21 -7
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +0 -1
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +7 -4
- package/src/components/checkout/steps/payment/PaymentMethods.style.ts +4 -1
- package/src/components/checkout/steps/payment/PaymentStep.tsx +0 -1
- package/src/components/checkout/steps/payment/stripe/useSubmit.ts +8 -4
- package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +3 -2
- package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +11 -8
- package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +35 -14
- package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +10 -5
- package/src/components/checkout/summary/CheckoutSuccess.tsx +52 -6
- package/src/components/checkout/summary/CheckoutSummary.tsx +166 -59
- package/src/components/checkout/summary/components/CheckoutCaptions.tsx +63 -39
- package/src/components/checkout/summary/components/LineItems.tsx +77 -28
- package/src/components/checkout/textOverrides.ts +112 -30
- package/src/components/checkout/theme.ts +0 -4
- package/src/components/checkout/types.ts +15 -1
- package/src/components/common/Icon.tsx +4 -6
- package/src/components/common/TiersSelectContainer.tsx +7 -8
- package/src/components/common/Typography.tsx +12 -3
- package/src/components/common/customIcons.ts +2 -0
- package/src/components/common/mapExternalTheme.ts +1 -2
- package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +4 -3
- package/src/components/paywall/PlanOfferingButton.tsx +6 -8
- package/src/components/paywall/PlanPrice.tsx +14 -17
- package/src/components/utils/currencyUtils.ts +4 -2
- package/src/components/utils/getFeatureName.ts +13 -5
- package/src/stories/Checkout.stories.tsx +37 -5
- package/src/stories/CustomerPortal.stories.tsx +2 -2
- package/src/stories/mocks/checkout/consts.ts +15 -0
- package/src/stories/mocks/checkout/mockCheckoutPreview.ts +138 -0
- package/src/stories/mocks/checkout/mockCheckoutState.ts +206 -0
- package/src/theme/Theme.tsx +10 -1
- package/src/theme/getResolvedTheme.ts +1 -0
- package/src/theme/types.ts +1 -0
- package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +0 -25
- package/src/components/checkout/planHeader/PlanHeader.style.tsx +0 -23
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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 {
|
|
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.
|
|
121
|
+
subscription.billingCountryCode,
|
|
122
|
+
subscription.billingPeriod,
|
|
81
123
|
subscription.billingAddress,
|
|
82
124
|
subscription.taxPercentage,
|
|
83
|
-
|
|
125
|
+
subscription.promotionCode,
|
|
126
|
+
widgetState.isValid,
|
|
127
|
+
onMockCheckoutPreview,
|
|
84
128
|
],
|
|
85
129
|
);
|
|
86
130
|
|
|
87
131
|
return { previewSubscriptionAction };
|
|
88
132
|
};
|
|
89
133
|
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
|
18
|
-
const isPlanChanged = plan?.id !== activeSubscription?.plan.id;
|
|
16
|
+
const { plan } = checkoutState || {};
|
|
19
17
|
|
|
20
18
|
return (
|
|
21
19
|
<>
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 ? '
|
|
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 {
|
|
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 = ({
|
|
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?.
|
|
40
|
+
if (!errorMessage && subscriptionPreview?.discountDetails) {
|
|
37
41
|
persistPromotionCode(promotionCode.toUpperCase());
|
|
38
42
|
setShowInput(false);
|
|
39
|
-
} else if (
|
|
43
|
+
} else if (errorMessage) {
|
|
40
44
|
setErrorMessage(errorMessage);
|
|
41
45
|
}
|
|
42
46
|
setIsLoading(false);
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
if (!showInput) {
|
|
46
|
-
return
|
|
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
|
|
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={{
|
|
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=
|
|
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
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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)`
|
|
@@ -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
|
-
|
|
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 });
|