@stigg/react-sdk 4.4.0-beta.5 → 4.4.0-beta.7
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/components/Button.d.ts +0 -1
- package/dist/components/checkout/components/ChangePlanButton.d.ts +8 -0
- package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +6 -2
- package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +2 -2
- package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +1 -0
- package/dist/react-sdk.cjs.development.js +264 -126
- 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 +277 -130
- package/dist/react-sdk.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/checkout/CheckoutContainer.style.ts +1 -0
- package/src/components/checkout/CheckoutContainer.tsx +2 -0
- package/src/components/checkout/components/Button.tsx +3 -14
- package/src/components/checkout/components/ChangePlanButton.tsx +32 -0
- package/src/components/checkout/components/DowngradeToFreeContainer.tsx +27 -7
- package/src/components/checkout/components/Skeletons.style.ts +4 -1
- package/src/components/checkout/hooks/usePreviewSubscription.ts +2 -1
- package/src/components/checkout/planHeader/PlanHeader.style.tsx +1 -1
- package/src/components/checkout/planHeader/PlanHeader.tsx +7 -15
- package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +3 -1
- package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +5 -2
- package/src/components/checkout/promotionCode/AddPromotionCode.tsx +6 -7
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +53 -9
- package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +26 -6
- package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +28 -7
- package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +5 -4
- package/src/components/checkout/summary/CheckoutSummary.tsx +7 -7
- package/src/components/checkout/summary/components/CheckoutCaptions.tsx +2 -2
- package/src/components/checkout/textOverrides.ts +1 -1
package/package.json
CHANGED
|
@@ -3,24 +3,13 @@ import React from 'react';
|
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
4
|
import { Button as MuiButton, ButtonProps, css } from '@mui/material';
|
|
5
5
|
|
|
6
|
-
export type StyledButtonProps = { $
|
|
6
|
+
export type StyledButtonProps = { $success?: boolean; $error?: boolean };
|
|
7
7
|
|
|
8
|
-
const StyledButton = styled(MuiButton)<StyledButtonProps>`
|
|
8
|
+
const StyledButton = styled(MuiButton, { shouldForwardProp: (prop) => !prop.startsWith('$') })<StyledButtonProps>`
|
|
9
9
|
border-radius: 10px;
|
|
10
10
|
text-transform: none;
|
|
11
11
|
|
|
12
|
-
${({ theme, $
|
|
13
|
-
if ($isLoading) {
|
|
14
|
-
return css`
|
|
15
|
-
background-color: ${theme.stigg.palette.primaryDark};
|
|
16
|
-
cursor: no-drop;
|
|
17
|
-
|
|
18
|
-
&:hover {
|
|
19
|
-
background-color: ${theme.stigg.palette.primaryDark};
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
}
|
|
23
|
-
|
|
12
|
+
${({ theme, $success, $error }) => {
|
|
24
13
|
if ($success) {
|
|
25
14
|
return css`
|
|
26
15
|
background-color: ${theme.stigg.palette.success};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Button } from '@mui/material';
|
|
2
|
+
import { Typography } from '../../common/Typography';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { CheckoutLocalization } from '../textOverrides';
|
|
5
|
+
import { ButtonProps } from '@mui/material/Button';
|
|
6
|
+
|
|
7
|
+
export const ChangePlanButton = ({
|
|
8
|
+
onClick,
|
|
9
|
+
checkoutLocalization,
|
|
10
|
+
size,
|
|
11
|
+
}: {
|
|
12
|
+
onClick: () => void;
|
|
13
|
+
checkoutLocalization: CheckoutLocalization;
|
|
14
|
+
size: ButtonProps['size'];
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<Button
|
|
18
|
+
className="stigg-checkout-change-plan-button"
|
|
19
|
+
sx={{ textTransform: 'none' }}
|
|
20
|
+
variant="text"
|
|
21
|
+
size={size}
|
|
22
|
+
onClick={onClick}>
|
|
23
|
+
<Typography
|
|
24
|
+
className="stigg-checkout-change-plan-button-text"
|
|
25
|
+
color="primary.main"
|
|
26
|
+
variant="body1"
|
|
27
|
+
style={{ lineHeight: '24px' }}>
|
|
28
|
+
{checkoutLocalization.changePlan}
|
|
29
|
+
</Typography>
|
|
30
|
+
</Button>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -7,6 +7,8 @@ import { CheckoutStatePlan, Subscription } from '@stigg/js-client-sdk';
|
|
|
7
7
|
import { Currency, BillingPeriod } from '@stigg/js-client-sdk';
|
|
8
8
|
import { currencyPriceFormatter } from '../../utils/currencyUtils';
|
|
9
9
|
import { CheckoutLocalization } from '../textOverrides';
|
|
10
|
+
import { CheckoutContainerProps } from '../CheckoutContainer';
|
|
11
|
+
import { ChangePlanButton } from './ChangePlanButton';
|
|
10
12
|
|
|
11
13
|
const DowngradeToFreePlansContainer = styled(Box)`
|
|
12
14
|
display: flex;
|
|
@@ -62,19 +64,37 @@ export const DowngradeToFreeContent = ({
|
|
|
62
64
|
);
|
|
63
65
|
};
|
|
64
66
|
|
|
67
|
+
type DowngradeToFreePlanProps = {
|
|
68
|
+
checkoutLocalization: CheckoutLocalization;
|
|
69
|
+
activeSubscription: Subscription;
|
|
70
|
+
freePlan: CheckoutStatePlan;
|
|
71
|
+
allowChangePlan?: boolean;
|
|
72
|
+
} & Pick<CheckoutContainerProps, 'onChangePlan'>;
|
|
73
|
+
|
|
65
74
|
export const DowngradeToFreePlan = ({
|
|
66
75
|
checkoutLocalization,
|
|
67
76
|
activeSubscription,
|
|
68
77
|
freePlan,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
allowChangePlan = false,
|
|
79
|
+
onChangePlan,
|
|
80
|
+
}: DowngradeToFreePlanProps) => {
|
|
81
|
+
let alertAction;
|
|
82
|
+
if (allowChangePlan && onChangePlan) {
|
|
83
|
+
alertAction = (
|
|
84
|
+
<ChangePlanButton
|
|
85
|
+
onClick={() => {
|
|
86
|
+
onChangePlan({ currentPlan: freePlan });
|
|
87
|
+
}}
|
|
88
|
+
checkoutLocalization={checkoutLocalization}
|
|
89
|
+
size="small"
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
return (
|
|
75
95
|
<>
|
|
76
|
-
<DowngradeToFreeAlert className="stigg-checkout-downgrade-to-free-alert" severity="info">
|
|
77
|
-
<Typography color="secondary">
|
|
96
|
+
<DowngradeToFreeAlert action={alertAction} className="stigg-checkout-downgrade-to-free-alert" severity="info">
|
|
97
|
+
<Typography span color="secondary">
|
|
78
98
|
{checkoutLocalization.downgradeToFreeAlertText({ plan: activeSubscription.plan })}
|
|
79
99
|
</Typography>
|
|
80
100
|
</DowngradeToFreeAlert>
|
|
@@ -2,7 +2,10 @@ import styled from '@emotion/styled/macro';
|
|
|
2
2
|
import { Grid } from '@mui/material';
|
|
3
3
|
import ReactSkeleton from 'react-loading-skeleton';
|
|
4
4
|
|
|
5
|
-
export const SkeletonsContainer = styled(Grid
|
|
5
|
+
export const SkeletonsContainer = styled(Grid, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{
|
|
6
|
+
$gap: number;
|
|
7
|
+
$flexDirection?: 'row' | 'column';
|
|
8
|
+
}>`
|
|
6
9
|
display: flex;
|
|
7
10
|
flex-direction: ${({ $flexDirection }) => $flexDirection || 'row'};
|
|
8
11
|
gap: ${({ $gap }) => $gap}px;
|
|
@@ -37,7 +37,8 @@ export const usePreviewSubscriptionAction = () => {
|
|
|
37
37
|
let subscriptionPreview: SubscriptionPreview | null = null;
|
|
38
38
|
let errorMessage: string | null = null;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
let isValid = !subscription.billableFeatures.some(({ quantity }) => quantity === null || quantity <= 0);
|
|
41
|
+
isValid = isValid && !estimateAddons.some(({ quantity }) => quantity === null || quantity <= 0);
|
|
41
42
|
if (!isValid) {
|
|
42
43
|
return { subscriptionPreview };
|
|
43
44
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Divider } from '@mui/material';
|
|
4
4
|
|
|
5
5
|
import { Typography } from '../../common/Typography';
|
|
6
6
|
import { useCheckoutModel } from '../hooks/useCheckoutModel';
|
|
7
7
|
import { PlanHeaderContainer, PlanPathContainer, StyledArrowRightIcon } from './PlanHeader.style';
|
|
8
8
|
import { CheckoutContainerProps } from '../CheckoutContainer';
|
|
9
|
+
import { ChangePlanButton } from '../components/ChangePlanButton';
|
|
9
10
|
|
|
10
11
|
type PlanHeaderProps = {
|
|
11
12
|
allowChangePlan?: boolean;
|
|
@@ -35,22 +36,13 @@ export function PlanHeader({ allowChangePlan = false, onChangePlan }: PlanHeader
|
|
|
35
36
|
</PlanPathContainer>
|
|
36
37
|
|
|
37
38
|
{allowChangePlan && onChangePlan && (
|
|
38
|
-
<
|
|
39
|
-
className="stigg-checkout-change-plan-button"
|
|
40
|
-
sx={{ textTransform: 'none' }}
|
|
41
|
-
variant="text"
|
|
42
|
-
size="medium"
|
|
39
|
+
<ChangePlanButton
|
|
43
40
|
onClick={() => {
|
|
44
41
|
onChangePlan({ currentPlan: plan });
|
|
45
|
-
}}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
style={{ lineHeight: '24px' }}
|
|
50
|
-
variant="body1">
|
|
51
|
-
{checkoutLocalization.changePlan}
|
|
52
|
-
</Typography>
|
|
53
|
-
</Button>
|
|
42
|
+
}}
|
|
43
|
+
checkoutLocalization={checkoutLocalization}
|
|
44
|
+
size="medium"
|
|
45
|
+
/>
|
|
54
46
|
)}
|
|
55
47
|
</PlanHeaderContainer>
|
|
56
48
|
<Divider className="stigg-checkout-plan-header-divider" />
|
|
@@ -4,7 +4,9 @@ import Color from 'color';
|
|
|
4
4
|
|
|
5
5
|
import { Icon } from '../../common/Icon';
|
|
6
6
|
|
|
7
|
-
export const StyledProgress = styled(LinearProgress
|
|
7
|
+
export const StyledProgress = styled(LinearProgress, { shouldForwardProp: (prop) => !prop.startsWith('$') })<{
|
|
8
|
+
$disabled?: boolean;
|
|
9
|
+
}>(({ theme, $disabled }) => ({
|
|
8
10
|
[`&.${linearProgressClasses.root}`]: {
|
|
9
11
|
borderRadius: theme.stigg.border.radius,
|
|
10
12
|
backgroundColor: theme.stigg.palette.outlinedBorder,
|
|
@@ -14,12 +14,15 @@ export const CheckoutProgressBar = () => {
|
|
|
14
14
|
const progress = ((activeStep + 1) * 100) / steps.length;
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<Box sx={{ width: '100%',
|
|
17
|
+
<Box sx={{ width: '100%', mb: 3 }}>
|
|
18
18
|
<StyledProgress variant="determinate" value={progress} $disabled={readOnly} />
|
|
19
19
|
<Grid container display="flex">
|
|
20
20
|
{steps.map(({ key, label }, index) => {
|
|
21
21
|
const isCompleted = completedSteps.includes(index);
|
|
22
|
-
const isDisabled =
|
|
22
|
+
const isDisabled =
|
|
23
|
+
readOnly ||
|
|
24
|
+
(index > activeStep && !isCompleted && !completedSteps.includes(index - 1)) ||
|
|
25
|
+
(activeStep !== index && progressBarState.isDisabled);
|
|
23
26
|
const checkedIcon: Icons = isDisabled ? 'OutlinedCheckedCircleDisabled' : 'OutlinedCheckedCircle';
|
|
24
27
|
|
|
25
28
|
return (
|
|
@@ -64,13 +64,12 @@ export const AddPromotionCode = ({ checkoutLocalization }: { checkoutLocalizatio
|
|
|
64
64
|
inputProps={{ maxLength: 20 }}
|
|
65
65
|
InputProps={{
|
|
66
66
|
endAdornment: (
|
|
67
|
-
<CouponCodeAddButton
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{isLoading ? <CircularProgress size={18} /> : <Icon style={{ display: 'flex' }} icon="ArrowForward" />}
|
|
67
|
+
<CouponCodeAddButton variant="contained" disabled={isLoading} onClick={handlePromotionCode}>
|
|
68
|
+
{isLoading ? (
|
|
69
|
+
<CircularProgress size={18} sx={{ color: 'white' }} />
|
|
70
|
+
) : (
|
|
71
|
+
<Icon style={{ display: 'flex' }} icon="ArrowForward" />
|
|
72
|
+
)}
|
|
74
73
|
</CouponCodeAddButton>
|
|
75
74
|
),
|
|
76
75
|
}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Grid } from '@mui/material';
|
|
4
4
|
import { Addon, BillingPeriod, SubscriptionAddon } from '@stigg/js-client-sdk';
|
|
@@ -11,7 +11,7 @@ import { useAddonsStepModel } from '../../hooks/useAddonsStepModel';
|
|
|
11
11
|
import { usePlanStepModel } from '../../hooks/usePlanStepModel';
|
|
12
12
|
import { AddonListItemContainer, CheckoutAddonsContainer, TrashButton } from './CheckoutAddonsStep.style';
|
|
13
13
|
import { CheckoutLocalization } from '../../textOverrides';
|
|
14
|
-
import { useCheckoutModel } from '../../hooks';
|
|
14
|
+
import { useCheckoutModel, useProgressBarModel } from '../../hooks';
|
|
15
15
|
|
|
16
16
|
type UseAddonsStepModel = ReturnType<typeof useAddonsStepModel>;
|
|
17
17
|
|
|
@@ -23,6 +23,8 @@ type AddonListItemProps = {
|
|
|
23
23
|
setAddon: UseAddonsStepModel['setAddon'];
|
|
24
24
|
removeAddon: UseAddonsStepModel['removeAddon'];
|
|
25
25
|
checkoutLocalization: CheckoutLocalization;
|
|
26
|
+
onAddonsValidationChange: (params: { addonId: string; isValid: boolean }) => void;
|
|
27
|
+
isValid: boolean;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
function AddonListItem({
|
|
@@ -33,6 +35,8 @@ function AddonListItem({
|
|
|
33
35
|
setAddon,
|
|
34
36
|
removeAddon,
|
|
35
37
|
checkoutLocalization,
|
|
38
|
+
onAddonsValidationChange,
|
|
39
|
+
isValid,
|
|
36
40
|
}: AddonListItemProps) {
|
|
37
41
|
const addonPrice = addon.pricePoints.find((pricePoint) => pricePoint.billingPeriod === billingPeriod);
|
|
38
42
|
const isAdded = !!addonState;
|
|
@@ -41,12 +45,27 @@ function AddonListItem({
|
|
|
41
45
|
(!initialAddonState && !!addonState) ||
|
|
42
46
|
(!!initialAddonState && !addonState);
|
|
43
47
|
|
|
44
|
-
const handleQuantityChange = (quantity
|
|
45
|
-
|
|
48
|
+
const handleQuantityChange = (quantity: number | null) => {
|
|
49
|
+
if (!quantity || quantity <= 0) {
|
|
50
|
+
onAddonsValidationChange({ addonId: addon.id, isValid: false });
|
|
51
|
+
// Reset the input value to null
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
setAddon(addon, quantity ?? null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onAddonsValidationChange({ addonId: addon.id, isValid: true });
|
|
58
|
+
setAddon(addon, quantity);
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
const handleUndo = () => {
|
|
49
|
-
|
|
62
|
+
if (initialAddonState) {
|
|
63
|
+
setAddon(addon, initialAddonState.quantity);
|
|
64
|
+
} else {
|
|
65
|
+
removeAddon(addon.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onAddonsValidationChange({ addonId: addon.id, isValid: true });
|
|
50
69
|
};
|
|
51
70
|
|
|
52
71
|
return (
|
|
@@ -78,16 +97,24 @@ function AddonListItem({
|
|
|
78
97
|
id={`${addon.id}-input`}
|
|
79
98
|
type="number"
|
|
80
99
|
sx={{ width: 120, marginX: 2 }}
|
|
81
|
-
value={addonState?.quantity
|
|
82
|
-
|
|
100
|
+
value={addonState?.quantity ?? ''}
|
|
101
|
+
error={!isValid}
|
|
102
|
+
helperText={!isValid ? 'Minimum 1' : undefined}
|
|
103
|
+
FormHelperTextProps={{ sx: { margin: '4px' } }}
|
|
104
|
+
onChange={(event) => handleQuantityChange(event?.target?.value ? Number(event?.target?.value) : null)}
|
|
83
105
|
/>
|
|
84
|
-
<TrashButton
|
|
106
|
+
<TrashButton
|
|
107
|
+
color="error"
|
|
108
|
+
onClick={() => {
|
|
109
|
+
removeAddon(addon.id);
|
|
110
|
+
onAddonsValidationChange({ addonId: addon.id, isValid: true });
|
|
111
|
+
}}>
|
|
85
112
|
<Icon icon="Trash" style={{ display: 'flex' }} />
|
|
86
113
|
</TrashButton>
|
|
87
114
|
</>
|
|
88
115
|
)}
|
|
89
116
|
{!isAdded && (
|
|
90
|
-
<Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange()}>
|
|
117
|
+
<Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange(1)}>
|
|
91
118
|
{checkoutLocalization.addAddonText}
|
|
92
119
|
</Button>
|
|
93
120
|
)}
|
|
@@ -99,13 +126,26 @@ function AddonListItem({
|
|
|
99
126
|
export function CheckoutAddonsStep() {
|
|
100
127
|
const { checkoutLocalization } = useCheckoutModel();
|
|
101
128
|
const { billingPeriod } = usePlanStepModel();
|
|
129
|
+
const { setIsDisabled } = useProgressBarModel();
|
|
102
130
|
const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
|
|
131
|
+
const [addonsValidation, setAddonsValidation] = useState(
|
|
132
|
+
availableAddons?.reduce<Record<string, boolean>>((acc, curr) => {
|
|
133
|
+
acc[curr.id] = true;
|
|
134
|
+
return acc;
|
|
135
|
+
}, {}) || {},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const isDisabled = Object.values(addonsValidation).some((x) => !x);
|
|
140
|
+
setIsDisabled(isDisabled);
|
|
141
|
+
}, [addonsValidation, setIsDisabled]);
|
|
103
142
|
|
|
104
143
|
return (
|
|
105
144
|
<CheckoutAddonsContainer container>
|
|
106
145
|
{availableAddons?.map((addon) => {
|
|
107
146
|
const addonState = addons.find((x) => x.addon.id === addon.id);
|
|
108
147
|
const initialAddonState = initialAddons?.find((x) => x.addon.id === addon.id);
|
|
148
|
+
const isValid = addonsValidation[addon.id];
|
|
109
149
|
|
|
110
150
|
return (
|
|
111
151
|
<AddonListItem
|
|
@@ -117,6 +157,10 @@ export function CheckoutAddonsStep() {
|
|
|
117
157
|
setAddon={setAddon}
|
|
118
158
|
removeAddon={removeAddon}
|
|
119
159
|
checkoutLocalization={checkoutLocalization}
|
|
160
|
+
onAddonsValidationChange={({ addonId, isValid }: { addonId: string; isValid: boolean }) =>
|
|
161
|
+
setAddonsValidation({ ...addonsValidation, [addonId]: isValid })
|
|
162
|
+
}
|
|
163
|
+
isValid={isValid}
|
|
120
164
|
/>
|
|
121
165
|
);
|
|
122
166
|
})}
|
|
@@ -5,19 +5,39 @@ export const BillingPeriodPickerContainer = styled(Box)`
|
|
|
5
5
|
margin: 16px 0;
|
|
6
6
|
`;
|
|
7
7
|
|
|
8
|
-
export const BillingPeriodButton = styled.button<{
|
|
9
|
-
|
|
8
|
+
export const BillingPeriodButton = styled.button<{
|
|
9
|
+
$isActive?: boolean;
|
|
10
|
+
$disabled?: boolean;
|
|
11
|
+
$isOnlyBillingPeriod?: boolean;
|
|
12
|
+
}>`
|
|
13
|
+
cursor: ${({ $disabled, $isOnlyBillingPeriod }) =>
|
|
14
|
+
$disabled ? 'default' : $isOnlyBillingPeriod ? 'default' : 'pointer'};
|
|
10
15
|
flex: 1;
|
|
11
16
|
display: flex;
|
|
12
17
|
align-items: center;
|
|
13
18
|
justify-content: flex-start;
|
|
14
|
-
padding:
|
|
19
|
+
padding: 2px 8px;
|
|
15
20
|
border-radius: 10px;
|
|
16
|
-
border: ${({ theme, $isActive }) =>
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
border: ${({ theme, $isActive, $isOnlyBillingPeriod }) => {
|
|
22
|
+
let borderColor = theme.stigg.palette.outlinedBorder;
|
|
23
|
+
if ($isOnlyBillingPeriod) {
|
|
24
|
+
borderColor = 'transparent';
|
|
25
|
+
} else if ($isActive) {
|
|
26
|
+
borderColor = theme.stigg.palette.outlinedRestingBorder;
|
|
27
|
+
}
|
|
28
|
+
return `1px solid ${borderColor}`;
|
|
29
|
+
}};
|
|
30
|
+
background: ${({ theme, $isActive, $isOnlyBillingPeriod }) => {
|
|
31
|
+
if ($isOnlyBillingPeriod) {
|
|
32
|
+
return 'transparent';
|
|
33
|
+
} else if ($isActive) {
|
|
34
|
+
return theme.stigg.palette.backgroundSection;
|
|
35
|
+
}
|
|
36
|
+
return 'transparent';
|
|
37
|
+
}};
|
|
19
38
|
text-transform: none;
|
|
20
39
|
text-align: start;
|
|
40
|
+
height: 36px;
|
|
21
41
|
|
|
22
42
|
&.MuiButton-root {
|
|
23
43
|
padding: 0 16px 0 8px;
|
|
@@ -11,21 +11,32 @@ import { usePlanStepModel } from '../../hooks/usePlanStepModel';
|
|
|
11
11
|
import { BillingPeriodButton, BillingPeriodOptions, BillingPeriodPickerContainer } from './BillingPeriodPicker.style';
|
|
12
12
|
import { CheckoutLocalization } from '../../textOverrides';
|
|
13
13
|
|
|
14
|
-
const BillingPeriodOption = ({
|
|
14
|
+
const BillingPeriodOption = ({
|
|
15
|
+
billingPeriod,
|
|
16
|
+
isOnlyBillingPeriod,
|
|
17
|
+
}: {
|
|
18
|
+
billingPeriod: BillingPeriod;
|
|
19
|
+
isOnlyBillingPeriod: boolean;
|
|
20
|
+
}) => {
|
|
15
21
|
const { billingPeriod: selectedBillingPeriod, setBillingPeriod } = usePlanStepModel();
|
|
16
22
|
const isActive = selectedBillingPeriod === billingPeriod;
|
|
17
23
|
|
|
18
24
|
return (
|
|
19
|
-
<BillingPeriodButton
|
|
25
|
+
<BillingPeriodButton
|
|
26
|
+
onClick={() => setBillingPeriod(billingPeriod)}
|
|
27
|
+
$isActive={isActive}
|
|
28
|
+
$isOnlyBillingPeriod={isOnlyBillingPeriod}>
|
|
20
29
|
<Radio
|
|
21
30
|
checked={isActive}
|
|
22
31
|
onChange={() => setBillingPeriod(billingPeriod)}
|
|
23
32
|
value={billingPeriod}
|
|
33
|
+
disabled={isOnlyBillingPeriod}
|
|
24
34
|
inputProps={{ 'aria-label': formatBillingPeriod(billingPeriod) }}
|
|
35
|
+
sx={{ padding: 0, marginRight: '8px' }}
|
|
25
36
|
/>
|
|
26
37
|
|
|
27
38
|
<Box>
|
|
28
|
-
<Typography variant="
|
|
39
|
+
<Typography variant="body1" color="primary">
|
|
29
40
|
{formatBillingPeriod(billingPeriod)}
|
|
30
41
|
</Typography>
|
|
31
42
|
</Box>
|
|
@@ -44,6 +55,8 @@ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPerio
|
|
|
44
55
|
(price) => price.billingPeriod === BillingPeriod.Monthly,
|
|
45
56
|
);
|
|
46
57
|
|
|
58
|
+
const hasBothBillingPeriods = !!monthlyPrices?.length && !!annualPrices?.length;
|
|
59
|
+
|
|
47
60
|
return (
|
|
48
61
|
<BillingPeriodPickerContainer>
|
|
49
62
|
<Typography variant="h6" color="primary" fontWeight={FontWeight.Medium}>
|
|
@@ -51,11 +64,19 @@ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPerio
|
|
|
51
64
|
</Typography>
|
|
52
65
|
|
|
53
66
|
<BillingPeriodOptions>
|
|
54
|
-
{!!monthlyPrices?.length && (
|
|
55
|
-
<BillingPeriodOption key={BillingPeriod.Monthly} billingPeriod={BillingPeriod.Monthly} />
|
|
56
|
-
)}
|
|
57
67
|
{!!annualPrices?.length && (
|
|
58
|
-
<BillingPeriodOption
|
|
68
|
+
<BillingPeriodOption
|
|
69
|
+
key={BillingPeriod.Annually}
|
|
70
|
+
billingPeriod={BillingPeriod.Annually}
|
|
71
|
+
isOnlyBillingPeriod={!hasBothBillingPeriods}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
{!!monthlyPrices?.length && (
|
|
75
|
+
<BillingPeriodOption
|
|
76
|
+
key={BillingPeriod.Monthly}
|
|
77
|
+
billingPeriod={BillingPeriod.Monthly}
|
|
78
|
+
isOnlyBillingPeriod={!hasBothBillingPeriods}
|
|
79
|
+
/>
|
|
59
80
|
)}
|
|
60
81
|
</BillingPeriodOptions>
|
|
61
82
|
</BillingPeriodPickerContainer>
|
|
@@ -58,7 +58,7 @@ export function PlanCharge({
|
|
|
58
58
|
onValidationChange({ featureId, isValid: false });
|
|
59
59
|
// Reset the input value to null
|
|
60
60
|
// @ts-ignore
|
|
61
|
-
setBillableFeature(featureId, null);
|
|
61
|
+
setBillableFeature(featureId, value ?? null);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -96,17 +96,18 @@ export function PlanCharge({
|
|
|
96
96
|
} else {
|
|
97
97
|
chargeRow = (
|
|
98
98
|
<InputField
|
|
99
|
-
sx={{ width:
|
|
99
|
+
sx={{ width: 120 }}
|
|
100
100
|
id={`${featureId}-input`}
|
|
101
101
|
type="number"
|
|
102
102
|
InputProps={
|
|
103
103
|
hasQuantityRestrictions ? { inputProps: { min: charge.minUnitQuantity, max: charge.maxUnitQuantity } } : {}
|
|
104
104
|
}
|
|
105
105
|
error={!isValid}
|
|
106
|
-
helperText={!isValid ? '
|
|
106
|
+
helperText={!isValid ? 'Minimum 1' : undefined}
|
|
107
107
|
FormHelperTextProps={{ sx: { margin: '4px' } }}
|
|
108
|
-
value={billableFeature?.quantity
|
|
108
|
+
value={billableFeature?.quantity ?? charge.minUnitQuantity ?? ''}
|
|
109
109
|
onChange={handleQuantityChange}
|
|
110
|
+
onWheel={(e: React.WheelEvent<HTMLInputElement>) => (e.target as HTMLElement).blur()}
|
|
110
111
|
/>
|
|
111
112
|
);
|
|
112
113
|
}
|
|
@@ -190,11 +190,13 @@ export const CheckoutSummary = ({
|
|
|
190
190
|
|
|
191
191
|
if (!addonPrice) return null;
|
|
192
192
|
|
|
193
|
+
const addonQuantity = addon.quantity && addon.quantity > 0 ? addon.quantity : 1;
|
|
194
|
+
|
|
193
195
|
return (
|
|
194
196
|
<BilledPriceLineItem
|
|
195
197
|
key={addon?.addon?.id}
|
|
196
198
|
label={addon.addon.displayName}
|
|
197
|
-
quantity={
|
|
199
|
+
quantity={addonQuantity}
|
|
198
200
|
price={addonPrice}
|
|
199
201
|
/>
|
|
200
202
|
);
|
|
@@ -213,7 +215,7 @@ export const CheckoutSummary = ({
|
|
|
213
215
|
|
|
214
216
|
<StyledDivider className="stigg-checkout-summary-divider" />
|
|
215
217
|
|
|
216
|
-
{!disablePromotionCode && (
|
|
218
|
+
{!disablePromotionCode && !isFreeDowngrade && (
|
|
217
219
|
<>
|
|
218
220
|
<PromotionCodeSection checkoutLocalization={checkoutLocalization} />
|
|
219
221
|
|
|
@@ -258,13 +260,11 @@ export const CheckoutSummary = ({
|
|
|
258
260
|
/>
|
|
259
261
|
|
|
260
262
|
<Button
|
|
261
|
-
disableRipple={isLoading || progressBar.progressBarState.isDisabled}
|
|
262
|
-
$isLoading={isLoading || progressBar.progressBarState.isDisabled}
|
|
263
263
|
$success={isCheckoutCompletedSuccessfully}
|
|
264
|
-
$error={isLastStep && subscriptionPreview?.isPlanDowngrade}
|
|
265
|
-
disabled={isLoading || isFetchingSubscriptionPreview}
|
|
264
|
+
$error={isLastStep && !!subscriptionPreview?.isPlanDowngrade}
|
|
265
|
+
disabled={isLoading || isFetchingSubscriptionPreview || progressBar.progressBarState.isDisabled}
|
|
266
266
|
className="stigg-checkout-summary-cta-button"
|
|
267
|
-
sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px' }}
|
|
267
|
+
sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px', height: '36px' }}
|
|
268
268
|
variant="contained"
|
|
269
269
|
size="medium"
|
|
270
270
|
onClick={(e: any) => {
|
|
@@ -58,11 +58,11 @@ const ScheduledUpdatesCaption = ({
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
const ChargeDueTodayCaption = ({ subscriptionPreview, plan, isFetchingSubscriptionPreview }: CheckoutCaptionProps) => {
|
|
61
|
-
if (!subscriptionPreview?.total) {
|
|
61
|
+
if (!subscriptionPreview?.total || subscriptionPreview.total.amount <= 0) {
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const total = currencyPriceFormatter(subscriptionPreview
|
|
65
|
+
const total = currencyPriceFormatter(subscriptionPreview.total);
|
|
66
66
|
const usedCredits = !!subscriptionPreview.credits?.used;
|
|
67
67
|
|
|
68
68
|
return (
|
|
@@ -56,7 +56,7 @@ export function getResolvedCheckoutLocalize(
|
|
|
56
56
|
},
|
|
57
57
|
appliedCreditsTitle: 'Applied credits',
|
|
58
58
|
taxTitle: ({ taxDetails }) => `Tax (${taxDetails?.percentage}%)`,
|
|
59
|
-
downgradeToFreeAlertText: () => `We’re sorry to see you
|
|
59
|
+
downgradeToFreeAlertText: () => `We’re sorry to see you cancel your paid subscription 😭`,
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
return merge(checkoutDefaultLocalization, localizeOverride);
|