@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
|
@@ -30,8 +30,9 @@ export const BillingPeriodButton = styled.button<{
|
|
|
30
30
|
background: ${({ theme, $isActive, $isOnlyBillingPeriod }) => {
|
|
31
31
|
if ($isOnlyBillingPeriod) {
|
|
32
32
|
return 'transparent';
|
|
33
|
-
}
|
|
34
|
-
|
|
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
|
|
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>
|
|
@@ -64,21 +67,21 @@ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPerio
|
|
|
64
67
|
</Typography>
|
|
65
68
|
|
|
66
69
|
<BillingPeriodOptions>
|
|
67
|
-
{!!
|
|
70
|
+
{!!monthlyPrices?.length && (
|
|
68
71
|
<BillingPeriodOption
|
|
69
|
-
key={BillingPeriod.
|
|
70
|
-
billingPeriod={BillingPeriod.
|
|
72
|
+
key={BillingPeriod.Monthly}
|
|
73
|
+
billingPeriod={BillingPeriod.Monthly}
|
|
71
74
|
isOnlyBillingPeriod={!hasBothBillingPeriods}
|
|
72
75
|
/>
|
|
73
76
|
)}
|
|
74
|
-
{!!
|
|
77
|
+
{!!annualPrices?.length && (
|
|
75
78
|
<BillingPeriodOption
|
|
76
|
-
key={BillingPeriod.
|
|
77
|
-
billingPeriod={BillingPeriod.
|
|
79
|
+
key={BillingPeriod.Annually}
|
|
80
|
+
billingPeriod={BillingPeriod.Annually}
|
|
78
81
|
isOnlyBillingPeriod={!hasBothBillingPeriods}
|
|
79
82
|
/>
|
|
80
83
|
)}
|
|
81
84
|
</BillingPeriodOptions>
|
|
82
85
|
</BillingPeriodPickerContainer>
|
|
83
86
|
);
|
|
84
|
-
}
|
|
87
|
+
}
|
|
@@ -10,10 +10,11 @@ import { useChargesSort } from '../../../hooks/useChargeSort';
|
|
|
10
10
|
import { calculateUnitQuantityText } from '../../../paywall/utils/calculateUnitQuantityText';
|
|
11
11
|
import { currencyPriceFormatter } from '../../../utils/currencyUtils';
|
|
12
12
|
import { InputField } from '../../components';
|
|
13
|
-
import { usePlanStepModel, useProgressBarModel } from '../../hooks';
|
|
13
|
+
import { useCheckoutModel, usePlanStepModel, useProgressBarModel } from '../../hooks';
|
|
14
14
|
import { TiersSelectContainer } from '../../../common/TiersSelectContainer';
|
|
15
|
-
import {
|
|
15
|
+
import { getTierByQuantity } from '../../../utils/priceTierUtils';
|
|
16
16
|
import { getValidPriceQuantity } from '../../../utils/priceUtils';
|
|
17
|
+
import { getFeatureDisplayNameText } from '../../../utils/getFeatureName';
|
|
17
18
|
|
|
18
19
|
export type UsePlanStepModel = ReturnType<typeof usePlanStepModel>;
|
|
19
20
|
|
|
@@ -31,6 +32,19 @@ const StyledPlanCharge = styled.div`
|
|
|
31
32
|
margin-top: 16px;
|
|
32
33
|
`;
|
|
33
34
|
|
|
35
|
+
const getValidationText = (charge: Price, quantity?: number) => {
|
|
36
|
+
const { minUnitQuantity, maxUnitQuantity } = charge;
|
|
37
|
+
if (!quantity || quantity < (minUnitQuantity || 1)) {
|
|
38
|
+
return `Minimum ${minUnitQuantity || 1}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (maxUnitQuantity && quantity > maxUnitQuantity) {
|
|
42
|
+
return `Maximum ${maxUnitQuantity}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return '';
|
|
46
|
+
};
|
|
47
|
+
|
|
34
48
|
export function PlanCharge({
|
|
35
49
|
charge,
|
|
36
50
|
isValid,
|
|
@@ -47,14 +61,22 @@ export function PlanCharge({
|
|
|
47
61
|
const featureId = charge.feature?.featureId;
|
|
48
62
|
const isBaseCharge = !featureId;
|
|
49
63
|
const isPayAsYouGo = charge.pricingModel === BillingModel.UsageBased;
|
|
50
|
-
const displayName = isBaseCharge
|
|
64
|
+
const displayName = isBaseCharge
|
|
65
|
+
? 'Base charge'
|
|
66
|
+
: getFeatureDisplayNameText(charge.feature?.displayName || '', charge.feature?.units, charge.feature?.unitsPlural);
|
|
51
67
|
const hasQuantityRestrictions = !!(charge?.minUnitQuantity || charge?.maxUnitQuantity);
|
|
52
68
|
|
|
53
69
|
const handleQuantityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
54
70
|
if (isBaseCharge || !featureId) return;
|
|
55
71
|
|
|
56
|
-
const
|
|
57
|
-
|
|
72
|
+
const { minUnitQuantity, maxUnitQuantity } = charge;
|
|
73
|
+
const value = event?.target?.value ? Number(event?.target?.value) : null;
|
|
74
|
+
if (
|
|
75
|
+
!value ||
|
|
76
|
+
value <= 0 ||
|
|
77
|
+
(minUnitQuantity && value < minUnitQuantity) ||
|
|
78
|
+
(maxUnitQuantity && value > maxUnitQuantity)
|
|
79
|
+
) {
|
|
58
80
|
onValidationChange({ featureId, isValid: false });
|
|
59
81
|
// Reset the input value to null
|
|
60
82
|
// @ts-ignore
|
|
@@ -74,22 +96,22 @@ export function PlanCharge({
|
|
|
74
96
|
amount: charge.amount!,
|
|
75
97
|
currency: charge.currency,
|
|
76
98
|
locale: 'en-us',
|
|
99
|
+
minimumFractionDigits: 2,
|
|
77
100
|
});
|
|
78
101
|
|
|
79
102
|
chargeRow = `${formattedAmount}`;
|
|
80
103
|
if (isPayAsYouGo) {
|
|
81
104
|
chargeRow += ' / unit';
|
|
82
105
|
}
|
|
83
|
-
} else if (charge.isTieredPrice) {
|
|
106
|
+
} else if (charge.isTieredPrice && featureId) {
|
|
84
107
|
const tier = getTierByQuantity(charge.tiers!, billableFeature!.quantity || 1);
|
|
85
108
|
chargeRow = (
|
|
86
109
|
<TiersSelectContainer
|
|
87
110
|
componentId={`${featureId}-tiers`}
|
|
88
111
|
tiers={charge.tiers}
|
|
89
|
-
tierUnits={getPriceFeatureUnit(charge)}
|
|
90
112
|
selectedTier={tier}
|
|
91
113
|
handleTierChange={(tier: PriceTierFragment) => {
|
|
92
|
-
setBillableFeature(featureId
|
|
114
|
+
setBillableFeature(featureId, tier.upTo);
|
|
93
115
|
}}
|
|
94
116
|
/>
|
|
95
117
|
);
|
|
@@ -99,13 +121,10 @@ export function PlanCharge({
|
|
|
99
121
|
sx={{ width: 120 }}
|
|
100
122
|
id={`${featureId}-input`}
|
|
101
123
|
type="number"
|
|
102
|
-
InputProps={
|
|
103
|
-
hasQuantityRestrictions ? { inputProps: { min: charge.minUnitQuantity, max: charge.maxUnitQuantity } } : {}
|
|
104
|
-
}
|
|
105
124
|
error={!isValid}
|
|
106
|
-
helperText={!isValid ?
|
|
125
|
+
helperText={!isValid ? getValidationText(charge, billableFeature?.quantity) : undefined}
|
|
107
126
|
FormHelperTextProps={{ sx: { margin: '4px' } }}
|
|
108
|
-
value={billableFeature?.quantity ??
|
|
127
|
+
value={billableFeature?.quantity ?? ''}
|
|
109
128
|
onChange={handleQuantityChange}
|
|
110
129
|
onWheel={(e: React.WheelEvent<HTMLInputElement>) => (e.target as HTMLElement).blur()}
|
|
111
130
|
/>
|
|
@@ -135,6 +154,7 @@ export function PlanCharge({
|
|
|
135
154
|
export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListProps) {
|
|
136
155
|
const { billableFeatures, setBillableFeature } = usePlanStepModel();
|
|
137
156
|
const { setIsDisabled } = useProgressBarModel();
|
|
157
|
+
const { setIsValid } = useCheckoutModel();
|
|
138
158
|
const planCharges = useChargesSort(plan?.pricePoints?.filter((p) => p.billingPeriod === billingPeriod) || []);
|
|
139
159
|
const [chargesValidation, setChargesValidation] = useState(
|
|
140
160
|
planCharges?.reduce<Record<string, boolean>>((acc, curr) => {
|
|
@@ -146,7 +166,8 @@ export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListPr
|
|
|
146
166
|
useEffect(() => {
|
|
147
167
|
const isDisabled = Object.values(chargesValidation).some((x) => !x);
|
|
148
168
|
setIsDisabled(isDisabled);
|
|
149
|
-
|
|
169
|
+
setIsValid(!isDisabled);
|
|
170
|
+
}, [chargesValidation, setIsDisabled, setIsValid]);
|
|
150
171
|
|
|
151
172
|
return (
|
|
152
173
|
<div>
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { CheckoutContainerProps } from '../../CheckoutContainer';
|
|
2
3
|
|
|
3
4
|
import { useCheckoutModel } from '../../hooks/useCheckoutModel';
|
|
4
5
|
import { usePlanStepModel } from '../../hooks/usePlanStepModel';
|
|
6
|
+
import { PlanHeader } from '../../planHeader';
|
|
5
7
|
import { BillingPeriodPicker } from './BillingPeriodPicker';
|
|
6
8
|
import { CheckoutChargeList } from './CheckoutChargeList';
|
|
7
9
|
import { CheckoutPlanContainer } from './CheckoutPlanStep.style';
|
|
8
10
|
|
|
9
|
-
export const CheckoutPlanStep = () => {
|
|
11
|
+
export const CheckoutPlanStep = ({ onChangePlan }: Pick<CheckoutContainerProps, 'onChangePlan'>) => {
|
|
10
12
|
const { checkoutState, checkoutLocalization } = useCheckoutModel();
|
|
11
13
|
const { plan } = checkoutState || {};
|
|
12
14
|
const { billingPeriod } = usePlanStepModel();
|
|
13
15
|
|
|
14
16
|
return (
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
17
|
+
<>
|
|
18
|
+
<PlanHeader allowChangePlan onChangePlan={onChangePlan} />
|
|
19
|
+
<CheckoutPlanContainer>
|
|
20
|
+
<BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
|
|
21
|
+
<CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
|
|
22
|
+
</CheckoutPlanContainer>
|
|
23
|
+
</>
|
|
19
24
|
);
|
|
20
25
|
};
|
|
@@ -4,27 +4,73 @@ import Color from 'color';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import Lottie from 'react-lottie';
|
|
6
6
|
import animationData from '../../../assets/lottie/checkout-success.json';
|
|
7
|
+
import { Typography } from '../../common/Typography';
|
|
8
|
+
import { CheckoutLocalization } from '../textOverrides';
|
|
7
9
|
|
|
8
10
|
export const ANIMATION_DURATION = 5000;
|
|
9
11
|
|
|
10
|
-
const BACKGROUND_COLOR = Color('#0b2f7a').alpha(0.3).toString();
|
|
11
|
-
|
|
12
12
|
const CheckoutSuccessContainer = styled(Box)`
|
|
13
|
+
@keyframes blurFade {
|
|
14
|
+
0% {
|
|
15
|
+
background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0).toString()};
|
|
16
|
+
backdrop-filter: blur(0px);
|
|
17
|
+
}
|
|
18
|
+
100% {
|
|
19
|
+
background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0.9).toString()};
|
|
20
|
+
backdrop-filter: blur(6.5px);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
position: absolute;
|
|
14
25
|
top: 0;
|
|
15
26
|
left: 0;
|
|
16
27
|
bottom: 0;
|
|
17
28
|
right: 0;
|
|
18
|
-
|
|
29
|
+
z-index: 5;
|
|
30
|
+
background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0.9).toString()};
|
|
31
|
+
animation: blurFade 2s ease-in forwards;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
|
|
19
36
|
* rect {
|
|
20
37
|
fill: transparent;
|
|
21
38
|
}
|
|
39
|
+
|
|
40
|
+
& path {
|
|
41
|
+
stroke: ${({ theme }) => theme.stigg.palette.primary};
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const CheckoutSuccessText = styled(Typography)`
|
|
46
|
+
@keyframes fadeIn {
|
|
47
|
+
0% {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
}
|
|
50
|
+
75% {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
}
|
|
53
|
+
100% {
|
|
54
|
+
opacity: 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
align-self: center;
|
|
59
|
+
animation: fadeIn 5s ease-in forwards;
|
|
22
60
|
`;
|
|
23
61
|
|
|
24
|
-
export function CheckoutSuccess() {
|
|
62
|
+
export function CheckoutSuccess({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) {
|
|
25
63
|
return (
|
|
26
|
-
<CheckoutSuccessContainer>
|
|
27
|
-
<Lottie
|
|
64
|
+
<CheckoutSuccessContainer className="stigg-checkout-success-container">
|
|
65
|
+
<Lottie
|
|
66
|
+
width={350}
|
|
67
|
+
height="auto"
|
|
68
|
+
isClickToPauseDisabled
|
|
69
|
+
options={{ loop: false, autoplay: true, animationData }}
|
|
70
|
+
/>
|
|
71
|
+
<CheckoutSuccessText variant="h1" color="primary.main">
|
|
72
|
+
{checkoutLocalization.summary.checkoutSuccessText}
|
|
73
|
+
</CheckoutSuccessText>
|
|
28
74
|
</CheckoutSuccessContainer>
|
|
29
75
|
);
|
|
30
76
|
}
|
|
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import partition from 'lodash/partition';
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
4
|
import { Box, CircularProgress, Divider, Grid, Paper } from '@mui/material';
|
|
5
|
-
import { BillingModel
|
|
5
|
+
import { BillingModel } from '@stigg/js-client-sdk';
|
|
6
6
|
import { PoweredByStigg } from '../../common/PoweredByStigg';
|
|
7
7
|
import { Typography } from '../../common/Typography';
|
|
8
8
|
import { useChargesSort } from '../../hooks/useChargeSort';
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
AppliedCreditsLineItem,
|
|
24
24
|
BilledPriceLineItem,
|
|
25
25
|
DiscountLineItem,
|
|
26
|
+
FreeChargeLineItem,
|
|
26
27
|
LineItemContainer,
|
|
27
28
|
LineItemRow,
|
|
28
29
|
TaxLineItem,
|
|
@@ -31,6 +32,7 @@ import { WithSkeleton } from './components/WithSkeleton';
|
|
|
31
32
|
import { Icon } from '../../common/Icon';
|
|
32
33
|
import { CheckoutLocalization } from '../textOverrides';
|
|
33
34
|
import { CheckoutSuccess } from './CheckoutSuccess';
|
|
35
|
+
import { getFeatureDisplayNameText } from '../../utils/getFeatureName';
|
|
34
36
|
|
|
35
37
|
export const SummaryCard = styled(Paper)`
|
|
36
38
|
border-radius: 10px;
|
|
@@ -42,53 +44,58 @@ SummaryCard.defaultProps = {
|
|
|
42
44
|
elevation: 0,
|
|
43
45
|
};
|
|
44
46
|
|
|
45
|
-
const
|
|
47
|
+
const SummaryTitle = styled(Typography)`
|
|
46
48
|
margin-bottom: 16px;
|
|
49
|
+
font-weight: 500;
|
|
47
50
|
`;
|
|
48
51
|
|
|
49
52
|
const StyledDivider = styled(Divider)`
|
|
50
53
|
margin: 16px 0;
|
|
51
54
|
`;
|
|
52
55
|
|
|
53
|
-
const SubtotalText = styled(Typography)`
|
|
54
|
-
margin-top: 24px;
|
|
55
|
-
`;
|
|
56
|
-
|
|
57
56
|
const TotalDueText = styled(Typography)`
|
|
58
57
|
margin-bottom: 8px;
|
|
59
58
|
`;
|
|
60
59
|
|
|
61
60
|
function resolveCheckoutButtonText({
|
|
62
61
|
isLastStep,
|
|
63
|
-
|
|
62
|
+
checkoutHasChanges,
|
|
63
|
+
isFreeDowngrade,
|
|
64
64
|
checkoutLocalization,
|
|
65
|
-
|
|
65
|
+
isPlanUpdate,
|
|
66
66
|
}: {
|
|
67
67
|
isLastStep?: boolean;
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
checkoutHasChanges: boolean;
|
|
69
|
+
isFreeDowngrade: boolean;
|
|
70
70
|
checkoutLocalization: CheckoutLocalization;
|
|
71
|
+
isPlanUpdate?: boolean;
|
|
71
72
|
}) {
|
|
72
73
|
if (!isLastStep) {
|
|
73
74
|
return checkoutLocalization.checkoutButton.nextText;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
if (
|
|
77
|
-
return checkoutLocalization.checkoutButton.
|
|
77
|
+
if (!checkoutHasChanges) {
|
|
78
|
+
return checkoutLocalization.checkoutButton.noChangesText;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isPlanUpdate) {
|
|
82
|
+
return checkoutLocalization.checkoutButton.updateText;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
if (
|
|
81
|
-
return checkoutLocalization.checkoutButton.
|
|
85
|
+
if (isFreeDowngrade) {
|
|
86
|
+
return checkoutLocalization.checkoutButton.downgradeToFreeText;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
return checkoutLocalization.checkoutButton.
|
|
89
|
+
return checkoutLocalization.checkoutButton.upgradeText;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
export const CheckoutSummary = ({
|
|
88
93
|
onCheckout,
|
|
89
94
|
onCheckoutCompleted,
|
|
90
95
|
disablePromotionCode,
|
|
96
|
+
disableSuccessAnimation,
|
|
91
97
|
isFreeDowngrade,
|
|
98
|
+
onMockCheckoutPreview,
|
|
92
99
|
}: CheckoutContainerProps & { isFreeDowngrade: boolean }) => {
|
|
93
100
|
const [isCheckoutCompletedSuccessfully, setIsCheckoutCompletedSuccessfully] = useState(false);
|
|
94
101
|
const { setErrorMessage } = usePaymentStepModel();
|
|
@@ -103,9 +110,10 @@ export const CheckoutSummary = ({
|
|
|
103
110
|
const [baseCharge] = baseCharges || [];
|
|
104
111
|
const isLastStep = isFreeDowngrade || (progressBar.isCheckoutComplete && progressBar.isLastStep);
|
|
105
112
|
|
|
106
|
-
const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription();
|
|
113
|
+
const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription({ onMockCheckoutPreview });
|
|
107
114
|
|
|
108
115
|
const { handleSubmit, isLoading } = useSubmit({
|
|
116
|
+
disableSuccessAnimation,
|
|
109
117
|
onCheckout,
|
|
110
118
|
onCheckoutCompleted,
|
|
111
119
|
onSuccess: () => {
|
|
@@ -140,25 +148,47 @@ export const CheckoutSummary = ({
|
|
|
140
148
|
}
|
|
141
149
|
};
|
|
142
150
|
|
|
151
|
+
const checkoutHasChanges =
|
|
152
|
+
!!subscriptionPreview && (!!subscriptionPreview.proration || !!subscriptionPreview.hasScheduledUpdates);
|
|
153
|
+
const showPromotionCodeLine = !disablePromotionCode && !isFreeDowngrade;
|
|
154
|
+
const showDiscountLine =
|
|
155
|
+
!!subscriptionPreview?.recurringSubscription &&
|
|
156
|
+
!!subscriptionPreview?.recurringSubscription?.discountDetails &&
|
|
157
|
+
!isFreeDowngrade;
|
|
158
|
+
const hasDiscounts = showPromotionCodeLine || showDiscountLine;
|
|
159
|
+
|
|
160
|
+
const hasPayAsYouGoCharges = usageCharges.some((price) => price.pricingModel === BillingModel.UsageBased);
|
|
161
|
+
const onlyPayAsYouGoCharges =
|
|
162
|
+
hasPayAsYouGoCharges &&
|
|
163
|
+
!baseCharge &&
|
|
164
|
+
usageCharges.every((price) => price.pricingModel === BillingModel.UsageBased);
|
|
165
|
+
|
|
166
|
+
const baseChargeLabel =
|
|
167
|
+
typeof checkoutLocalization.summary.baseChargeText === 'function'
|
|
168
|
+
? checkoutLocalization.summary.baseChargeText({ billingPeriod: subscription.billingPeriod })
|
|
169
|
+
: checkoutLocalization.summary.baseChargeText;
|
|
170
|
+
|
|
143
171
|
return (
|
|
144
172
|
<Box flex={1}>
|
|
145
173
|
<SummaryCard>
|
|
146
|
-
<
|
|
174
|
+
<SummaryTitle variant="h6">{checkoutLocalization.summary.title}</SummaryTitle>
|
|
175
|
+
|
|
176
|
+
<Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
|
|
177
|
+
<Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
|
|
178
|
+
{checkoutLocalization.summary.planName({ plan: plan! })}
|
|
179
|
+
</Typography>
|
|
180
|
+
<StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
|
|
181
|
+
</Grid>
|
|
147
182
|
|
|
148
183
|
{baseCharge && (
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</Typography>
|
|
156
|
-
<Typography variant="body1" color="secondary">
|
|
157
|
-
{currencyPriceFormatter({ amount: baseCharge.amount!, currency: baseCharge.currency })}
|
|
158
|
-
</Typography>
|
|
159
|
-
</LineItemRow>
|
|
160
|
-
</LineItemContainer>
|
|
184
|
+
<BilledPriceLineItem
|
|
185
|
+
checkoutLocalization={checkoutLocalization}
|
|
186
|
+
label={baseChargeLabel}
|
|
187
|
+
quantity={1}
|
|
188
|
+
price={baseCharge}
|
|
189
|
+
/>
|
|
161
190
|
)}
|
|
191
|
+
{!baseCharge && isFreeDowngrade ? <FreeChargeLineItem label={baseChargeLabel} /> : null}
|
|
162
192
|
|
|
163
193
|
{usageCharges.map((price) => {
|
|
164
194
|
const priceBillableFeature = subscription.billableFeatures?.find(
|
|
@@ -167,8 +197,13 @@ export const CheckoutSummary = ({
|
|
|
167
197
|
|
|
168
198
|
return (
|
|
169
199
|
<BilledPriceLineItem
|
|
200
|
+
checkoutLocalization={checkoutLocalization}
|
|
170
201
|
key={price.feature?.featureId}
|
|
171
|
-
label={
|
|
202
|
+
label={getFeatureDisplayNameText(
|
|
203
|
+
price.feature?.displayName || '',
|
|
204
|
+
price.feature?.units,
|
|
205
|
+
price.feature?.unitsPlural,
|
|
206
|
+
)}
|
|
172
207
|
quantity={priceBillableFeature?.quantity || 1}
|
|
173
208
|
price={price}
|
|
174
209
|
/>
|
|
@@ -177,11 +212,11 @@ export const CheckoutSummary = ({
|
|
|
177
212
|
|
|
178
213
|
{!!subscription.addons?.length && (
|
|
179
214
|
<>
|
|
180
|
-
<Grid display="flex" flexDirection="row" alignItems="center" marginY={
|
|
215
|
+
<Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
|
|
181
216
|
<Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
|
|
182
|
-
{checkoutLocalization.addonsSectionTitle}
|
|
217
|
+
{checkoutLocalization.summary.addonsSectionTitle}
|
|
183
218
|
</Typography>
|
|
184
|
-
<StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1 }} />
|
|
219
|
+
<StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
|
|
185
220
|
</Grid>
|
|
186
221
|
{subscription.addons.map((addon) => {
|
|
187
222
|
const addonPrice = addon.addon.pricePoints?.find(
|
|
@@ -194,6 +229,7 @@ export const CheckoutSummary = ({
|
|
|
194
229
|
|
|
195
230
|
return (
|
|
196
231
|
<BilledPriceLineItem
|
|
232
|
+
checkoutLocalization={checkoutLocalization}
|
|
197
233
|
key={addon?.addon?.id}
|
|
198
234
|
label={addon.addon.displayName}
|
|
199
235
|
quantity={addonQuantity}
|
|
@@ -204,36 +240,93 @@ export const CheckoutSummary = ({
|
|
|
204
240
|
</>
|
|
205
241
|
)}
|
|
206
242
|
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
{
|
|
212
|
-
</
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
<StyledDivider className="stigg-checkout-summary-divider" />
|
|
243
|
+
{!hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
|
|
244
|
+
{hasDiscounts && (
|
|
245
|
+
<Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
|
|
246
|
+
<Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
|
|
247
|
+
{checkoutLocalization.summary.discountsSectionTitle}
|
|
248
|
+
</Typography>
|
|
249
|
+
<StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
|
|
250
|
+
</Grid>
|
|
251
|
+
)}
|
|
217
252
|
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
253
|
+
{showPromotionCodeLine && (
|
|
254
|
+
<PromotionCodeSection
|
|
255
|
+
disabled={isLoading || isFetchingSubscriptionPreview}
|
|
256
|
+
checkoutLocalization={checkoutLocalization}
|
|
257
|
+
onMockCheckoutPreview={onMockCheckoutPreview}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
221
260
|
|
|
222
|
-
|
|
223
|
-
|
|
261
|
+
{showDiscountLine && (
|
|
262
|
+
<DiscountLineItem
|
|
263
|
+
subscriptionPreview={subscriptionPreview!}
|
|
264
|
+
isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
|
|
265
|
+
checkoutLocalization={checkoutLocalization}
|
|
266
|
+
/>
|
|
224
267
|
)}
|
|
225
268
|
|
|
226
|
-
<
|
|
227
|
-
subscriptionPreview={subscriptionPreview}
|
|
228
|
-
isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
|
|
229
|
-
/>
|
|
269
|
+
{hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
|
|
230
270
|
|
|
231
271
|
<TaxLineItem
|
|
232
|
-
|
|
272
|
+
tax={subscriptionPreview?.recurringSubscription?.tax}
|
|
273
|
+
taxDetails={subscriptionPreview?.recurringSubscription?.taxDetails}
|
|
233
274
|
isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
|
|
234
275
|
checkoutLocalization={checkoutLocalization}
|
|
235
276
|
/>
|
|
236
277
|
|
|
278
|
+
{!isFreeDowngrade ? (
|
|
279
|
+
<>
|
|
280
|
+
<LineItemRow style={{ marginTop: 16 }}>
|
|
281
|
+
<Grid display="flex" flexDirection="column" container>
|
|
282
|
+
<Grid item display="flex" justifyContent="space-between">
|
|
283
|
+
<Typography variant="h6">{checkoutLocalization.summary.totalText}</Typography>
|
|
284
|
+
<Typography variant="h6">
|
|
285
|
+
<WithSkeleton isLoading={isFetchingSubscriptionPreview}>
|
|
286
|
+
{onlyPayAsYouGoCharges ? checkoutLocalization.summary.onlyPayAsYouGoText : null}
|
|
287
|
+
{!onlyPayAsYouGoCharges && hasPayAsYouGoCharges
|
|
288
|
+
? checkoutLocalization.summary.startsAtText
|
|
289
|
+
: null}
|
|
290
|
+
{!onlyPayAsYouGoCharges
|
|
291
|
+
? currencyPriceFormatter({
|
|
292
|
+
amount: 0,
|
|
293
|
+
...subscriptionPreview?.recurringSubscription?.total,
|
|
294
|
+
minimumFractionDigits: 2,
|
|
295
|
+
})
|
|
296
|
+
: null}
|
|
297
|
+
</WithSkeleton>
|
|
298
|
+
</Typography>
|
|
299
|
+
</Grid>
|
|
300
|
+
<Grid item>
|
|
301
|
+
<Typography variant="body1" color="secondary">
|
|
302
|
+
{checkoutLocalization.summary.totalBillingPeriodText({ billingPeriod: subscription.billingPeriod })}
|
|
303
|
+
</Typography>
|
|
304
|
+
</Grid>
|
|
305
|
+
</Grid>
|
|
306
|
+
</LineItemRow>
|
|
307
|
+
<StyledDivider className="stigg-checkout-summary-divider" />
|
|
308
|
+
</>
|
|
309
|
+
) : null}
|
|
310
|
+
|
|
311
|
+
{subscriptionPreview?.subTotal && subscriptionPreview?.subTotal.amount > 0 ? (
|
|
312
|
+
<LineItemContainer>
|
|
313
|
+
<LineItemRow>
|
|
314
|
+
<Typography variant="body1" color="secondary">
|
|
315
|
+
{checkoutLocalization.summary.proratedTotalDueText}
|
|
316
|
+
</Typography>
|
|
317
|
+
<Typography variant="body1" color="secondary">
|
|
318
|
+
<WithSkeleton isLoading={isFetchingSubscriptionPreview}>
|
|
319
|
+
{currencyPriceFormatter({
|
|
320
|
+
amount: subscriptionPreview?.subTotal.amount + (subscriptionPreview?.tax?.amount || 0),
|
|
321
|
+
currency: subscriptionPreview?.subTotal.currency,
|
|
322
|
+
minimumFractionDigits: 2,
|
|
323
|
+
})}
|
|
324
|
+
</WithSkeleton>
|
|
325
|
+
</Typography>
|
|
326
|
+
</LineItemRow>
|
|
327
|
+
</LineItemContainer>
|
|
328
|
+
) : null}
|
|
329
|
+
|
|
237
330
|
<AppliedCreditsLineItem
|
|
238
331
|
subscriptionPreview={subscriptionPreview}
|
|
239
332
|
isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
|
|
@@ -242,10 +335,10 @@ export const CheckoutSummary = ({
|
|
|
242
335
|
|
|
243
336
|
<LineItemContainer>
|
|
244
337
|
<LineItemRow>
|
|
245
|
-
<TotalDueText variant="h6">{checkoutLocalization.
|
|
338
|
+
<TotalDueText variant="h6">{checkoutLocalization.summary.totalDueText}</TotalDueText>
|
|
246
339
|
<TotalDueText variant="h6">
|
|
247
340
|
<WithSkeleton isLoading={isFetchingSubscriptionPreview}>
|
|
248
|
-
{currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total })}
|
|
341
|
+
{currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total, minimumFractionDigits: 2 })}
|
|
249
342
|
</WithSkeleton>
|
|
250
343
|
</TotalDueText>
|
|
251
344
|
</LineItemRow>
|
|
@@ -257,12 +350,18 @@ export const CheckoutSummary = ({
|
|
|
257
350
|
subscriptionPreview={subscriptionPreview}
|
|
258
351
|
isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
|
|
259
352
|
checkoutLocalization={checkoutLocalization}
|
|
353
|
+
billingPeriod={subscription.billingPeriod}
|
|
260
354
|
/>
|
|
261
355
|
|
|
262
356
|
<Button
|
|
263
357
|
$success={isCheckoutCompletedSuccessfully}
|
|
264
|
-
$error={isLastStep &&
|
|
265
|
-
disabled={
|
|
358
|
+
$error={isLastStep && isFreeDowngrade}
|
|
359
|
+
disabled={
|
|
360
|
+
isLoading ||
|
|
361
|
+
isFetchingSubscriptionPreview ||
|
|
362
|
+
progressBar.progressBarState.isDisabled ||
|
|
363
|
+
(isLastStep && !checkoutHasChanges)
|
|
364
|
+
}
|
|
266
365
|
className="stigg-checkout-summary-cta-button"
|
|
267
366
|
sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px', height: '36px' }}
|
|
268
367
|
variant="contained"
|
|
@@ -277,7 +376,13 @@ export const CheckoutSummary = ({
|
|
|
277
376
|
) : isLoading || isFetchingSubscriptionPreview ? (
|
|
278
377
|
<CircularProgress size={20} sx={{ color: 'white' }} />
|
|
279
378
|
) : (
|
|
280
|
-
resolveCheckoutButtonText({
|
|
379
|
+
resolveCheckoutButtonText({
|
|
380
|
+
isLastStep,
|
|
381
|
+
checkoutHasChanges,
|
|
382
|
+
isFreeDowngrade,
|
|
383
|
+
checkoutLocalization,
|
|
384
|
+
isPlanUpdate: !!activeSubscription && activeSubscription?.plan.id === plan?.id,
|
|
385
|
+
})
|
|
281
386
|
)}
|
|
282
387
|
</Typography>
|
|
283
388
|
</Button>
|
|
@@ -287,7 +392,9 @@ export const CheckoutSummary = ({
|
|
|
287
392
|
showWatermark
|
|
288
393
|
style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}
|
|
289
394
|
/>
|
|
290
|
-
{isCheckoutCompletedSuccessfully &&
|
|
395
|
+
{!disableSuccessAnimation && isCheckoutCompletedSuccessfully && (
|
|
396
|
+
<CheckoutSuccess checkoutLocalization={checkoutLocalization} />
|
|
397
|
+
)}
|
|
291
398
|
</Box>
|
|
292
399
|
);
|
|
293
400
|
};
|