@stigg/react-sdk 4.5.3 → 4.5.5
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/README.md +7 -0
- package/dist/components/checkout/CheckoutContainer.d.ts +4 -2
- package/dist/components/checkout/hooks/useSubscriptionState.d.ts +2 -2
- package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +9 -13
- package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +2 -1
- package/dist/components/customerPortal/paywall/CustomerPortalPaywall.d.ts +0 -1
- package/dist/components/customerPortal/paywall/CustomerPortalPaywall.style.d.ts +0 -1
- package/dist/components/paywall/Paywall.d.ts +0 -1
- package/dist/components/paywall/PlanOffering.d.ts +2 -1
- package/dist/components/paywall/PlanOfferingButton.d.ts +1 -2
- package/dist/react-sdk.cjs.development.js +287 -260
- 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 +288 -261
- package/dist/react-sdk.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/checkout/CheckoutContainer.tsx +13 -3
- package/src/components/checkout/hooks/useSubscriptionState.ts +2 -3
- package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +31 -22
- package/src/components/checkout/steps/payment/stripe/useSubmit.ts +63 -46
- package/src/components/checkout/summary/CheckoutSummary.tsx +1 -0
- package/src/components/customerPortal/CustomerPortalContainer.tsx +5 -8
- package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +0 -10
- package/src/components/customerPortal/paywall/CustomerPortalPaywall.tsx +2 -5
- package/src/components/paywall/Paywall.tsx +36 -20
- package/src/components/paywall/PaywallContainer.tsx +2 -2
- package/src/components/paywall/PlanOffering.tsx +32 -17
- package/src/components/paywall/PlanOfferingButton.tsx +1 -3
- package/src/components/paywall/utils/mapPaywallData.ts +13 -11
- package/src/stories/Checkout.stories.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.5.
|
|
2
|
+
"version": "4.5.5",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@emotion/react": "^11.10.5",
|
|
110
110
|
"@emotion/styled": "^11.10.5",
|
|
111
111
|
"@mui/material": "^5.12.0",
|
|
112
|
-
"@stigg/js-client-sdk": "2.24.
|
|
112
|
+
"@stigg/js-client-sdk": "2.24.2",
|
|
113
113
|
"@stripe/react-stripe-js": "^2.1.1",
|
|
114
114
|
"@stripe/stripe-js": "^1.54.1",
|
|
115
115
|
"@types/styled-components": "^5.1.26",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { Elements } from '@stripe/react-stripe-js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PricingType,
|
|
5
|
+
BillingAddress,
|
|
6
|
+
ApplySubscription,
|
|
7
|
+
CheckoutStatePlan,
|
|
8
|
+
ApplySubscriptionResults,
|
|
9
|
+
} from '@stigg/js-client-sdk';
|
|
4
10
|
import { CheckoutContent, CheckoutLayout, CheckoutPanel } from './CheckoutContainer.style';
|
|
5
11
|
import { CheckoutProgressBar } from './progressBar/CheckoutProgressBar';
|
|
6
12
|
import { CheckoutSummary, CheckoutSummarySkeleton } from './summary';
|
|
@@ -45,9 +51,13 @@ const getStepProps = (
|
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
export type CheckoutResult = { success: boolean; errorMessage?: string };
|
|
54
|
+
export type CheckoutResult = { success: boolean; errorMessage?: string; results?: ApplySubscriptionResults };
|
|
49
55
|
|
|
50
|
-
export type OnCheckoutParams = {
|
|
56
|
+
export type OnCheckoutParams = {
|
|
57
|
+
customerId: string;
|
|
58
|
+
checkoutParams: ApplySubscription;
|
|
59
|
+
checkoutAction: (params: ApplySubscription) => Promise<CheckoutResult>;
|
|
60
|
+
};
|
|
51
61
|
|
|
52
62
|
export type OnCheckoutCompletedParams = { success: boolean; error?: string };
|
|
53
63
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ApplySubscription } from '@stigg/js-client-sdk';
|
|
1
|
+
import { ApplySubscription, PreviewSubscription } from '@stigg/js-client-sdk';
|
|
2
2
|
import { useCheckoutContext } from '../CheckoutProvider';
|
|
3
3
|
import { useCheckoutModel } from './useCheckoutModel';
|
|
4
4
|
import { useSubscriptionModel } from './useSubscriptionModel';
|
|
5
5
|
|
|
6
|
-
export function useSubscriptionState(): ApplySubscription | undefined {
|
|
6
|
+
export function useSubscriptionState(): ApplySubscription | PreviewSubscription | undefined {
|
|
7
7
|
const subscription = useSubscriptionModel();
|
|
8
8
|
const [{ resourceId }] = useCheckoutContext();
|
|
9
9
|
const { checkoutState } = useCheckoutModel();
|
|
@@ -22,6 +22,5 @@ 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 } } : {}),
|
|
26
25
|
};
|
|
27
26
|
}
|
|
@@ -23,28 +23,6 @@ export async function handleStripeFormValidations({ elements }: Pick<StripeEleme
|
|
|
23
23
|
return { success: true };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export async function handleNewPaymentMethod({
|
|
27
|
-
stripe,
|
|
28
|
-
elements,
|
|
29
|
-
setupIntentClientSecret,
|
|
30
|
-
}: {
|
|
31
|
-
stripe: Stripe | null;
|
|
32
|
-
elements: StripeElements | null;
|
|
33
|
-
setupIntentClientSecret?: string;
|
|
34
|
-
}) {
|
|
35
|
-
if (!stripe || !elements || !setupIntentClientSecret) {
|
|
36
|
-
return { success: false };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { newPaymentMethodId, setupErrorMessage } = await handleStripeSetup({
|
|
40
|
-
stripe,
|
|
41
|
-
elements,
|
|
42
|
-
clientSecret: setupIntentClientSecret,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return { success: !!newPaymentMethodId, paymentMethodId: newPaymentMethodId, errorMessage: setupErrorMessage };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
26
|
export async function handleStripeNextAction({
|
|
49
27
|
applySubscriptionResults,
|
|
50
28
|
stripe,
|
|
@@ -108,3 +86,34 @@ async function handleStripeSetup({
|
|
|
108
86
|
|
|
109
87
|
return { newPaymentMethodId, setupErrorMessage };
|
|
110
88
|
}
|
|
89
|
+
|
|
90
|
+
export async function handleNewPaymentMethod({
|
|
91
|
+
stripe,
|
|
92
|
+
elements,
|
|
93
|
+
setupIntentClientSecret,
|
|
94
|
+
}: {
|
|
95
|
+
stripe: Stripe | null;
|
|
96
|
+
elements: StripeElements | null;
|
|
97
|
+
setupIntentClientSecret?: string;
|
|
98
|
+
}): Promise<{ success: boolean; errorMessage?: string; paymentMethodId?: string }> {
|
|
99
|
+
if (!stripe || !elements || !setupIntentClientSecret) {
|
|
100
|
+
return { success: false };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const stripeFormValidationsResults = await handleStripeFormValidations({ elements });
|
|
104
|
+
if (!stripeFormValidationsResults.success) {
|
|
105
|
+
return { success: false, errorMessage: stripeFormValidationsResults.errorMessage };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { newPaymentMethodId, setupErrorMessage } = await handleStripeSetup({
|
|
109
|
+
stripe,
|
|
110
|
+
elements,
|
|
111
|
+
clientSecret: setupIntentClientSecret,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (setupErrorMessage || !newPaymentMethodId) {
|
|
115
|
+
return { success: false, errorMessage: setupErrorMessage };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { success: true, paymentMethodId: newPaymentMethodId };
|
|
119
|
+
}
|
|
@@ -5,7 +5,7 @@ import { useCheckoutModel } from '../../../hooks';
|
|
|
5
5
|
import { usePaymentStepModel } from '../../../hooks/usePaymentStepModel';
|
|
6
6
|
import { useSubscriptionState } from '../../../hooks/useSubscriptionState';
|
|
7
7
|
import { CheckoutContainerProps, CheckoutResult } from '../../../CheckoutContainer';
|
|
8
|
-
import { handleNewPaymentMethod,
|
|
8
|
+
import { handleNewPaymentMethod, handleStripeNextAction } from './stripe.utils';
|
|
9
9
|
import { ANIMATION_DURATION } from '../../../summary/CheckoutSuccess';
|
|
10
10
|
|
|
11
11
|
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
|
@@ -14,14 +14,21 @@ export type HandleSubmitResult = { results?: ApplySubscriptionResults; success:
|
|
|
14
14
|
|
|
15
15
|
export type UseSubmitProps = {
|
|
16
16
|
onSuccess?: () => void;
|
|
17
|
+
isMocked?: boolean;
|
|
17
18
|
} & Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted' | 'disableSuccessAnimation'>;
|
|
18
19
|
|
|
19
|
-
export function useSubmit({
|
|
20
|
+
export function useSubmit({
|
|
21
|
+
isMocked = false,
|
|
22
|
+
onCheckout,
|
|
23
|
+
onCheckoutCompleted,
|
|
24
|
+
onSuccess,
|
|
25
|
+
disableSuccessAnimation,
|
|
26
|
+
}: UseSubmitProps) {
|
|
20
27
|
const { stigg } = useStiggContext();
|
|
21
28
|
const { useNewPaymentMethod } = usePaymentStepModel();
|
|
22
29
|
const subscriptionState = useSubscriptionState();
|
|
23
30
|
const { checkoutState, widgetState, setWidgetReadOnly } = useCheckoutModel();
|
|
24
|
-
const { setupSecret: setupIntentClientSecret } = checkoutState || {};
|
|
31
|
+
const { setupSecret: setupIntentClientSecret, customer } = checkoutState || {};
|
|
25
32
|
const stripe = useStripe();
|
|
26
33
|
const elements = useElements();
|
|
27
34
|
|
|
@@ -32,48 +39,38 @@ export function useSubmit({ onCheckout, onCheckoutCompleted, onSuccess, disableS
|
|
|
32
39
|
return { success: false, errorMessage: 'Unexpected error, please contact support.' };
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
let
|
|
36
|
-
let checkoutResults: ApplySubscriptionResults | undefined;
|
|
42
|
+
let success = true;
|
|
37
43
|
let errorMessage: string | undefined;
|
|
44
|
+
let usedStiggCheckoutAction = false;
|
|
38
45
|
let paymentMethodId: string | undefined;
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
if (useNewPaymentMethod) {
|
|
42
|
-
const { success, errorMessage: stripeValidationsErrorMessage } = await handleStripeFormValidations({
|
|
43
|
-
elements,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (!success) {
|
|
47
|
-
return { success: false, errorMessage: stripeValidationsErrorMessage };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const paymentMethodResults = await handleNewPaymentMethod({ elements, stripe, setupIntentClientSecret });
|
|
51
|
-
if (!paymentMethodResults.success) {
|
|
52
|
-
errorMessage = paymentMethodResults.errorMessage;
|
|
53
|
-
}
|
|
47
|
+
setWidgetReadOnly(true);
|
|
54
48
|
|
|
49
|
+
if (!isMocked && useNewPaymentMethod) {
|
|
50
|
+
const paymentMethodResults = await handleNewPaymentMethod({ elements, stripe, setupIntentClientSecret });
|
|
51
|
+
if (!paymentMethodResults.success) {
|
|
52
|
+
errorMessage = paymentMethodResults.errorMessage;
|
|
53
|
+
success = false;
|
|
54
|
+
} else {
|
|
55
55
|
paymentMethodId = paymentMethodResults.paymentMethodId;
|
|
56
56
|
}
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
return { success: false, errorMessage };
|
|
60
|
-
}
|
|
59
|
+
const checkoutParams: ApplySubscription = { ...subscriptionState, paymentMethodId };
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
const checkoutAction = async (params: ApplySubscription): Promise<CheckoutResult> => {
|
|
62
|
+
usedStiggCheckoutAction = true;
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
|
-
const applySubscriptionResults = await stigg.applySubscription(
|
|
66
|
-
const nextActionResults = await handleStripeNextAction({
|
|
67
|
-
applySubscriptionResults,
|
|
68
|
-
stripe,
|
|
69
|
-
});
|
|
65
|
+
const applySubscriptionResults = await stigg.applySubscription(params);
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
if (nextActionResults.errorMessage) {
|
|
73
|
-
errorMessage = nextActionResults.errorMessage;
|
|
74
|
-
}
|
|
67
|
+
const nextActionResults = await handleStripeNextAction({ applySubscriptionResults, stripe });
|
|
75
68
|
|
|
76
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
success: !nextActionResults.errorMessage,
|
|
71
|
+
errorMessage: nextActionResults.errorMessage,
|
|
72
|
+
results: applySubscriptionResults,
|
|
73
|
+
};
|
|
77
74
|
} catch (e) {
|
|
78
75
|
console.error(e);
|
|
79
76
|
errorMessage = (e as any)?.message;
|
|
@@ -81,21 +78,41 @@ export function useSubmit({ onCheckout, onCheckoutCompleted, onSuccess, disableS
|
|
|
81
78
|
}
|
|
82
79
|
};
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
let checkoutResults: ApplySubscriptionResults | undefined;
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
if (success) {
|
|
84
|
+
if (onCheckout) {
|
|
85
|
+
const externalCheckoutResults = await onCheckout({
|
|
86
|
+
customerId: customer!.id,
|
|
87
|
+
checkoutParams,
|
|
88
|
+
checkoutAction,
|
|
89
|
+
});
|
|
90
|
+
if (externalCheckoutResults.errorMessage) {
|
|
91
|
+
errorMessage = externalCheckoutResults.errorMessage;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!usedStiggCheckoutAction && externalCheckoutResults.results && externalCheckoutResults.success) {
|
|
95
|
+
const nextActionResults = await handleStripeNextAction({
|
|
96
|
+
applySubscriptionResults: externalCheckoutResults.results,
|
|
97
|
+
stripe,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (nextActionResults.errorMessage) {
|
|
101
|
+
errorMessage = nextActionResults.errorMessage;
|
|
102
|
+
success = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
success = success && externalCheckoutResults.success && !errorMessage;
|
|
107
|
+
checkoutResults = externalCheckoutResults.results;
|
|
108
|
+
} else {
|
|
109
|
+
const checkoutActionResults = await checkoutAction(checkoutParams);
|
|
110
|
+
if (!checkoutActionResults.success && checkoutActionResults.errorMessage) {
|
|
111
|
+
errorMessage = checkoutActionResults.errorMessage;
|
|
112
|
+
}
|
|
113
|
+
success = checkoutActionResults.success && !errorMessage;
|
|
114
|
+
checkoutResults = checkoutActionResults.results;
|
|
97
115
|
}
|
|
98
|
-
success = checkoutActionResults.success && !errorMessage;
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
setWidgetReadOnly(false);
|
|
@@ -122,6 +122,7 @@ export const CheckoutSummary = ({
|
|
|
122
122
|
const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription({ onMockCheckoutPreview });
|
|
123
123
|
|
|
124
124
|
const { handleSubmit, isLoading } = useSubmit({
|
|
125
|
+
isMocked: !!onMockCheckoutPreview, // This is a hack to make the submit button work with mocked data
|
|
125
126
|
disableSuccessAnimation,
|
|
126
127
|
onCheckout,
|
|
127
128
|
onCheckoutCompleted,
|
|
@@ -4,7 +4,6 @@ import { CustomerPortalHeader } from './CustomerPortalHeader';
|
|
|
4
4
|
import { PaymentDetailsSection } from './billing/PaymentDetailsSection';
|
|
5
5
|
import { CustomerPortalPaywall } from './paywall/CustomerPortalPaywall';
|
|
6
6
|
import { useCustomerPortalContext } from './CustomerPortalProvider';
|
|
7
|
-
import { PricingType } from '@stigg/js-client-sdk';
|
|
8
7
|
import { CustomerPortalLayout, CustomerPortalSections } from './CustomerPortal.style';
|
|
9
8
|
import { CustomerPortalProps } from './CustomerPortal';
|
|
10
9
|
import { InvoicesSection, useStiggContext } from '../..';
|
|
@@ -24,20 +23,19 @@ export function CustomerPortalContainer({
|
|
|
24
23
|
|
|
25
24
|
const onManageClick = () => {
|
|
26
25
|
if (onManageSubscription) {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
27
27
|
onManageSubscription();
|
|
28
28
|
} else {
|
|
29
29
|
customerPortalSectionRef?.current?.scrollIntoView({ behavior: 'smooth' });
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
const shouldShowUsage = !hiddenSections?.some(section => section === 'usage');
|
|
32
|
+
|
|
33
|
+
const shouldShowUsage = !hiddenSections?.some((section) => section === 'usage');
|
|
36
34
|
const shouldShowPaymentDetails = !hiddenSections?.some(
|
|
37
|
-
section => section === 'paymentDetails' || section === 'billingInformation',
|
|
35
|
+
(section) => section === 'paymentDetails' || section === 'billingInformation',
|
|
38
36
|
);
|
|
39
37
|
const shouldShowInvoices = !hiddenSections?.some(
|
|
40
|
-
section => section === 'invoices' || section === 'billingInformation',
|
|
38
|
+
(section) => section === 'invoices' || section === 'billingInformation',
|
|
41
39
|
);
|
|
42
40
|
|
|
43
41
|
return (
|
|
@@ -56,7 +54,6 @@ export function CustomerPortalContainer({
|
|
|
56
54
|
<CustomerPortalPaywall
|
|
57
55
|
ref={customerPortalSectionRef}
|
|
58
56
|
paywallComponent={paywallComponent}
|
|
59
|
-
hidePaywallButtons={!!hasCustomSubscription}
|
|
60
57
|
theme={theme}
|
|
61
58
|
title={textOverrides.paywallSectionTitle}
|
|
62
59
|
isLoading={!customerPortal || isLoading}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { css } from '@emotion/react';
|
|
2
1
|
import styled from '@emotion/styled/macro';
|
|
3
2
|
import { STIGG_WATERMARK_CLASSNAME } from '../../common/PoweredByStigg';
|
|
4
3
|
import { SectionContainer } from '../common/SectionContainer';
|
|
5
4
|
|
|
6
5
|
export const CustomerPortalPaywallLayout = styled(SectionContainer)<{
|
|
7
|
-
$hideButtons: boolean;
|
|
8
6
|
$backgroundColor: string;
|
|
9
7
|
$borderColor: string;
|
|
10
8
|
}>`
|
|
@@ -29,14 +27,6 @@ export const CustomerPortalPaywallLayout = styled(SectionContainer)<{
|
|
|
29
27
|
margin-right: auto;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
${({ $hideButtons }) =>
|
|
33
|
-
$hideButtons &&
|
|
34
|
-
css`
|
|
35
|
-
.stigg-paywall-plan-button:not([disabled]) {
|
|
36
|
-
display: none;
|
|
37
|
-
}
|
|
38
|
-
`}
|
|
39
|
-
|
|
40
30
|
.${STIGG_WATERMARK_CLASSNAME} {
|
|
41
31
|
display: none;
|
|
42
32
|
}
|
|
@@ -6,14 +6,13 @@ import { SectionHeader } from '../common/SectionHeader';
|
|
|
6
6
|
|
|
7
7
|
type CustomerPortalPaywallProps = {
|
|
8
8
|
paywallComponent?: React.ReactNode;
|
|
9
|
-
hidePaywallButtons: boolean;
|
|
10
9
|
theme: CustomerPortalTheme;
|
|
11
10
|
title: string;
|
|
12
11
|
isLoading: boolean;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
export const CustomerPortalPaywall = React.forwardRef<HTMLDivElement, CustomerPortalPaywallProps>(
|
|
16
|
-
({ paywallComponent,
|
|
15
|
+
({ paywallComponent, theme, title, isLoading }, ref) => {
|
|
17
16
|
if (!paywallComponent) {
|
|
18
17
|
return null;
|
|
19
18
|
}
|
|
@@ -22,10 +21,8 @@ export const CustomerPortalPaywall = React.forwardRef<HTMLDivElement, CustomerPo
|
|
|
22
21
|
<CustomerPortalPaywallLayout
|
|
23
22
|
className="stigg-customer-portal-paywall-section"
|
|
24
23
|
ref={ref}
|
|
25
|
-
$hideButtons={hidePaywallButtons}
|
|
26
24
|
$backgroundColor={theme.backgroundColor}
|
|
27
|
-
$borderColor={theme.borderColor}
|
|
28
|
-
>
|
|
25
|
+
$borderColor={theme.borderColor}>
|
|
29
26
|
<SectionHeader className="stigg-customer-portal-paywall-header">
|
|
30
27
|
<SectionTitle isLoading={isLoading} className="stigg-customer-portal-paywall-section-title" title={title} />
|
|
31
28
|
</SectionHeader>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BillableFeature, BillingPeriod, Customer, Plan, Subscription } from '@stigg/js-client-sdk';
|
|
2
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import { BillableFeature, BillingPeriod, Customer, Plan, PricingType, Subscription } from '@stigg/js-client-sdk';
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
4
|
import { PlanOffering } from './PlanOffering';
|
|
5
5
|
import { BillingPeriodPicker } from './BillingPeriodPicker';
|
|
@@ -47,7 +47,6 @@ type PaywallProps = {
|
|
|
47
47
|
highlightedPlanId?: string;
|
|
48
48
|
onBillingPeriodChanged: (billingPeriod: BillingPeriod) => void;
|
|
49
49
|
availableBillingPeriods: BillingPeriod[];
|
|
50
|
-
isLoading: boolean;
|
|
51
50
|
isCustomerOnTrial: boolean;
|
|
52
51
|
onPlanSelected: OnPlanSelectedCallbackFn;
|
|
53
52
|
paywallLocale: PaywallLocalization;
|
|
@@ -69,7 +68,7 @@ export const Paywall = ({
|
|
|
69
68
|
}: PaywallProps) => {
|
|
70
69
|
const { stigg } = useStiggContext();
|
|
71
70
|
const discountRate = calculatePaywallDiscountRate(plans);
|
|
72
|
-
const shouldShowDescriptionSection = plans.some(plan => !!plan.description);
|
|
71
|
+
const shouldShowDescriptionSection = plans.some((plan) => !!plan.description);
|
|
73
72
|
const hasMonthlyPrice = hasPricePointsForPlans(plans, BillingPeriod.Monthly);
|
|
74
73
|
const hasAnnuallyPrice = hasPricePointsForPlans(plans, BillingPeriod.Annually);
|
|
75
74
|
const plansToShow = getPlansToDisplay(plans, selectedBillingPeriod);
|
|
@@ -85,26 +84,42 @@ export const Paywall = ({
|
|
|
85
84
|
billableFeatures,
|
|
86
85
|
});
|
|
87
86
|
},
|
|
88
|
-
[customer, selectedBillingPeriod],
|
|
87
|
+
[customer, selectedBillingPeriod, currentSubscription, onPlanSelected],
|
|
89
88
|
);
|
|
90
89
|
|
|
91
|
-
const
|
|
92
|
-
const planPrices = plan.pricePoints.filter(pricePoint => pricePoint.billingPeriod === selectedBillingPeriod);
|
|
93
|
-
const paywallCalculatedPrice = plan.paywallCalculatedPricePoints?.find(
|
|
94
|
-
pricePoint => pricePoint.billingPeriod === selectedBillingPeriod,
|
|
95
|
-
);
|
|
96
|
-
return planPrices.length > 1 && !!paywallCalculatedPrice?.additionalChargesMayApply;
|
|
97
|
-
});
|
|
90
|
+
const isCustomerInCustomPlan = !!currentSubscription && currentSubscription.plan.pricingType === PricingType.Custom;
|
|
98
91
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
const withStartingAtRow = useMemo(
|
|
93
|
+
() =>
|
|
94
|
+
plansToShow.some((plan) => {
|
|
95
|
+
const planPrices = plan.pricePoints.filter((pricePoint) => pricePoint.billingPeriod === selectedBillingPeriod);
|
|
96
|
+
const paywallCalculatedPrice = plan.paywallCalculatedPricePoints?.find(
|
|
97
|
+
(pricePoint) => pricePoint.billingPeriod === selectedBillingPeriod,
|
|
98
|
+
);
|
|
99
|
+
return planPrices.length > 1 && !!paywallCalculatedPrice?.additionalChargesMayApply;
|
|
100
|
+
}),
|
|
101
|
+
[selectedBillingPeriod, plansToShow],
|
|
102
|
+
);
|
|
102
103
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const withUnitPriceRow = useMemo(
|
|
105
|
+
() =>
|
|
106
|
+
plansToShow.some((plan) => {
|
|
107
|
+
return !!getPlanPrice(plan, selectedBillingPeriod, paywallLocale, locale, hasMonthlyPrice).unit;
|
|
108
|
+
}),
|
|
109
|
+
[selectedBillingPeriod, hasMonthlyPrice, locale, paywallLocale, plansToShow],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const withTiersRow = useMemo(() => {
|
|
113
|
+
return (
|
|
114
|
+
!isCustomerInCustomPlan &&
|
|
115
|
+
plansToShow.some((plan) => {
|
|
116
|
+
const tiers = getSelectedTier(plan, selectedBillingPeriod, currentSubscription, {});
|
|
117
|
+
return Object.values(tiers).length > 0;
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}, [selectedBillingPeriod, currentSubscription, isCustomerInCustomPlan, plansToShow]);
|
|
106
121
|
|
|
107
|
-
const withTrialLeftRow = plansToShow.some(plan => {
|
|
122
|
+
const withTrialLeftRow = plansToShow.some((plan) => {
|
|
108
123
|
return plan.isCurrentCustomerPlan && plan.trialDaysLeft;
|
|
109
124
|
});
|
|
110
125
|
|
|
@@ -119,7 +134,7 @@ export const Paywall = ({
|
|
|
119
134
|
/>
|
|
120
135
|
|
|
121
136
|
<PaywallPlansContainer className="stigg-paywall-plans-layout">
|
|
122
|
-
{plansToShow.map(plan => (
|
|
137
|
+
{plansToShow.map((plan) => (
|
|
123
138
|
<PlanOffering
|
|
124
139
|
withUnitPriceRow={withUnitPriceRow}
|
|
125
140
|
withTiersRow={withTiersRow}
|
|
@@ -140,6 +155,7 @@ export const Paywall = ({
|
|
|
140
155
|
paywallLocale={paywallLocale}
|
|
141
156
|
locale={locale}
|
|
142
157
|
customer={customer}
|
|
158
|
+
isCustomerInCustomPlan={isCustomerInCustomPlan}
|
|
143
159
|
/>
|
|
144
160
|
))}
|
|
145
161
|
</PaywallPlansContainer>
|
|
@@ -22,7 +22,7 @@ export type PaywallContainerProps = {
|
|
|
22
22
|
preferredBillingPeriod?: BillingPeriod;
|
|
23
23
|
onBillingPeriodChange?: (billingPeriod: BillingPeriod) => void;
|
|
24
24
|
textOverrides?: DeepPartial<PaywallLocalization>;
|
|
25
|
-
billingCountryCode?: string
|
|
25
|
+
billingCountryCode?: string;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export const PaywallContainer = ({
|
|
@@ -39,6 +39,7 @@ export const PaywallContainer = ({
|
|
|
39
39
|
const hasCustomerPortalContext = useCheckContextExists(CustomerPortalContext);
|
|
40
40
|
let isCustomerPortalLoading = false;
|
|
41
41
|
if (hasCustomerPortalContext) {
|
|
42
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
42
43
|
const { isLoading, resourceId: customerPortalResourceId } = useCustomerPortalContext();
|
|
43
44
|
isCustomerPortalLoading = isLoading;
|
|
44
45
|
resourceId = customerPortalResourceId;
|
|
@@ -81,7 +82,6 @@ export const PaywallContainer = ({
|
|
|
81
82
|
onBillingPeriodChanged={handlePeriodChange}
|
|
82
83
|
availableBillingPeriods={availableBillingPeriods}
|
|
83
84
|
highlightedPlanId={highlightedPlanId}
|
|
84
|
-
isLoading={isLoading}
|
|
85
85
|
isCustomerOnTrial={isCustomerOnTrial}
|
|
86
86
|
onPlanSelected={onPlanSelected}
|
|
87
87
|
paywallLocale={paywallLocale}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BillableFeature,
|
|
4
|
+
BillingPeriod,
|
|
5
|
+
Customer,
|
|
6
|
+
PriceTierFragment,
|
|
7
|
+
PricingType,
|
|
8
|
+
Subscription,
|
|
9
|
+
} from '@stigg/js-client-sdk';
|
|
3
10
|
import styled from '@emotion/styled/macro';
|
|
11
|
+
import classNames from 'classnames';
|
|
12
|
+
import Grid from '@mui/material/Grid';
|
|
4
13
|
import { PlanEntitlements } from './PlanEntitlements';
|
|
5
14
|
import { PlanOfferingButton } from './PlanOfferingButton';
|
|
6
15
|
import { PaywallPlan, SubscribeIntentionType } from './types';
|
|
7
16
|
import { PaywallLocalization } from './paywallTextOverrides';
|
|
8
17
|
import { flexLayoutMapper } from '../../theme/getResolvedTheme';
|
|
9
18
|
import { Typography } from '../common/Typography';
|
|
10
|
-
import classNames from 'classnames';
|
|
11
|
-
import Grid from '@mui/material/Grid';
|
|
12
19
|
import MiniSchedule from '../../assets/mini-schedule.svg';
|
|
13
20
|
import { PlanPrice } from './PlanPrice';
|
|
14
21
|
import { getSelectedTier } from '../utils/priceTierUtils';
|
|
15
22
|
|
|
23
|
+
const PlanOfferingButtonHeight = '66px';
|
|
24
|
+
|
|
16
25
|
const PlanOfferingContainer = styled.div<{ $isHighlighted: boolean; $isCurrentPlan: boolean }>`
|
|
17
26
|
position: relative;
|
|
18
27
|
background-color: ${({ theme, $isCurrentPlan }) =>
|
|
@@ -82,6 +91,7 @@ type PlanOfferingProps = {
|
|
|
82
91
|
paywallLocale: PaywallLocalization;
|
|
83
92
|
locale: string;
|
|
84
93
|
withStartingAtRow: boolean;
|
|
94
|
+
isCustomerInCustomPlan: boolean;
|
|
85
95
|
};
|
|
86
96
|
|
|
87
97
|
const NextPlanTagContainer = styled.div`
|
|
@@ -132,6 +142,7 @@ export function PlanOffering({
|
|
|
132
142
|
paywallLocale,
|
|
133
143
|
locale,
|
|
134
144
|
withStartingAtRow,
|
|
145
|
+
isCustomerInCustomPlan,
|
|
135
146
|
}: PlanOfferingProps) {
|
|
136
147
|
const isNextPlan = plan.isNextPlan && plan.isNextPlan(billingPeriod);
|
|
137
148
|
const planPrices = plan.pricePoints.filter((pricePoint) => pricePoint.billingPeriod === billingPeriod);
|
|
@@ -139,6 +150,7 @@ export function PlanOffering({
|
|
|
139
150
|
(pricePoint) => pricePoint.billingPeriod === billingPeriod,
|
|
140
151
|
);
|
|
141
152
|
const showStartingAt = planPrices.length > 1 && !!paywallCalculatedPrice?.additionalChargesMayApply;
|
|
153
|
+
const showCTAButton = !isCustomerInCustomPlan || plan.pricingType === PricingType.Custom;
|
|
142
154
|
|
|
143
155
|
let planBadge = null;
|
|
144
156
|
if (isNextPlan) {
|
|
@@ -159,7 +171,7 @@ export function PlanOffering({
|
|
|
159
171
|
|
|
160
172
|
useEffect(() => {
|
|
161
173
|
setSelectedTierByFeature(getSelectedTier(plan, billingPeriod, currentSubscription, selectedTierByFeature));
|
|
162
|
-
}, [billingPeriod]);
|
|
174
|
+
}, [billingPeriod, currentSubscription, plan, selectedTierByFeature]);
|
|
163
175
|
|
|
164
176
|
const onPlanButtonClick = (intentionType: SubscribeIntentionType) => {
|
|
165
177
|
const billableFeatures: BillableFeature[] = Object.keys(selectedTierByFeature).map((featureId) => ({
|
|
@@ -205,19 +217,22 @@ export function PlanOffering({
|
|
|
205
217
|
hasMonthlyPrice={hasMonthlyPrice}
|
|
206
218
|
/>
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
{showCTAButton ? (
|
|
221
|
+
<PlanOfferingButton
|
|
222
|
+
isNextPlan={isNextPlan}
|
|
223
|
+
customer={customer}
|
|
224
|
+
plan={plan}
|
|
225
|
+
currentSubscription={currentSubscription}
|
|
226
|
+
billingPeriod={billingPeriod}
|
|
227
|
+
isCustomerOnTrial={isCustomerOnTrial}
|
|
228
|
+
onPlanSelected={onPlanButtonClick}
|
|
229
|
+
paywallLocale={paywallLocale}
|
|
230
|
+
withTrialLeftRow={withTrialLeftRow}
|
|
231
|
+
selectedTierByFeature={selectedTierByFeature}
|
|
232
|
+
/>
|
|
233
|
+
) : (
|
|
234
|
+
<div style={{ height: PlanOfferingButtonHeight }} />
|
|
235
|
+
)}
|
|
221
236
|
|
|
222
237
|
<Divider className="stigg-plan-header-divider" />
|
|
223
238
|
</HeaderWrapper>
|
|
@@ -81,7 +81,6 @@ type PlanOfferingButtonProps = {
|
|
|
81
81
|
isCustomerOnTrial: boolean;
|
|
82
82
|
paywallLocale: PaywallLocalization;
|
|
83
83
|
onPlanSelected: (intentionType: SubscribeIntentionType) => void | Promise<void>;
|
|
84
|
-
hasTiersRow: boolean;
|
|
85
84
|
withTrialLeftRow: boolean;
|
|
86
85
|
selectedTierByFeature: Record<string, PriceTierFragment>;
|
|
87
86
|
};
|
|
@@ -94,7 +93,6 @@ export function PlanOfferingButton({
|
|
|
94
93
|
isCustomerOnTrial,
|
|
95
94
|
onPlanSelected,
|
|
96
95
|
paywallLocale,
|
|
97
|
-
hasTiersRow,
|
|
98
96
|
withTrialLeftRow,
|
|
99
97
|
currentSubscription,
|
|
100
98
|
selectedTierByFeature,
|
|
@@ -208,7 +206,7 @@ export function PlanOfferingButton({
|
|
|
208
206
|
{isLoading && <LoadingIndicator color={theme.stigg.palette.text.disabled} loading size={16} />}
|
|
209
207
|
</OfferingButton>
|
|
210
208
|
|
|
211
|
-
{
|
|
209
|
+
{!withTrialLeftRow ? (
|
|
212
210
|
<div style={{ height: '20px' }} />
|
|
213
211
|
) : (
|
|
214
212
|
<TrialDaysLeft className="stigg-trial-days-left-text" variant="h6" color="secondary">
|