@stigg/react-sdk 4.4.0-beta.0 → 4.4.0-beta.10
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 +1 -1
- package/dist/components/checkout/CheckoutContainer.d.ts +7 -2
- package/dist/components/checkout/CheckoutProvider.d.ts +3 -1
- 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 +32 -0
- package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
- package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +8 -2
- package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +10 -1
- package/dist/components/checkout/hooks/useProgressBarModel.d.ts +3 -0
- package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +2 -1
- package/dist/components/checkout/index.d.ts +2 -0
- package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +3 -2
- package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +3 -2
- package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -1
- package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -1
- package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +4 -0
- package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +4 -3
- package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +1 -0
- package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +6 -1
- package/dist/components/checkout/summary/CheckoutSuccess.d.ts +4 -1
- package/dist/components/checkout/summary/CheckoutSummary.d.ts +3 -1
- package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +2 -1
- package/dist/components/checkout/summary/components/LineItems.d.ts +2 -2
- package/dist/components/checkout/textOverrides.d.ts +7 -3
- package/dist/components/checkout/theme.d.ts +0 -1
- package/dist/components/checkout/types.d.ts +7 -0
- package/dist/components/paywall/paywallTextOverrides.d.ts +4 -0
- package/dist/components/utils/getPaidPriceText.d.ts +3 -1
- package/dist/react-sdk.cjs.development.js +1148 -524
- 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 +1182 -532
- package/dist/react-sdk.esm.js.map +1 -1
- package/dist/theme/getResolvedTheme.d.ts +1 -0
- package/dist/theme/types.d.ts +1 -0
- package/package.json +2 -2
- package/src/assets/payment-method.svg +3 -10
- package/src/components/checkout/Checkout.tsx +2 -1
- package/src/components/checkout/CheckoutContainer.style.ts +1 -0
- package/src/components/checkout/CheckoutContainer.tsx +59 -28
- package/src/components/checkout/CheckoutProvider.tsx +18 -18
- package/src/components/checkout/components/Button.tsx +19 -35
- package/src/components/checkout/components/ChangePlanButton.tsx +32 -0
- package/src/components/checkout/components/DowngradeToFreeContainer.tsx +118 -0
- package/src/components/checkout/components/Skeletons.style.ts +4 -1
- package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
- package/src/components/checkout/hooks/usePaymentStepModel.ts +22 -3
- package/src/components/checkout/hooks/usePlanStepModel.ts +25 -10
- package/src/components/checkout/hooks/usePreviewSubscription.ts +112 -40
- package/src/components/checkout/hooks/useProgressBarModel.ts +18 -0
- package/src/components/checkout/hooks/useSubscriptionModel.ts +8 -2
- package/src/components/checkout/hooks/useSubscriptionState.ts +2 -1
- package/src/components/checkout/index.ts +2 -0
- 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 +6 -3
- package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +13 -9
- package/src/components/checkout/promotionCode/AddPromotionCode.tsx +6 -7
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +58 -11
- package/src/components/checkout/steps/payment/PaymentMethods.style.ts +1 -0
- package/src/components/checkout/steps/payment/PaymentMethods.tsx +13 -6
- package/src/components/checkout/steps/payment/PaymentStep.tsx +3 -1
- package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +35 -4
- package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +4 -3
- package/src/components/checkout/steps/payment/stripe/useSubmit.ts +61 -48
- package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +27 -6
- package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +26 -5
- package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +62 -12
- package/src/components/checkout/summary/CheckoutSuccess.tsx +52 -8
- package/src/components/checkout/summary/CheckoutSummary.tsx +48 -33
- package/src/components/checkout/summary/components/CheckoutCaptions.tsx +30 -29
- package/src/components/checkout/summary/components/LineItems.tsx +8 -16
- package/src/components/checkout/textOverrides.ts +15 -12
- package/src/components/checkout/theme.ts +0 -4
- package/src/components/checkout/types.ts +9 -0
- package/src/components/common/Icon.tsx +4 -6
- package/src/components/common/mapExternalTheme.ts +1 -2
- package/src/components/paywall/PlanPrice.tsx +10 -2
- package/src/components/paywall/paywallTextOverrides.ts +3 -0
- package/src/components/utils/getPaidPriceText.ts +8 -2
- package/src/components/utils/getPlanPrice.ts +1 -1
- package/src/stories/Checkout.stories.tsx +2 -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/steps/surprise/SurpriseStep.d.ts +0 -2
- package/src/assets/nyancat.svg +0 -634
- package/src/components/checkout/steps/surprise/SurpriseStep.tsx +0 -27
|
@@ -20,6 +20,16 @@ type GetPlanStepInitialStateProps = {
|
|
|
20
20
|
preconfiguredBillableFeatures: BillableFeature[];
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const getBillingPeriod = (billingPeriod: BillingPeriod, hasMonthlyPrices?: boolean, hasAnnualPrices?: boolean) => {
|
|
24
|
+
if (billingPeriod === BillingPeriod.Monthly && hasMonthlyPrices) {
|
|
25
|
+
return billingPeriod;
|
|
26
|
+
}
|
|
27
|
+
if (billingPeriod === BillingPeriod.Annually && hasAnnualPrices) {
|
|
28
|
+
return billingPeriod;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
23
33
|
const isInAdvanceCommitmentCharge = ({ pricingModel }: Price) => {
|
|
24
34
|
return pricingModel === BillingModel.PerUnit;
|
|
25
35
|
};
|
|
@@ -88,22 +98,27 @@ function resolveBillingPeriod({
|
|
|
88
98
|
const hasMonthlyPrices = plan?.pricePoints.some((pricePoint) => pricePoint.billingPeriod === BillingPeriod.Monthly);
|
|
89
99
|
const hasAnnualPrices = plan?.pricePoints.some((pricePoint) => pricePoint.billingPeriod === BillingPeriod.Annually);
|
|
90
100
|
|
|
101
|
+
if (preferredBillingPeriod) {
|
|
102
|
+
const billingPeriod = getBillingPeriod(preferredBillingPeriod, hasMonthlyPrices, hasAnnualPrices);
|
|
103
|
+
if (billingPeriod) {
|
|
104
|
+
return billingPeriod;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
const isUpdate = activeSubscription?.plan?.id === plan?.id;
|
|
92
109
|
if (isUpdate) {
|
|
93
110
|
return activeSubscription?.price?.billingPeriod || BillingPeriod.Monthly;
|
|
94
111
|
}
|
|
95
112
|
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
if (activeSubscription?.prices && activeSubscription?.prices.length > 0) {
|
|
114
|
+
const billingPeriod = getBillingPeriod(
|
|
115
|
+
activeSubscription?.prices[0].billingPeriod,
|
|
116
|
+
hasMonthlyPrices,
|
|
117
|
+
hasAnnualPrices,
|
|
118
|
+
);
|
|
119
|
+
if (billingPeriod) {
|
|
120
|
+
return billingPeriod;
|
|
99
121
|
}
|
|
100
|
-
if (preferredBillingPeriod === BillingPeriod.Annually && hasAnnualPrices) {
|
|
101
|
-
return BillingPeriod.Annually;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (activeSubscription?.price?.billingPeriod) {
|
|
106
|
-
return activeSubscription?.price?.billingPeriod;
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
return hasAnnualPrices ? BillingPeriod.Annually : BillingPeriod.Monthly;
|
|
@@ -1,64 +1,127 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import isEmpty from 'lodash/isEmpty';
|
|
3
|
+
import { BillingAddress, PreviewSubscription, StiggClient, SubscriptionPreview } 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
|
+
|
|
9
|
+
function mapBillingInformation({
|
|
10
|
+
billingAddress,
|
|
11
|
+
taxPercentage,
|
|
12
|
+
}: {
|
|
13
|
+
billingAddress?: BillingAddress;
|
|
14
|
+
taxPercentage?: number;
|
|
15
|
+
}): Pick<PreviewSubscription, 'billingInformation'> {
|
|
16
|
+
if (!billingAddress && !taxPercentage) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
billingInformation: {
|
|
22
|
+
...(billingAddress ? { billingAddress } : {}),
|
|
23
|
+
...(taxPercentage ? { taxPercentage } : {}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PreviewSubscriptionProps = {
|
|
29
|
+
customerId?: string;
|
|
30
|
+
planId?: string;
|
|
31
|
+
resourceId?: string;
|
|
32
|
+
stigg: StiggClient;
|
|
33
|
+
} & SubscriptionState;
|
|
34
|
+
|
|
35
|
+
const previewSubscription = async ({
|
|
36
|
+
stigg,
|
|
37
|
+
customerId,
|
|
38
|
+
planId,
|
|
39
|
+
resourceId,
|
|
40
|
+
promotionCode,
|
|
41
|
+
addons,
|
|
42
|
+
billableFeatures,
|
|
43
|
+
billingCountryCode,
|
|
44
|
+
billingPeriod,
|
|
45
|
+
billingAddress,
|
|
46
|
+
taxPercentage,
|
|
47
|
+
}: PreviewSubscriptionProps) => {
|
|
48
|
+
const estimateAddons = addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
|
|
49
|
+
let subscriptionPreview: SubscriptionPreview | null = null;
|
|
50
|
+
let errorMessage: string | null = null;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
if (customerId && planId) {
|
|
54
|
+
const previewSubscriptionProps: PreviewSubscription = {
|
|
55
|
+
customerId,
|
|
56
|
+
planId,
|
|
57
|
+
resourceId,
|
|
58
|
+
billingCountryCode,
|
|
59
|
+
addons: estimateAddons,
|
|
60
|
+
billingPeriod,
|
|
61
|
+
promotionCode,
|
|
62
|
+
billableFeatures: isEmpty(billableFeatures) ? undefined : billableFeatures,
|
|
63
|
+
...mapBillingInformation({ billingAddress, taxPercentage }),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
subscriptionPreview = await stigg.previewSubscription(previewSubscriptionProps);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const [, errorMsg] = (error as any)?.message?.split('Error:') || [];
|
|
70
|
+
|
|
71
|
+
errorMessage = errorMsg?.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { subscriptionPreview, errorMessage };
|
|
75
|
+
};
|
|
7
76
|
|
|
8
77
|
export const usePreviewSubscriptionAction = () => {
|
|
9
78
|
const { stigg } = useStiggContext();
|
|
10
79
|
const subscription = useSubscriptionModel();
|
|
11
|
-
const [{ resourceId
|
|
12
|
-
const { checkoutState } = useCheckoutModel();
|
|
13
|
-
const { plan,
|
|
80
|
+
const [{ resourceId }] = useCheckoutContext();
|
|
81
|
+
const { checkoutState, widgetState } = useCheckoutModel();
|
|
82
|
+
const { plan, customer } = checkoutState || {};
|
|
14
83
|
|
|
15
84
|
const previewSubscriptionAction = useCallback(
|
|
16
85
|
async ({ promotionCode }: { promotionCode?: string | null } = {}) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
let errorMessage: string | null = null;
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
if (customer?.id && plan?.id) {
|
|
23
|
-
const previewSubscriptionProps: PreviewSubscription = {
|
|
24
|
-
customerId: customer.id,
|
|
25
|
-
planId: plan?.id,
|
|
26
|
-
billableFeatures: subscription.billableFeatures,
|
|
27
|
-
addons: estimateAddons,
|
|
28
|
-
billingPeriod: subscription.billingPeriod,
|
|
29
|
-
promotionCode: promotionCode ?? subscription.promotionCode,
|
|
30
|
-
resourceId,
|
|
31
|
-
billingCountryCode: planStep.billingCountryCode,
|
|
32
|
-
// TODO: Add billing information
|
|
33
|
-
// billingInformation, // TaxId / Tax percentage
|
|
34
|
-
};
|
|
35
|
-
subscriptionPreview = await stigg.previewSubscription(previewSubscriptionProps);
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
const [, errorMsg] = (error as any)?.message?.split('Error:') || [];
|
|
39
|
-
|
|
40
|
-
errorMessage = errorMsg?.trim();
|
|
86
|
+
if (!widgetState.isValid) {
|
|
87
|
+
return { subscriptionPreview: null };
|
|
41
88
|
}
|
|
42
89
|
|
|
43
|
-
return {
|
|
90
|
+
return previewSubscription({
|
|
91
|
+
stigg,
|
|
92
|
+
customerId: customer?.id,
|
|
93
|
+
planId: plan?.id,
|
|
94
|
+
resourceId,
|
|
95
|
+
addons: subscription.addons,
|
|
96
|
+
billableFeatures: subscription.billableFeatures,
|
|
97
|
+
billingCountryCode: subscription.billingCountryCode,
|
|
98
|
+
billingPeriod: subscription.billingPeriod,
|
|
99
|
+
billingAddress: subscription.billingAddress,
|
|
100
|
+
taxPercentage: subscription.taxPercentage,
|
|
101
|
+
promotionCode: promotionCode ?? subscription.promotionCode,
|
|
102
|
+
});
|
|
44
103
|
},
|
|
45
104
|
[
|
|
46
|
-
activeSubscription,
|
|
47
|
-
customer,
|
|
48
|
-
plan,
|
|
49
|
-
resourceId,
|
|
50
105
|
stigg,
|
|
106
|
+
customer?.id,
|
|
107
|
+
plan?.id,
|
|
108
|
+
resourceId,
|
|
51
109
|
subscription.addons,
|
|
52
|
-
subscription.billingPeriod,
|
|
53
110
|
subscription.billableFeatures,
|
|
111
|
+
subscription.billingCountryCode,
|
|
112
|
+
subscription.billingPeriod,
|
|
113
|
+
subscription.billingAddress,
|
|
114
|
+
subscription.taxPercentage,
|
|
54
115
|
subscription.promotionCode,
|
|
55
|
-
|
|
116
|
+
widgetState.isValid,
|
|
56
117
|
],
|
|
57
118
|
);
|
|
58
119
|
|
|
59
120
|
return { previewSubscriptionAction };
|
|
60
121
|
};
|
|
61
122
|
|
|
123
|
+
const SUBSCRIPTION_PREVIEW_DEBOUNCE_TIME = 500;
|
|
124
|
+
|
|
62
125
|
export const usePreviewSubscription = () => {
|
|
63
126
|
const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreview | null>(null);
|
|
64
127
|
const [isFetchingSubscriptionPreview, setIsFetchingSubscriptionPreview] = useState(false);
|
|
@@ -67,15 +130,24 @@ export const usePreviewSubscription = () => {
|
|
|
67
130
|
|
|
68
131
|
useEffect(() => {
|
|
69
132
|
const estimateSubscription = async () => {
|
|
70
|
-
setIsFetchingSubscriptionPreview(true);
|
|
71
|
-
|
|
72
133
|
const { subscriptionPreview } = await previewSubscriptionAction();
|
|
73
|
-
|
|
134
|
+
if (subscriptionPreview) {
|
|
135
|
+
setSubscriptionPreview(subscriptionPreview);
|
|
136
|
+
}
|
|
74
137
|
|
|
75
138
|
setIsFetchingSubscriptionPreview(false);
|
|
76
139
|
};
|
|
77
140
|
|
|
78
|
-
|
|
141
|
+
setIsFetchingSubscriptionPreview(true);
|
|
142
|
+
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
setIsFetchingSubscriptionPreview(true);
|
|
145
|
+
void estimateSubscription();
|
|
146
|
+
}, SUBSCRIPTION_PREVIEW_DEBOUNCE_TIME);
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
};
|
|
79
151
|
}, [previewSubscriptionAction]);
|
|
80
152
|
|
|
81
153
|
return { subscriptionPreview, isFetchingSubscriptionPreview };
|
|
@@ -22,12 +22,14 @@ export type ProgressBarState = {
|
|
|
22
22
|
activeStep: number;
|
|
23
23
|
completedSteps: number[];
|
|
24
24
|
steps: CheckoutStep[];
|
|
25
|
+
isDisabled: boolean;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const INITIAL_STATE: ProgressBarState = {
|
|
28
29
|
activeStep: 0,
|
|
29
30
|
completedSteps: [],
|
|
30
31
|
steps: CHECKOUT_STEPS,
|
|
32
|
+
isDisabled: false,
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export function getProgressBarInitialState({ availableAddons }: { availableAddons?: Addon[] }) {
|
|
@@ -66,6 +68,10 @@ function useGoNext() {
|
|
|
66
68
|
const [, setState] = useCheckoutContext();
|
|
67
69
|
return () =>
|
|
68
70
|
setState(({ progressBar }) => {
|
|
71
|
+
if (progressBar.isDisabled) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
if (!progressBar.completedSteps.includes(progressBar.activeStep)) {
|
|
70
76
|
progressBar.completedSteps.push(progressBar.activeStep);
|
|
71
77
|
}
|
|
@@ -76,14 +82,26 @@ function useGoNext() {
|
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function useSetIsDisabled() {
|
|
86
|
+
const [, setState] = useCheckoutContext();
|
|
87
|
+
return (isDisabled?: boolean) =>
|
|
88
|
+
setState(({ progressBar }) => {
|
|
89
|
+
progressBar.isDisabled = !!isDisabled;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
export function useProgressBarModel() {
|
|
80
94
|
const progressBarState = useProgressBarState();
|
|
95
|
+
const currentStep = progressBarState.steps[progressBarState.activeStep];
|
|
96
|
+
|
|
81
97
|
return {
|
|
98
|
+
currentStep,
|
|
82
99
|
progressBarState,
|
|
83
100
|
isLastStep: progressBarState.activeStep === progressBarState.steps.length - 1,
|
|
84
101
|
isCheckoutComplete: isCheckoutComplete(progressBarState),
|
|
85
102
|
setActiveStep: useSetActiveStep(),
|
|
86
103
|
markStepAsCompleted: useMarkStepAsCompleted(),
|
|
87
104
|
goNext: useGoNext(),
|
|
105
|
+
setIsDisabled: useSetIsDisabled(),
|
|
88
106
|
};
|
|
89
107
|
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { useCheckoutContext } from '../CheckoutProvider';
|
|
2
2
|
import { AddonsStepState } from './useAddonsStepModel';
|
|
3
3
|
import { PromotionCodeState } from './useCouponModel';
|
|
4
|
+
import { PaymentStepState } from './usePaymentStepModel';
|
|
4
5
|
import { PlanStepState } from './usePlanStepModel';
|
|
5
6
|
|
|
6
|
-
export type SubscriptionState = PlanStepState &
|
|
7
|
+
export type SubscriptionState = PlanStepState &
|
|
8
|
+
PromotionCodeState &
|
|
9
|
+
Pick<AddonsStepState, 'addons'> &
|
|
10
|
+
Pick<PaymentStepState, 'billingAddress' | 'taxPercentage'>;
|
|
7
11
|
|
|
8
12
|
export function useSubscriptionModel(): SubscriptionState {
|
|
9
|
-
const [{ planStep, addonsStep, promotionCode }] = useCheckoutContext();
|
|
13
|
+
const [{ planStep, addonsStep, promotionCode, paymentStep }] = useCheckoutContext();
|
|
10
14
|
|
|
11
15
|
return {
|
|
12
16
|
...planStep,
|
|
13
17
|
addons: addonsStep.addons,
|
|
14
18
|
promotionCode,
|
|
19
|
+
billingAddress: paymentStep.billingAddress,
|
|
20
|
+
taxPercentage: paymentStep.taxPercentage,
|
|
15
21
|
};
|
|
16
22
|
}
|
|
@@ -11,7 +11,7 @@ export function useSubscriptionState(): ApplySubscription | undefined {
|
|
|
11
11
|
const addons = subscription.addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
|
|
12
12
|
|
|
13
13
|
if (!plan?.id) {
|
|
14
|
-
return;
|
|
14
|
+
return undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
return {
|
|
@@ -22,5 +22,6 @@ export function useSubscriptionState(): ApplySubscription | undefined {
|
|
|
22
22
|
addons,
|
|
23
23
|
promotionCode: subscription.promotionCode,
|
|
24
24
|
billingCountryCode: subscription.billingCountryCode,
|
|
25
|
+
...(subscription.taxPercentage ? { billingInformation: { taxPercentage: subscription.taxPercentage } } : {}),
|
|
25
26
|
};
|
|
26
27
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { CheckoutTheme } from './theme';
|
|
2
2
|
export { Checkout, CheckoutProps } from './Checkout';
|
|
3
|
+
export { OnCheckoutCompletedParams, OnCheckoutParams, CheckoutResult } from './CheckoutContainer';
|
|
3
4
|
export { CheckoutLocalization } from './textOverrides';
|
|
5
|
+
export * from './types';
|
|
@@ -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,
|
|
@@ -25,10 +27,11 @@ export const StyledStepButton = styled(Button)(() => ({
|
|
|
25
27
|
},
|
|
26
28
|
}));
|
|
27
29
|
|
|
28
|
-
export const StyledIcon = styled(Icon)<{ $disabled?: boolean; $shouldStroke?: boolean }>(
|
|
29
|
-
({ theme, $disabled, $shouldStroke = true }) => ({
|
|
30
|
+
export const StyledIcon = styled(Icon)<{ $disabled?: boolean; $shouldStroke?: boolean; $shouldFill?: boolean }>(
|
|
31
|
+
({ theme, $disabled, $shouldStroke = true, $shouldFill }) => ({
|
|
30
32
|
circle: {
|
|
31
33
|
stroke: $shouldStroke ? ($disabled ? theme.stigg.palette.text.disabled : theme.stigg.palette.primary) : undefined,
|
|
34
|
+
fill: $shouldFill ? theme.stigg.palette.primary : undefined,
|
|
32
35
|
},
|
|
33
36
|
}),
|
|
34
37
|
);
|
|
@@ -14,27 +14,31 @@ 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 (
|
|
26
29
|
<Grid key={key} item display="flex" flexDirection="row" flex={1} justifyContent="flex-start">
|
|
27
|
-
{isLoadingCheckoutData
|
|
28
|
-
|
|
30
|
+
{isLoadingCheckoutData ? (
|
|
31
|
+
<Skeleton width={120} height={20} style={{ marginTop: 8 }} />
|
|
32
|
+
) : (
|
|
29
33
|
<StyledStepButton onClick={() => setActiveStep(index)} fullWidth disabled={isDisabled}>
|
|
30
34
|
<Grid item display="flex" flexDirection="row" alignItems="center" gap={1}>
|
|
31
35
|
<StepIcon
|
|
32
36
|
icon={
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
<StyledIcon
|
|
38
|
+
icon={isCompleted ? checkedIcon : 'OutlinedCircle'}
|
|
39
|
+
$disabled={isDisabled}
|
|
40
|
+
$shouldFill={isCompleted}
|
|
41
|
+
/>
|
|
38
42
|
}
|
|
39
43
|
/>
|
|
40
44
|
|
|
@@ -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,17 +97,27 @@ 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()}>
|
|
91
|
-
|
|
117
|
+
<Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange(1)}>
|
|
118
|
+
<Typography color="primary.main" variant="body1">
|
|
119
|
+
{checkoutLocalization.addAddonText}
|
|
120
|
+
</Typography>
|
|
92
121
|
</Button>
|
|
93
122
|
)}
|
|
94
123
|
</Grid>
|
|
@@ -97,15 +126,29 @@ function AddonListItem({
|
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
export function CheckoutAddonsStep() {
|
|
100
|
-
const { checkoutLocalization } = useCheckoutModel();
|
|
129
|
+
const { checkoutLocalization, setIsValid } = useCheckoutModel();
|
|
101
130
|
const { billingPeriod } = usePlanStepModel();
|
|
131
|
+
const { setIsDisabled } = useProgressBarModel();
|
|
102
132
|
const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
|
|
133
|
+
const [addonsValidation, setAddonsValidation] = useState(
|
|
134
|
+
availableAddons?.reduce<Record<string, boolean>>((acc, curr) => {
|
|
135
|
+
acc[curr.id] = true;
|
|
136
|
+
return acc;
|
|
137
|
+
}, {}) || {},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const isDisabled = Object.values(addonsValidation).some((x) => !x);
|
|
142
|
+
setIsDisabled(isDisabled);
|
|
143
|
+
setIsValid(!isDisabled);
|
|
144
|
+
}, [addonsValidation, setIsDisabled, setIsValid]);
|
|
103
145
|
|
|
104
146
|
return (
|
|
105
147
|
<CheckoutAddonsContainer container>
|
|
106
148
|
{availableAddons?.map((addon) => {
|
|
107
149
|
const addonState = addons.find((x) => x.addon.id === addon.id);
|
|
108
150
|
const initialAddonState = initialAddons?.find((x) => x.addon.id === addon.id);
|
|
151
|
+
const isValid = addonsValidation[addon.id];
|
|
109
152
|
|
|
110
153
|
return (
|
|
111
154
|
<AddonListItem
|
|
@@ -117,6 +160,10 @@ export function CheckoutAddonsStep() {
|
|
|
117
160
|
setAddon={setAddon}
|
|
118
161
|
removeAddon={removeAddon}
|
|
119
162
|
checkoutLocalization={checkoutLocalization}
|
|
163
|
+
onAddonsValidationChange={({ addonId, isValid }: { addonId: string; isValid: boolean }) =>
|
|
164
|
+
setAddonsValidation({ ...addonsValidation, [addonId]: isValid })
|
|
165
|
+
}
|
|
166
|
+
isValid={isValid}
|
|
120
167
|
/>
|
|
121
168
|
);
|
|
122
169
|
})}
|
|
@@ -6,6 +6,7 @@ export const PaymentMethodContainer = styled(Grid)<{ $disabled?: boolean }>`
|
|
|
6
6
|
border-radius: 10px;
|
|
7
7
|
border: 1px solid ${({ theme }) => theme.stigg.palette.outlinedBorder};
|
|
8
8
|
cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
|
|
9
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.6 : 1)};
|
|
9
10
|
`;
|
|
10
11
|
|
|
11
12
|
export const NewPaymentMethodContainer = styled(PaymentMethodContainer)`
|