@stigg/react-sdk 5.28.4 → 5.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.28.4",
2
+ "version": "5.29.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -10,6 +10,7 @@ import {
10
10
  PaywallPlan,
11
11
  SubscribeIntentionType,
12
12
  SelectDefaultTierIndexFn,
13
+ CurrentSubscriptionOverride,
13
14
  } from './types';
14
15
  import { PaywallLocalization } from './paywallTextOverrides';
15
16
  import { PoweredByStigg } from '../common/PoweredByStigg';
@@ -49,6 +50,7 @@ type PaywallProps = {
49
50
  plans: PaywallPlan[];
50
51
  customer: Customer | null;
51
52
  currentSubscription: Subscription | null;
53
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
52
54
  selectedBillingPeriod: BillingPeriod;
53
55
  highlightedPlanId?: string;
54
56
  onBillingPeriodChanged: (billingPeriod: BillingPeriod) => void;
@@ -75,6 +77,7 @@ export const Paywall = ({
75
77
  locale,
76
78
  shouldHidePlan,
77
79
  selectDefaultTierIndex,
80
+ currentSubscriptionOverride,
78
81
  }: PaywallProps) => {
79
82
  const { stigg } = useStiggContext();
80
83
  const discountRate = calculatePaywallDiscountRate(plans);
@@ -123,11 +126,16 @@ export const Paywall = ({
123
126
  return (
124
127
  !isCustomerInCustomPlan &&
125
128
  plansToShow.some((plan) => {
126
- const tiers = getTiersPerUnitQuantities(plan, selectedBillingPeriod, currentSubscription);
129
+ const tiers = getTiersPerUnitQuantities({
130
+ plan,
131
+ billingPeriod: selectedBillingPeriod,
132
+ currentSubscription,
133
+ currentSubscriptionOverride,
134
+ });
127
135
  return Object.values(tiers).length > 0;
128
136
  })
129
137
  );
130
- }, [selectedBillingPeriod, currentSubscription, isCustomerInCustomPlan, plansToShow]);
138
+ }, [selectedBillingPeriod, currentSubscription, currentSubscriptionOverride, isCustomerInCustomPlan, plansToShow]);
131
139
 
132
140
  const withTrialLeftRow = plansToShow.some((plan) => {
133
141
  return plan.isCurrentCustomerPlan && plan.trialDaysLeft;
@@ -156,6 +164,7 @@ export const Paywall = ({
156
164
  plan={plan}
157
165
  withStartingAtRow={withStartingAtRow}
158
166
  currentSubscription={currentSubscription}
167
+ currentSubscriptionOverride={currentSubscriptionOverride}
159
168
  billingPeriod={selectedBillingPeriod}
160
169
  isHighlighted={plan.id === highlightedPlanId}
161
170
  isCustomerOnTrial={isCustomerOnTrial}
@@ -2,7 +2,12 @@ import React from 'react';
2
2
  import { BillingPeriod } from '@stigg/js-client-sdk';
3
3
  import { Paywall } from './Paywall';
4
4
  import { useLoadPaywallData } from './hooks/useLoadPaywallData';
5
- import { ShouldHidePlanFn, OnPlanSelectedCallbackFn, SelectDefaultTierIndexFn } from './types';
5
+ import {
6
+ ShouldHidePlanFn,
7
+ OnPlanSelectedCallbackFn,
8
+ SelectDefaultTierIndexFn,
9
+ CurrentSubscriptionOverrideFn,
10
+ } from './types';
6
11
  import { getResolvedPaywallLocalize, PaywallLocalization } from './paywallTextOverrides';
7
12
  import { DeepPartial } from '../../types';
8
13
  import { PaywallLoader } from './PaywallLoader';
@@ -25,6 +30,7 @@ export type PaywallContainerProps = {
25
30
  billingCountryCode?: string;
26
31
  shouldHidePlan?: ShouldHidePlanFn;
27
32
  selectDefaultTierIndex?: SelectDefaultTierIndexFn;
33
+ currentSubscriptionOverride?: CurrentSubscriptionOverrideFn;
28
34
  };
29
35
 
30
36
  export const PaywallContainer = ({
@@ -39,6 +45,7 @@ export const PaywallContainer = ({
39
45
  billingCountryCode,
40
46
  shouldHidePlan,
41
47
  selectDefaultTierIndex,
48
+ currentSubscriptionOverride: currentSubscriptionOverrideFn,
42
49
  }: PaywallContainerProps) => {
43
50
  const hasCustomerPortalContext = useCheckContextExists(CustomerPortalContext);
44
51
  let isCustomerPortalLoading = false;
@@ -52,6 +59,7 @@ export const PaywallContainer = ({
52
59
  plans,
53
60
  customer,
54
61
  currentSubscription,
62
+ currentSubscriptionOverride,
55
63
  isCustomerOnTrial,
56
64
  isLoading,
57
65
  selectedBillingPeriod,
@@ -65,6 +73,7 @@ export const PaywallContainer = ({
65
73
  showOnlyEligiblePlans,
66
74
  billingCountryCode,
67
75
  preferredBillingPeriod,
76
+ currentSubscriptionOverrideFn,
68
77
  });
69
78
  const paywallLocale = getResolvedPaywallLocalize(textOverrides);
70
79
  const handlePeriodChange = (billingPeriod: BillingPeriod) => {
@@ -82,6 +91,7 @@ export const PaywallContainer = ({
82
91
  plans={plans}
83
92
  customer={customer}
84
93
  currentSubscription={currentSubscription}
94
+ currentSubscriptionOverride={currentSubscriptionOverride}
85
95
  selectedBillingPeriod={selectedBillingPeriod}
86
96
  onBillingPeriodChanged={handlePeriodChange}
87
97
  availableBillingPeriods={availableBillingPeriods}
@@ -5,7 +5,7 @@ import classNames from 'classnames';
5
5
  import Grid from '@mui/material/Grid';
6
6
  import { PlanEntitlements } from './PlanEntitlements';
7
7
  import { PlanOfferingButton } from './PlanOfferingButton';
8
- import { PaywallPlan, SelectDefaultTierIndexFn, SubscribeIntentionType } from './types';
8
+ import { CurrentSubscriptionOverride, PaywallPlan, SelectDefaultTierIndexFn, SubscribeIntentionType } from './types';
9
9
  import { PaywallLocalization } from './paywallTextOverrides';
10
10
  import { flexLayoutMapper } from '../../theme/getResolvedTheme';
11
11
  import { Typography } from '../common/Typography';
@@ -75,6 +75,7 @@ type PlanOfferingProps = {
75
75
  plan: PaywallPlan;
76
76
  billingPeriod: BillingPeriod;
77
77
  currentSubscription: Subscription | null;
78
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
78
79
  isHighlighted: boolean;
79
80
  shouldShowDescriptionSection: boolean;
80
81
  hasAnnuallyPrice: boolean;
@@ -128,6 +129,7 @@ export function PlanOffering({
128
129
  billingPeriod,
129
130
  isHighlighted,
130
131
  currentSubscription,
132
+ currentSubscriptionOverride,
131
133
  shouldShowDescriptionSection,
132
134
  hasMonthlyPrice,
133
135
  hasAnnuallyPrice,
@@ -161,7 +163,13 @@ export function PlanOffering({
161
163
  }
162
164
 
163
165
  const [perUnitQuantityByFeature, setPerUnitQuantityByFeature] = useState<Record<string, number>>(
164
- getTiersPerUnitQuantities(plan, billingPeriod, currentSubscription, selectDefaultTierIndex),
166
+ getTiersPerUnitQuantities({
167
+ plan,
168
+ billingPeriod,
169
+ currentSubscription,
170
+ currentSubscriptionOverride,
171
+ selectDefaultTierIndex,
172
+ }),
165
173
  );
166
174
 
167
175
  const onPlanButtonClick = (intentionType: SubscribeIntentionType) => {
@@ -214,6 +222,7 @@ export function PlanOffering({
214
222
  customer={customer}
215
223
  plan={plan}
216
224
  currentSubscription={currentSubscription}
225
+ currentSubscriptionOverride={currentSubscriptionOverride}
217
226
  billingPeriod={billingPeriod}
218
227
  isCustomerOnTrial={isCustomerOnTrial}
219
228
  onPlanSelected={onPlanButtonClick}
@@ -4,7 +4,7 @@ import { isFunction } from 'lodash';
4
4
  import ClipLoader from 'react-spinners/ClipLoader';
5
5
  import styled from '@emotion/styled/macro';
6
6
  import { css, useTheme } from '@emotion/react';
7
- import { PaywallPlan, SubscribeIntentionType } from './types';
7
+ import { CurrentSubscriptionOverride, PaywallPlan, SubscribeIntentionType } from './types';
8
8
  import { PaywallLocalization } from './paywallTextOverrides';
9
9
  import { flexLayoutMapper } from '../../theme/getResolvedTheme';
10
10
  import { Typography } from '../common/Typography';
@@ -77,6 +77,7 @@ type PlanOfferingButtonProps = {
77
77
  customer: Customer | null;
78
78
  plan: PaywallPlan;
79
79
  currentSubscription: Subscription | null;
80
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
80
81
  billingPeriod: BillingPeriod;
81
82
  isCustomerOnTrial: boolean;
82
83
  paywallLocale: PaywallLocalization;
@@ -95,6 +96,7 @@ export function PlanOfferingButton({
95
96
  paywallLocale,
96
97
  withTrialLeftRow,
97
98
  currentSubscription,
99
+ currentSubscriptionOverride,
98
100
  perUnitQuantityByFeature,
99
101
  }: PlanOfferingButtonProps) {
100
102
  const theme = useTheme();
@@ -142,7 +144,12 @@ export function PlanOfferingButton({
142
144
  isSameBillingPeriod ||
143
145
  (plan.pricingType && [PricingType.Free, PricingType.Custom].includes(plan.pricingType))
144
146
  ) {
145
- const planComparison = compareSelectedTierToCurrentTier(perUnitQuantityByFeature, currentSubscription);
147
+ const planComparison = compareSelectedTierToCurrentTier({
148
+ perUnitQuantityByFeature,
149
+ plan,
150
+ currentSubscription,
151
+ currentSubscriptionOverride,
152
+ });
146
153
  switch (planComparison) {
147
154
  case PriceTierComparison.Lower:
148
155
  buttonProps.intentionType = SubscribeIntentionType.CHANGE_UNIT_QUANTITY;
@@ -1,7 +1,7 @@
1
1
  import { BillingPeriod, Paywall } from '@stigg/js-client-sdk';
2
2
  import { useEffect, useState } from 'react';
3
3
  import logger from '../../../services/logger';
4
- import { PaywallData } from '../types';
4
+ import { CurrentSubscriptionOverrideFn, PaywallData } from '../types';
5
5
  import { computeBillingPeriods } from '../utils/computeDefaultBillingPeriod';
6
6
  import { mapPaywallData } from '../utils/mapPaywallData';
7
7
  import { useStiggContext } from '../../../hooks/useStiggContext';
@@ -12,6 +12,7 @@ type UseLoadPaywallDataProps = {
12
12
  showOnlyEligiblePlans?: boolean;
13
13
  billingCountryCode?: string;
14
14
  preferredBillingPeriod?: BillingPeriod;
15
+ currentSubscriptionOverrideFn?: CurrentSubscriptionOverrideFn;
15
16
  };
16
17
 
17
18
  export function useLoadPaywallData({
@@ -20,6 +21,7 @@ export function useLoadPaywallData({
20
21
  showOnlyEligiblePlans,
21
22
  billingCountryCode,
22
23
  preferredBillingPeriod,
24
+ currentSubscriptionOverrideFn,
23
25
  }: UseLoadPaywallDataProps): PaywallData {
24
26
  const { stigg, locale } = useStiggContext();
25
27
  const [selectedBillingPeriod, setSelectedBillingPeriod] = useState(BillingPeriod.Annually);
@@ -57,7 +59,7 @@ export function useLoadPaywallData({
57
59
  void loadPaywall();
58
60
  }, [stigg, productId, stigg.isCustomerLoaded, billingCountryCode, resourceId, preferredBillingPeriod]);
59
61
 
60
- const paywallData = mapPaywallData(paywall, showOnlyEligiblePlans);
62
+ const paywallData = mapPaywallData(paywall, showOnlyEligiblePlans, currentSubscriptionOverrideFn);
61
63
 
62
64
  return {
63
65
  customer: paywall?.customer || null,
@@ -14,6 +14,7 @@ export type PaywallData = {
14
14
  plans: PaywallPlan[] | null;
15
15
  customer: Customer | null;
16
16
  currentSubscription: Subscription | null;
17
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
17
18
  isCustomerOnTrial: boolean;
18
19
  isLoading: boolean;
19
20
  selectedBillingPeriod: BillingPeriod;
@@ -23,6 +24,11 @@ export type PaywallData = {
23
24
  configuration?: CustomizedTheme;
24
25
  };
25
26
 
27
+ export type CurrentSubscriptionOverride = {
28
+ planId: string;
29
+ billableFeatures: BillableFeature[];
30
+ };
31
+
26
32
  export enum SubscribeIntentionType {
27
33
  START_TRIAL = 'START_TRIAL',
28
34
  UPGRADE_TRIAL_TO_PAID = 'UPGRADE_TRIAL_TO_PAID',
@@ -64,4 +70,10 @@ export type OnPlanSelectedCallbackFn = ({
64
70
 
65
71
  export type ShouldHidePlanFn = ({ plan }: { plan: PaywallPlan }) => boolean | Promise<boolean>;
66
72
 
73
+ export type CurrentSubscriptionOverrideFn = ({
74
+ currentSubscription,
75
+ }: {
76
+ currentSubscription: Subscription | null;
77
+ }) => CurrentSubscriptionOverride | null | undefined;
78
+
67
79
  export type SelectDefaultTierIndexFn = ({ plan }: { plan: PaywallPlan }) => number;
@@ -8,7 +8,7 @@ import {
8
8
  } from '@stigg/js-client-sdk';
9
9
  import isNil from 'lodash/isNil';
10
10
  import sortBy from 'lodash/sortBy';
11
- import { PaywallPlan } from '../types';
11
+ import { CurrentSubscriptionOverride, CurrentSubscriptionOverrideFn, PaywallPlan } from '../types';
12
12
  import { calculateTrialDaysLeft } from './calculateTrialDaysLeft';
13
13
  import { BillingPeriodChangeVariables, DeepPartial, DowngradeChangeVariables } from '../../../types';
14
14
  import { StiggTheme } from '../../../theme/types';
@@ -39,16 +39,23 @@ function getCustomerSubscriptionDetails(activeSubscriptions?: Subscription[] | n
39
39
  type PaywallData = {
40
40
  currentPlan?: Plan;
41
41
  currentSubscription: Subscription | null;
42
+ currentSubscriptionOverride: CurrentSubscriptionOverride | null | undefined;
42
43
  isCustomerOnTrial: boolean;
43
44
  plans: PaywallPlan[];
44
45
  paywallConfiguration?: DeepPartial<StiggTheme>;
45
46
  };
46
47
 
47
- export function mapPaywallData(paywall: Paywall | null, showOnlyEligiblePlans?: boolean): PaywallData {
48
+ export function mapPaywallData(
49
+ paywall: Paywall | null,
50
+ showOnlyEligiblePlans?: boolean,
51
+ currentSubscriptionOverrideFn?: CurrentSubscriptionOverrideFn,
52
+ ): PaywallData {
48
53
  const { plans, currency, configuration, customer, activeSubscriptions, paywallCalculatedPricePoints } = paywall || {};
49
54
  const { currentSubscription, currentPlan, isCustomerOnTrial, trialDaysLeft } =
50
55
  getCustomerSubscriptionDetails(activeSubscriptions);
51
56
 
57
+ const currentSubscriptionOverride = currentSubscriptionOverrideFn?.({ currentSubscription });
58
+
52
59
  const scheduledUpdates = currentSubscription?.scheduledUpdates || [];
53
60
  const currentCustomerPlanBillingPeriod = currentSubscription?.price?.billingPeriod;
54
61
  const downgradeSchedule = scheduledUpdates.find(
@@ -62,7 +69,9 @@ export function mapPaywallData(paywall: Paywall | null, showOnlyEligiblePlans?:
62
69
  const eligibleForProductTrial = customer?.eligibleForTrial?.find(
63
70
  (productTrial) => productTrial.productId === plan.product.id,
64
71
  );
65
- const isCurrentCustomerPlan = plan.id === currentPlan?.id;
72
+ const isCurrentCustomerPlan = currentSubscriptionOverride?.planId
73
+ ? currentSubscriptionOverride?.planId === plan.id
74
+ : plan.id === currentPlan?.id;
66
75
 
67
76
  const isNextPlan = (currentBillingPeriod: BillingPeriod) => {
68
77
  const downgradeVariables = downgradeSchedule?.scheduleVariables as DowngradeChangeVariables;
@@ -109,6 +118,7 @@ export function mapPaywallData(paywall: Paywall | null, showOnlyEligiblePlans?:
109
118
  return {
110
119
  currentPlan,
111
120
  currentSubscription,
121
+ currentSubscriptionOverride,
112
122
  isCustomerOnTrial,
113
123
  plans: paywallPlans,
114
124
  paywallConfiguration,
@@ -2,7 +2,7 @@ import { BillingModel, TiersMode, BillingPeriod, Price, PriceTierFragment, Subsc
2
2
  import isNil from 'lodash/isNil';
3
3
  import { sum } from 'lodash';
4
4
  import { PaywallPlan } from '../paywall';
5
- import { SelectDefaultTierIndexFn } from '../paywall/types';
5
+ import { CurrentSubscriptionOverride, SelectDefaultTierIndexFn } from '../paywall/types';
6
6
 
7
7
  export function getPriceFeatureUnit(price: Price) {
8
8
  if (!price.feature) {
@@ -131,12 +131,19 @@ export function isQuantityInFirstTier(tiers: PriceTierFragment[] | null | undefi
131
131
  return tiers?.[0].upTo && quantity <= tiers[0].upTo;
132
132
  }
133
133
 
134
- export function getTiersPerUnitQuantities(
135
- plan: PaywallPlan,
136
- billingPeriod: BillingPeriod,
137
- currentSubscription: Subscription | null,
138
- selectDefaultTierIndex?: SelectDefaultTierIndexFn,
139
- ): Record<string, number> {
134
+ export function getTiersPerUnitQuantities({
135
+ plan,
136
+ billingPeriod,
137
+ currentSubscription,
138
+ currentSubscriptionOverride,
139
+ selectDefaultTierIndex,
140
+ }: {
141
+ plan: PaywallPlan;
142
+ billingPeriod: BillingPeriod;
143
+ currentSubscription: Subscription | null;
144
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
145
+ selectDefaultTierIndex?: SelectDefaultTierIndexFn;
146
+ }): Record<string, number> {
140
147
  const planTierPrices = plan.pricePoints.filter(
141
148
  (price) => price.billingPeriod === billingPeriod && price.isTieredPrice,
142
149
  );
@@ -161,6 +168,15 @@ export function getTiersPerUnitQuantities(
161
168
  }
162
169
  }
163
170
 
171
+ if (currentSubscriptionOverride?.planId === plan.id) {
172
+ const billableFeature = currentSubscriptionOverride.billableFeatures.find(
173
+ (billableFeature) => billableFeature.featureId === featureId,
174
+ );
175
+ if (billableFeature) {
176
+ quantity = billableFeature.quantity;
177
+ }
178
+ }
179
+
164
180
  const result: Record<string, number> = {};
165
181
  result[featureId] = quantity;
166
182
 
@@ -176,22 +192,47 @@ export enum PriceTierComparison {
176
192
  Higher = 1,
177
193
  }
178
194
 
179
- export function compareSelectedTierToCurrentTier(
180
- perUnitQuantityByFeature: Record<string, number>,
181
- currentSubscription: Subscription | null,
182
- ): PriceTierComparison {
195
+ export function compareSelectedTierToCurrentTier({
196
+ perUnitQuantityByFeature,
197
+ plan,
198
+ currentSubscription,
199
+ currentSubscriptionOverride,
200
+ }: {
201
+ perUnitQuantityByFeature: Record<string, number>;
202
+ plan: PaywallPlan;
203
+ currentSubscription: Subscription | null;
204
+ currentSubscriptionOverride?: CurrentSubscriptionOverride | null;
205
+ }): PriceTierComparison {
183
206
  if (!currentSubscription) {
184
207
  return PriceTierComparison.Equal;
185
208
  }
186
209
 
187
- const currentTierPrice = currentSubscription.prices.find(
210
+ let currentTierPrice = currentSubscription.prices.find(
188
211
  (price) => price.pricingModel === BillingModel.PerUnit && price.tiersMode,
189
212
  );
213
+
214
+ const isCurrentPlanOverride = plan.id === currentSubscriptionOverride?.planId;
215
+ if (isCurrentPlanOverride) {
216
+ currentTierPrice =
217
+ plan.pricePoints.find((price) => price.pricingModel === BillingModel.PerUnit && price.tiersMode) ??
218
+ currentTierPrice;
219
+ }
220
+
190
221
  if (!currentTierPrice) {
191
222
  return PriceTierComparison.Equal;
192
223
  }
193
224
 
194
- const { featureId, unitQuantity: oldQuantity } = currentTierPrice.feature!;
225
+ const { featureId, unitQuantity } = currentTierPrice.feature!;
226
+ let oldQuantity = unitQuantity;
227
+
228
+ if (isCurrentPlanOverride) {
229
+ const billableFeature = currentSubscriptionOverride?.billableFeatures?.find(
230
+ (billableFeature) => billableFeature.featureId === featureId,
231
+ );
232
+ if (billableFeature) {
233
+ oldQuantity = billableFeature.quantity;
234
+ }
235
+ }
195
236
 
196
237
  if (isNil(oldQuantity)) {
197
238
  return PriceTierComparison.Equal;