@stigg/react-sdk 4.1.2 → 4.2.1
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/paywall/PlanOffering.d.ts +6 -4
- package/dist/components/paywall/PlanOfferingButton.d.ts +5 -2
- package/dist/components/paywall/PlanPrice.d.ts +18 -0
- package/dist/components/paywall/TiersLayout.d.ts +10 -0
- package/dist/components/paywall/paywallTextOverrides.d.ts +12 -1
- package/dist/components/paywall/planPriceTier.d.ts +10 -0
- package/dist/components/paywall/types.d.ts +4 -2
- package/dist/components/utils/getPaidPriceText.d.ts +3 -2
- package/dist/components/utils/getPlanPrice.d.ts +4 -2
- package/dist/react-sdk.cjs.development.js +593 -288
- 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 +610 -301
- package/dist/react-sdk.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/paywall/Paywall.tsx +17 -3
- package/src/components/paywall/PlanOffering.tsx +37 -104
- package/src/components/paywall/PlanOfferingButton.tsx +37 -11
- package/src/components/paywall/PlanPrice.tsx +152 -0
- package/src/components/paywall/TiersLayout.tsx +74 -0
- package/src/components/paywall/paywallTextOverrides.ts +9 -1
- package/src/components/paywall/planPriceTier.ts +105 -0
- package/src/components/paywall/types.ts +4 -0
- package/src/components/utils/getPaidPriceText.ts +42 -18
- package/src/components/utils/getPlanPrice.ts +10 -2
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.1
|
|
2
|
+
"version": "4.2.1",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@emotion/react": "^11.10.5",
|
|
86
86
|
"@emotion/styled": "^11.10.5",
|
|
87
87
|
"@mui/material": "^5.10.13",
|
|
88
|
-
"@stigg/js-client-sdk": "2.
|
|
88
|
+
"@stigg/js-client-sdk": "2.18.1",
|
|
89
89
|
"@types/styled-components": "^5.1.26",
|
|
90
90
|
"classnames": "^2.3.2",
|
|
91
91
|
"color": "^4.2.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BillingPeriod, Customer, Plan, Subscription } from '@stigg/js-client-sdk';
|
|
1
|
+
import { BillableFeature, BillingPeriod, Customer, Plan, Subscription } from '@stigg/js-client-sdk';
|
|
2
2
|
import React, { useCallback } from 'react';
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
4
|
import { PlanOffering } from './PlanOffering';
|
|
@@ -11,6 +11,7 @@ import { useStiggContext } from '../..';
|
|
|
11
11
|
import { hasPricePointsForPlans } from './utils/hasPricePoints';
|
|
12
12
|
import { getPlansToDisplay } from './utils/getPlansToDisplay';
|
|
13
13
|
import { getPlanPrice } from '../utils/getPlanPrice';
|
|
14
|
+
import { getSelectedTier } from './planPriceTier';
|
|
14
15
|
|
|
15
16
|
const PaywallPlansContainer = styled.div`
|
|
16
17
|
color: ${({ theme }) => theme.stigg.palette.text.primary};
|
|
@@ -74,13 +75,14 @@ export const Paywall = ({
|
|
|
74
75
|
const plansToShow = getPlansToDisplay(plans, selectedBillingPeriod);
|
|
75
76
|
|
|
76
77
|
const handleOnSubscribe = useCallback(
|
|
77
|
-
(plan: Plan, intentionType: SubscribeIntentionType) => {
|
|
78
|
+
(plan: Plan, intentionType: SubscribeIntentionType, billableFeatures: BillableFeature[]) => {
|
|
78
79
|
return onPlanSelected({
|
|
79
80
|
plan,
|
|
80
81
|
customer,
|
|
81
82
|
subscription: currentSubscription,
|
|
82
83
|
intentionType,
|
|
83
84
|
selectedBillingPeriod,
|
|
85
|
+
billableFeatures,
|
|
84
86
|
});
|
|
85
87
|
},
|
|
86
88
|
[customer, selectedBillingPeriod],
|
|
@@ -98,6 +100,14 @@ export const Paywall = ({
|
|
|
98
100
|
return !!getPlanPrice(plan, selectedBillingPeriod, paywallLocale, locale, hasMonthlyPrice).unit;
|
|
99
101
|
});
|
|
100
102
|
|
|
103
|
+
const withTiersRow = plansToShow.some(plan => {
|
|
104
|
+
return !!getSelectedTier(plan, selectedBillingPeriod, currentSubscription, {});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const withTrialLeftRow = plansToShow.some(plan => {
|
|
108
|
+
return plan.isCurrentCustomerPlan && plan.trialDaysLeft;
|
|
109
|
+
});
|
|
110
|
+
|
|
101
111
|
return (
|
|
102
112
|
<PaywallContainer className="stigg-paywall-container">
|
|
103
113
|
<PaywallLayout className="stigg-paywall-layout">
|
|
@@ -112,6 +122,8 @@ export const Paywall = ({
|
|
|
112
122
|
{plansToShow.map(plan => (
|
|
113
123
|
<PlanOffering
|
|
114
124
|
withUnitPriceRow={withUnitPriceRow}
|
|
125
|
+
withTiersRow={withTiersRow}
|
|
126
|
+
withTrialLeftRow={withTrialLeftRow}
|
|
115
127
|
key={plan.id}
|
|
116
128
|
shouldShowDescriptionSection={shouldShowDescriptionSection}
|
|
117
129
|
hasMonthlyPrice={hasMonthlyPrice}
|
|
@@ -122,7 +134,9 @@ export const Paywall = ({
|
|
|
122
134
|
billingPeriod={selectedBillingPeriod}
|
|
123
135
|
isHighlighted={plan.id === highlightedPlanId}
|
|
124
136
|
isCustomerOnTrial={isCustomerOnTrial}
|
|
125
|
-
onPlanSelected={(intentionType: SubscribeIntentionType) =>
|
|
137
|
+
onPlanSelected={(intentionType: SubscribeIntentionType, billableFeatures: BillableFeature[]) =>
|
|
138
|
+
handleOnSubscribe(plan, intentionType, billableFeatures)
|
|
139
|
+
}
|
|
126
140
|
paywallLocale={paywallLocale}
|
|
127
141
|
locale={locale}
|
|
128
142
|
customer={customer}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { BillingPeriod, Customer, Subscription } from '@stigg/js-client-sdk';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { BillableFeature, BillingPeriod, Customer, PriceTierFragment, Subscription } from '@stigg/js-client-sdk';
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
|
-
import { getPlanPrice } from '../utils/getPlanPrice';
|
|
5
4
|
import { PlanEntitlements } from './PlanEntitlements';
|
|
6
5
|
import { PlanOfferingButton } from './PlanOfferingButton';
|
|
7
6
|
import { PaywallPlan, SubscribeIntentionType } from './types';
|
|
@@ -11,8 +10,8 @@ import { Typography } from '../common/Typography';
|
|
|
11
10
|
import classNames from 'classnames';
|
|
12
11
|
import { Grid } from '@mui/material';
|
|
13
12
|
import MiniSchedule from '../../assets/mini-schedule.svg';
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import { PlanPrice } from './PlanPrice';
|
|
14
|
+
import { getSelectedTier } from './planPriceTier';
|
|
16
15
|
|
|
17
16
|
const PlanOfferingContainer = styled.div<{ $isHighlighted: boolean; $isCurrentPlan: boolean }>`
|
|
18
17
|
position: relative;
|
|
@@ -35,99 +34,6 @@ const PlanHeader = styled(Typography)`
|
|
|
35
34
|
padding-bottom: 8px;
|
|
36
35
|
`;
|
|
37
36
|
|
|
38
|
-
const PlanPriceContainer = styled(Typography)`
|
|
39
|
-
word-break: break-word;
|
|
40
|
-
`;
|
|
41
|
-
|
|
42
|
-
const UnitSpan = styled(Typography)`
|
|
43
|
-
white-space: nowrap;
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const PriceSpan = styled(Typography)`
|
|
47
|
-
white-space: normal;
|
|
48
|
-
min-height: 39px;
|
|
49
|
-
`;
|
|
50
|
-
|
|
51
|
-
function PlanPrice({
|
|
52
|
-
showStartingAt,
|
|
53
|
-
withUnitPriceRow,
|
|
54
|
-
withStartingAtRow,
|
|
55
|
-
plan,
|
|
56
|
-
billingPeriod,
|
|
57
|
-
paywallLocale,
|
|
58
|
-
locale,
|
|
59
|
-
hasMonthlyPrice,
|
|
60
|
-
hasAnnuallyPrice,
|
|
61
|
-
}: {
|
|
62
|
-
showStartingAt: boolean;
|
|
63
|
-
withUnitPriceRow: boolean;
|
|
64
|
-
withStartingAtRow: boolean;
|
|
65
|
-
plan: PaywallPlan;
|
|
66
|
-
billingPeriod: BillingPeriod;
|
|
67
|
-
paywallLocale: PaywallLocalization;
|
|
68
|
-
hasMonthlyPrice: boolean;
|
|
69
|
-
hasAnnuallyPrice: boolean;
|
|
70
|
-
locale: string;
|
|
71
|
-
}) {
|
|
72
|
-
const { price, unit } = getPlanPrice(plan, billingPeriod, paywallLocale, locale, hasMonthlyPrice);
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<PlanPriceContainer className="stigg-price-text">
|
|
76
|
-
<>
|
|
77
|
-
{withStartingAtRow && (
|
|
78
|
-
<Typography
|
|
79
|
-
style={{ minHeight: '20px' }}
|
|
80
|
-
className="stigg-starting-at-text"
|
|
81
|
-
variant="body1"
|
|
82
|
-
color="secondary"
|
|
83
|
-
>
|
|
84
|
-
{showStartingAt ? paywallLocale.price.startingAtCaption : EMPTY_CHAR}
|
|
85
|
-
</Typography>
|
|
86
|
-
)}
|
|
87
|
-
|
|
88
|
-
<PriceSpan variant="h1">{price}</PriceSpan>
|
|
89
|
-
|
|
90
|
-
{withUnitPriceRow && (
|
|
91
|
-
<Typography className="stigg-price-unit-and-billing-period-text" style={{ minHeight: '48px' }}>
|
|
92
|
-
<UnitSpan span variant="h6" color="secondary">
|
|
93
|
-
{unit}
|
|
94
|
-
</UnitSpan>
|
|
95
|
-
|
|
96
|
-
<PriceBillingPeriod
|
|
97
|
-
plan={plan}
|
|
98
|
-
billingPeriod={billingPeriod}
|
|
99
|
-
hasAnnuallyPrice={hasAnnuallyPrice}
|
|
100
|
-
hasMonthlyPrice={hasMonthlyPrice}
|
|
101
|
-
/>
|
|
102
|
-
</Typography>
|
|
103
|
-
)}
|
|
104
|
-
</>
|
|
105
|
-
</PlanPriceContainer>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type PriceBillingPeriodProps = {
|
|
110
|
-
plan: PaywallPlan;
|
|
111
|
-
billingPeriod: BillingPeriod;
|
|
112
|
-
hasMonthlyPrice: boolean;
|
|
113
|
-
hasAnnuallyPrice: boolean;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
function PriceBillingPeriod({ plan, billingPeriod, hasMonthlyPrice, hasAnnuallyPrice }: PriceBillingPeriodProps) {
|
|
117
|
-
const hasPrice = plan.pricePoints.find(pricePoint => pricePoint.billingPeriod === billingPeriod);
|
|
118
|
-
|
|
119
|
-
let content = EMPTY_CHAR;
|
|
120
|
-
if (hasPrice && hasMonthlyPrice && hasAnnuallyPrice) {
|
|
121
|
-
content = `, billed ${billingPeriod.toLowerCase()}`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<Typography span className="stigg-price-billing-period-text" variant="h6" color="secondary">
|
|
126
|
-
{content}
|
|
127
|
-
</Typography>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
37
|
const Divider = styled.div`
|
|
132
38
|
height: 2px;
|
|
133
39
|
width: 100%;
|
|
@@ -161,6 +67,8 @@ const HeaderWrapper = styled.div`
|
|
|
161
67
|
|
|
162
68
|
type PlanOfferingProps = {
|
|
163
69
|
withUnitPriceRow: boolean;
|
|
70
|
+
withTiersRow: boolean;
|
|
71
|
+
withTrialLeftRow: boolean;
|
|
164
72
|
customer: Customer | null;
|
|
165
73
|
plan: PaywallPlan;
|
|
166
74
|
billingPeriod: BillingPeriod;
|
|
@@ -170,10 +78,10 @@ type PlanOfferingProps = {
|
|
|
170
78
|
hasAnnuallyPrice: boolean;
|
|
171
79
|
hasMonthlyPrice: boolean;
|
|
172
80
|
isCustomerOnTrial: boolean;
|
|
173
|
-
onPlanSelected: (intentionType: SubscribeIntentionType) => void | Promise<void>;
|
|
81
|
+
onPlanSelected: (intentionType: SubscribeIntentionType, billableFeatures: BillableFeature[]) => void | Promise<void>;
|
|
174
82
|
paywallLocale: PaywallLocalization;
|
|
175
83
|
locale: string;
|
|
176
|
-
withStartingAtRow
|
|
84
|
+
withStartingAtRow: boolean;
|
|
177
85
|
};
|
|
178
86
|
|
|
179
87
|
const NextPlanTagContainer = styled.div`
|
|
@@ -187,7 +95,7 @@ const NextPlanTagContainer = styled.div`
|
|
|
187
95
|
const StyledMiniSchedule = styled(MiniSchedule)<{ $iconsColor?: string }>`
|
|
188
96
|
display: flex;
|
|
189
97
|
height: 100%;
|
|
190
|
-
margin-right:
|
|
98
|
+
margin-right: 4px;
|
|
191
99
|
|
|
192
100
|
path {
|
|
193
101
|
fill: ${({ theme }) => theme.stigg.palette.text.primary};
|
|
@@ -209,6 +117,8 @@ function UpcomingChangeTag({ text = 'Next', iconsColor }: { text?: string; icons
|
|
|
209
117
|
|
|
210
118
|
export function PlanOffering({
|
|
211
119
|
withUnitPriceRow,
|
|
120
|
+
withTiersRow,
|
|
121
|
+
withTrialLeftRow,
|
|
212
122
|
customer,
|
|
213
123
|
plan,
|
|
214
124
|
billingPeriod,
|
|
@@ -243,6 +153,23 @@ export function PlanOffering({
|
|
|
243
153
|
);
|
|
244
154
|
}
|
|
245
155
|
|
|
156
|
+
const [selectedTierByFeature, setSelectedTierByFeature] = React.useState<Record<string, PriceTierFragment>>(
|
|
157
|
+
getSelectedTier(plan, billingPeriod, currentSubscription, {}),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
setSelectedTierByFeature(getSelectedTier(plan, billingPeriod, currentSubscription, selectedTierByFeature));
|
|
162
|
+
}, [billingPeriod]);
|
|
163
|
+
|
|
164
|
+
const onPlanButtonClick = (intentionType: SubscribeIntentionType) => {
|
|
165
|
+
const billableFeatures: BillableFeature[] = Object.keys(selectedTierByFeature).map(featureId => ({
|
|
166
|
+
featureId,
|
|
167
|
+
quantity: selectedTierByFeature[featureId].upTo,
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
return onPlanSelected(intentionType, billableFeatures);
|
|
171
|
+
};
|
|
172
|
+
|
|
246
173
|
return (
|
|
247
174
|
<PlanOfferingContainer
|
|
248
175
|
className={classNames('stigg-plan-offering-container', {
|
|
@@ -266,8 +193,11 @@ export function PlanOffering({
|
|
|
266
193
|
|
|
267
194
|
<PlanPrice
|
|
268
195
|
showStartingAt={showStartingAt}
|
|
269
|
-
withUnitPriceRow={
|
|
270
|
-
withStartingAtRow={
|
|
196
|
+
withUnitPriceRow={withUnitPriceRow}
|
|
197
|
+
withStartingAtRow={withStartingAtRow}
|
|
198
|
+
withTiersRow={withTiersRow}
|
|
199
|
+
selectedTierByFeature={selectedTierByFeature}
|
|
200
|
+
setSelectedTierByFeature={setSelectedTierByFeature}
|
|
271
201
|
plan={plan}
|
|
272
202
|
billingPeriod={billingPeriod}
|
|
273
203
|
paywallLocale={paywallLocale}
|
|
@@ -283,8 +213,11 @@ export function PlanOffering({
|
|
|
283
213
|
currentSubscription={currentSubscription}
|
|
284
214
|
billingPeriod={billingPeriod}
|
|
285
215
|
isCustomerOnTrial={isCustomerOnTrial}
|
|
286
|
-
onPlanSelected={
|
|
216
|
+
onPlanSelected={onPlanButtonClick}
|
|
287
217
|
paywallLocale={paywallLocale}
|
|
218
|
+
hasTiersRow={withTiersRow}
|
|
219
|
+
withTrialLeftRow={withTrialLeftRow}
|
|
220
|
+
selectedTierByFeature={selectedTierByFeature}
|
|
288
221
|
/>
|
|
289
222
|
|
|
290
223
|
<Divider className="stigg-plan-header-divider" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { BillingPeriod, Customer, PricingType, Subscription } from '@stigg/js-client-sdk';
|
|
2
|
+
import { BillingPeriod, Customer, PriceTierFragment, PricingType, Subscription } from '@stigg/js-client-sdk';
|
|
3
3
|
import styled from '@emotion/styled/macro';
|
|
4
4
|
import { css, Theme, useTheme } from '@emotion/react';
|
|
5
5
|
import { PaywallPlan, SubscribeIntentionType } from './types';
|
|
@@ -9,6 +9,7 @@ import ClipLoader from 'react-spinners/ClipLoader';
|
|
|
9
9
|
import { Typography } from '../common/Typography';
|
|
10
10
|
import { getSubscriptionScheduleUpdateTexts } from '../utils/getSubscriptionScheduleUpdateTexts';
|
|
11
11
|
import { isFunction } from 'lodash';
|
|
12
|
+
import { compareSelectedTierToCurrentTier, PriceTierComparison } from './planPriceTier';
|
|
12
13
|
|
|
13
14
|
const LoadingIndicator = styled(ClipLoader)`
|
|
14
15
|
margin-left: 10px;
|
|
@@ -66,7 +67,7 @@ const TrialDaysLeft = styled(Typography)`
|
|
|
66
67
|
const ButtonLayout = styled.div`
|
|
67
68
|
display: flex;
|
|
68
69
|
flex-direction: column;
|
|
69
|
-
margin-top:
|
|
70
|
+
margin-top: 4px;
|
|
70
71
|
align-self: ${({ theme }) => flexLayoutMapper(theme.stigg.layout.ctaAlignment)};
|
|
71
72
|
width: 100%;
|
|
72
73
|
`;
|
|
@@ -80,6 +81,9 @@ type PlanOfferingButtonProps = {
|
|
|
80
81
|
isCustomerOnTrial: boolean;
|
|
81
82
|
paywallLocale: PaywallLocalization;
|
|
82
83
|
onPlanSelected: (intentionType: SubscribeIntentionType) => void | Promise<void>;
|
|
84
|
+
hasTiersRow: boolean;
|
|
85
|
+
withTrialLeftRow: boolean;
|
|
86
|
+
selectedTierByFeature: Record<string, PriceTierFragment>;
|
|
83
87
|
};
|
|
84
88
|
|
|
85
89
|
export function PlanOfferingButton({
|
|
@@ -90,6 +94,10 @@ export function PlanOfferingButton({
|
|
|
90
94
|
isCustomerOnTrial,
|
|
91
95
|
onPlanSelected,
|
|
92
96
|
paywallLocale,
|
|
97
|
+
hasTiersRow,
|
|
98
|
+
withTrialLeftRow,
|
|
99
|
+
currentSubscription,
|
|
100
|
+
selectedTierByFeature,
|
|
93
101
|
}: PlanOfferingButtonProps) {
|
|
94
102
|
const theme = useTheme() as Theme;
|
|
95
103
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -136,8 +144,22 @@ export function PlanOfferingButton({
|
|
|
136
144
|
isSameBillingPeriod ||
|
|
137
145
|
(plan.pricingType && [PricingType.Free, PricingType.Custom].includes(plan.pricingType))
|
|
138
146
|
) {
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
const planComparison = compareSelectedTierToCurrentTier(selectedTierByFeature, currentSubscription);
|
|
148
|
+
switch (planComparison) {
|
|
149
|
+
case PriceTierComparison.Lower:
|
|
150
|
+
buttonProps.intentionType = SubscribeIntentionType.CHANGE_UNIT_QUANTITY;
|
|
151
|
+
buttonProps.title = resolvedDowngrade;
|
|
152
|
+
break;
|
|
153
|
+
case PriceTierComparison.Higher:
|
|
154
|
+
buttonProps.intentionType = SubscribeIntentionType.CHANGE_UNIT_QUANTITY;
|
|
155
|
+
buttonProps.title = resolvedUpgrade;
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
buttonProps.title = currentPlan;
|
|
160
|
+
buttonProps.disabled = true;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
141
163
|
} else {
|
|
142
164
|
buttonProps.title = switchToBillingPeriod(billingPeriod);
|
|
143
165
|
buttonProps.intentionType = SubscribeIntentionType.CHANGE_BILLING_PERIOD;
|
|
@@ -188,13 +210,17 @@ export function PlanOfferingButton({
|
|
|
188
210
|
{isLoading && <LoadingIndicator color={theme.stigg.palette.text.disabled} loading size={16} />}
|
|
189
211
|
</OfferingButton>
|
|
190
212
|
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
{hasTiersRow && !withTrialLeftRow ? (
|
|
214
|
+
<div style={{ height: '20px' }} />
|
|
215
|
+
) : (
|
|
216
|
+
<TrialDaysLeft className="stigg-trial-days-left-text" variant="h6" color="secondary">
|
|
217
|
+
{plan.isCurrentCustomerPlan && plan.trialDaysLeft && (
|
|
218
|
+
<>
|
|
219
|
+
<b>{plan.trialDaysLeft} days</b> left on your free trial
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
</TrialDaysLeft>
|
|
223
|
+
)}
|
|
198
224
|
</>
|
|
199
225
|
)}
|
|
200
226
|
</ButtonLayout>
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { PaywallPlan } from './types';
|
|
2
|
+
import { PaywallLocalization } from './paywallTextOverrides';
|
|
3
|
+
import { getPlanPrice } from '../utils/getPlanPrice';
|
|
4
|
+
import { Typography } from '../common/Typography';
|
|
5
|
+
import React, { useEffect, useState } from 'react';
|
|
6
|
+
import styled from '@emotion/styled/macro';
|
|
7
|
+
import { BillingPeriod, PriceTierFragment } from '@stigg/js-client-sdk';
|
|
8
|
+
import { TiersLayout } from './TiersLayout';
|
|
9
|
+
|
|
10
|
+
const EMPTY_CHAR = '';
|
|
11
|
+
|
|
12
|
+
const PlanPriceContainer = styled(Typography)`
|
|
13
|
+
word-break: break-word;
|
|
14
|
+
width: 100%;
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const UnitSpan = styled(Typography)`
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const PriceSpan = styled(Typography)`
|
|
22
|
+
white-space: normal;
|
|
23
|
+
min-height: 39px;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
type PriceBillingPeriodProps = {
|
|
27
|
+
plan: PaywallPlan;
|
|
28
|
+
billingPeriod: BillingPeriod;
|
|
29
|
+
hasMonthlyPrice: boolean;
|
|
30
|
+
hasAnnuallyPrice: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function PriceBillingPeriod({ plan, billingPeriod, hasMonthlyPrice, hasAnnuallyPrice }: PriceBillingPeriodProps) {
|
|
34
|
+
const hasPrice = plan.pricePoints.find(pricePoint => pricePoint.billingPeriod === billingPeriod);
|
|
35
|
+
|
|
36
|
+
let content = EMPTY_CHAR;
|
|
37
|
+
if (hasPrice && hasMonthlyPrice && hasAnnuallyPrice) {
|
|
38
|
+
content = `, billed ${billingPeriod.toLowerCase()}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Typography span className="stigg-price-billing-period-text" variant="h6" color="secondary">
|
|
43
|
+
{content}
|
|
44
|
+
</Typography>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const PlanPrice = ({
|
|
49
|
+
showStartingAt,
|
|
50
|
+
withUnitPriceRow,
|
|
51
|
+
withStartingAtRow,
|
|
52
|
+
withTiersRow,
|
|
53
|
+
selectedTierByFeature,
|
|
54
|
+
setSelectedTierByFeature,
|
|
55
|
+
plan,
|
|
56
|
+
billingPeriod,
|
|
57
|
+
paywallLocale,
|
|
58
|
+
locale,
|
|
59
|
+
hasMonthlyPrice,
|
|
60
|
+
hasAnnuallyPrice,
|
|
61
|
+
}: {
|
|
62
|
+
showStartingAt: boolean;
|
|
63
|
+
withUnitPriceRow: boolean;
|
|
64
|
+
withStartingAtRow: boolean;
|
|
65
|
+
withTiersRow: boolean;
|
|
66
|
+
selectedTierByFeature: Record<string, PriceTierFragment>;
|
|
67
|
+
setSelectedTierByFeature: (selectedTierByFeature: Record<string, PriceTierFragment>) => void;
|
|
68
|
+
plan: PaywallPlan;
|
|
69
|
+
billingPeriod: BillingPeriod;
|
|
70
|
+
paywallLocale: PaywallLocalization;
|
|
71
|
+
hasMonthlyPrice: boolean;
|
|
72
|
+
hasAnnuallyPrice: boolean;
|
|
73
|
+
locale: string;
|
|
74
|
+
}) => {
|
|
75
|
+
const { price, unit, tiers, tierUnits } = getPlanPrice(
|
|
76
|
+
plan,
|
|
77
|
+
billingPeriod,
|
|
78
|
+
paywallLocale,
|
|
79
|
+
locale,
|
|
80
|
+
hasMonthlyPrice,
|
|
81
|
+
selectedTierByFeature,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const [selectedTier, setSelectedTier] = useState<PriceTierFragment>();
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setSelectedTier(selectedTierByFeature[featureId!]);
|
|
88
|
+
}, [selectedTierByFeature]);
|
|
89
|
+
|
|
90
|
+
// We currently only support prices with one tier - so we select the first one
|
|
91
|
+
const tieredPrice = plan.pricePoints.find(planPrice => {
|
|
92
|
+
return planPrice.billingPeriod === billingPeriod && planPrice.isTieredPrice;
|
|
93
|
+
});
|
|
94
|
+
const featureId = tieredPrice ? tieredPrice!.feature!.featureId : undefined;
|
|
95
|
+
|
|
96
|
+
const handleTierChange = (tier: PriceTierFragment) => {
|
|
97
|
+
const updatedTierByFeature: Record<string, PriceTierFragment> = {};
|
|
98
|
+
updatedTierByFeature[featureId!] = tier;
|
|
99
|
+
|
|
100
|
+
setSelectedTierByFeature(updatedTierByFeature);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (tiers) {
|
|
105
|
+
handleTierChange(selectedTierByFeature[featureId!]);
|
|
106
|
+
}
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<PlanPriceContainer className="stigg-price-text">
|
|
111
|
+
<>
|
|
112
|
+
{withStartingAtRow && (
|
|
113
|
+
<Typography
|
|
114
|
+
style={{ minHeight: '20px' }}
|
|
115
|
+
className="stigg-starting-at-text"
|
|
116
|
+
variant="body1"
|
|
117
|
+
color="secondary"
|
|
118
|
+
>
|
|
119
|
+
{showStartingAt ? paywallLocale.price.startingAtCaption : EMPTY_CHAR}
|
|
120
|
+
</Typography>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<PriceSpan variant="h1">{price}</PriceSpan>
|
|
124
|
+
|
|
125
|
+
{withUnitPriceRow && (
|
|
126
|
+
<Typography className="stigg-price-unit-and-billing-period-text" style={{ minHeight: '48px' }}>
|
|
127
|
+
<UnitSpan span variant="h6" color="secondary">
|
|
128
|
+
{unit}
|
|
129
|
+
</UnitSpan>
|
|
130
|
+
|
|
131
|
+
<PriceBillingPeriod
|
|
132
|
+
plan={plan}
|
|
133
|
+
billingPeriod={billingPeriod}
|
|
134
|
+
hasAnnuallyPrice={hasAnnuallyPrice}
|
|
135
|
+
hasMonthlyPrice={hasMonthlyPrice}
|
|
136
|
+
/>
|
|
137
|
+
</Typography>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{withTiersRow && (
|
|
141
|
+
<TiersLayout
|
|
142
|
+
plan={plan}
|
|
143
|
+
tiers={tiers}
|
|
144
|
+
tierUnits={tierUnits}
|
|
145
|
+
selectedTier={selectedTier}
|
|
146
|
+
handleTierChange={handleTierChange}
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
</>
|
|
150
|
+
</PlanPriceContainer>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { PaywallPlan } from './types';
|
|
2
|
+
import { PriceTierFragment } from '@stigg/js-client-sdk';
|
|
3
|
+
import { MenuItem, OutlinedInput, Select, SelectChangeEvent } from '@mui/material';
|
|
4
|
+
import { Typography } from '../common/Typography';
|
|
5
|
+
import { map } from 'lodash';
|
|
6
|
+
import React, { ReactNode } from 'react';
|
|
7
|
+
import styled from '@emotion/styled/macro';
|
|
8
|
+
|
|
9
|
+
const TierSelect = styled(Select)`
|
|
10
|
+
border-radius: 10px;
|
|
11
|
+
|
|
12
|
+
&:hover .MuiOutlinedInput-notchedOutline {
|
|
13
|
+
border-color: ${({ theme }) => theme.stigg.palette.outlinedBorder};
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const TierInput = styled(OutlinedInput)`
|
|
18
|
+
& .MuiInputBase-input {
|
|
19
|
+
padding: 7px 12px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.Mui-focused .MuiOutlinedInput-notchedOutline {
|
|
23
|
+
border-color: ${({ theme }) => theme.stigg.palette.primary} !important;
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export function TiersLayout({
|
|
28
|
+
plan,
|
|
29
|
+
tiers,
|
|
30
|
+
tierUnits,
|
|
31
|
+
selectedTier,
|
|
32
|
+
handleTierChange,
|
|
33
|
+
}: {
|
|
34
|
+
plan: PaywallPlan;
|
|
35
|
+
tiers?: PriceTierFragment[] | null;
|
|
36
|
+
tierUnits?: string;
|
|
37
|
+
selectedTier?: PriceTierFragment;
|
|
38
|
+
handleTierChange: (tier: PriceTierFragment) => void;
|
|
39
|
+
}) {
|
|
40
|
+
const handleChange = (event: SelectChangeEvent<unknown>, _: ReactNode) => {
|
|
41
|
+
handleTierChange(tiers!.find(tier => tier.upTo.toString() === event.target.value)!);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Typography className="stigg-price-tier-select" style={{ minHeight: '46px' }}>
|
|
46
|
+
{tiers ? (
|
|
47
|
+
<TierSelect
|
|
48
|
+
labelId="demo-simple-select-label"
|
|
49
|
+
id={`select_${plan.id}`}
|
|
50
|
+
value={selectedTier ? selectedTier.upTo.toString() : tiers[0].upTo.toString()}
|
|
51
|
+
fullWidth
|
|
52
|
+
onChange={handleChange}
|
|
53
|
+
input={<TierInput />}
|
|
54
|
+
MenuProps={{
|
|
55
|
+
MenuListProps: { disablePadding: true },
|
|
56
|
+
PaperProps: {
|
|
57
|
+
sx: { marginTop: '4px', borderRadius: '10px' },
|
|
58
|
+
},
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{map(tiers, (tier: PriceTierFragment) => (
|
|
62
|
+
<MenuItem className="stigg-price-tier-menu-item-text" key={tier.upTo} value={tier.upTo.toString()}>
|
|
63
|
+
<Typography variant="body1" color="primary">
|
|
64
|
+
{tier.upTo} {tierUnits}
|
|
65
|
+
</Typography>
|
|
66
|
+
</MenuItem>
|
|
67
|
+
))}
|
|
68
|
+
</TierSelect>
|
|
69
|
+
) : (
|
|
70
|
+
<div />
|
|
71
|
+
)}
|
|
72
|
+
</Typography>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BillingPeriod,
|
|
3
|
+
PaywallCalculatedPricePoint,
|
|
4
|
+
PaywallCurrency,
|
|
5
|
+
Plan,
|
|
6
|
+
Price,
|
|
7
|
+
PriceTierFragment,
|
|
8
|
+
} from '@stigg/js-client-sdk';
|
|
2
9
|
import merge from 'lodash/merge';
|
|
3
10
|
import { DeepPartial } from '../../types';
|
|
4
11
|
import { PlanPriceText } from '../utils/getPlanPrice';
|
|
@@ -27,6 +34,7 @@ export type PaywallLocalization = {
|
|
|
27
34
|
paywallCalculatedPrice?: PaywallCalculatedPricePoint;
|
|
28
35
|
plan: Plan;
|
|
29
36
|
selectedBillingPeriod: BillingPeriod;
|
|
37
|
+
selectedTier?: PriceTierFragment;
|
|
30
38
|
}) => PlanPriceText;
|
|
31
39
|
};
|
|
32
40
|
};
|