@stigg/react-sdk 5.36.0 → 6.1.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.
@@ -6,3 +6,4 @@ export declare const CustomerPaywall: import("@storybook/csf").AnnotatedStoryFn<
6
6
  export declare const CustomerUpgradePaywall: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, any>;
7
7
  export declare const CustomerAsyncCTA: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, any>;
8
8
  export declare const CustomerResourcePaywall: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, any>;
9
+ export declare const PaywallWithAddons: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, any>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.36.0",
2
+ "version": "6.1.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -114,7 +114,7 @@
114
114
  "@emotion/react": "^11.10.5",
115
115
  "@emotion/styled": "^11.10.5",
116
116
  "@mui/material": "^5.12.0",
117
- "@stigg/js-client-sdk": "3.44.0",
117
+ "@stigg/js-client-sdk": "3.45.0",
118
118
  "@stripe/react-stripe-js": "^2.1.1",
119
119
  "@stripe/stripe-js": "^1.54.1",
120
120
  "@types/styled-components": "^5.1.26",
@@ -124,10 +124,10 @@
124
124
  "immer": "^10.0.2",
125
125
  "lodash": "^4.17.21",
126
126
  "lodash-es": "^4.17.21",
127
- "react-lottie": "1.2.10",
128
127
  "moment": "^2.29.4",
129
128
  "react-feather": "^2.0.10",
130
129
  "react-loading-skeleton": "^3.1.0",
130
+ "react-lottie": "1.2.10",
131
131
  "react-spinners": "^0.13.3",
132
132
  "react-switch": "^7.0.0",
133
133
  "styled-components": "^5.3.6",
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12 3V21.0001M3 11.9446H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -2,4 +2,4 @@
2
2
  <g id="check">
3
3
  <path id="Shape" d="M20 7L9 18L4 13" stroke="#F5F6F9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
4
  </g>
5
- </svg>
5
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 11.1429L9.71572 17.8586C9.79383 17.9367 9.92046 17.9367 9.99856 17.8586L21.8571 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 12H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -19,3 +19,6 @@ export { default as Close } from '../../assets/close.svg';
19
19
  export { default as Check } from '../../assets/check.svg';
20
20
  export { default as PayAsYouGoCharge } from '../../assets/pay-as-you-go-charge.svg';
21
21
  export { default as Coupon } from '../../assets/coupon.svg';
22
+ export { default as Add } from '../../assets/add.svg';
23
+ export { default as Remove } from '../../assets/remove.svg';
24
+ export { default as EntitlementCheck } from '../../assets/entitlement-check.svg';
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import styled from '@emotion/styled/macro';
3
3
  import { EntitlementResetPeriod } from '@stigg/js-client-sdk';
4
- import CheckUrl from '../../assets/check-stigg.svg';
5
4
  import { calculateUnitQuantityText } from './utils/calculateUnitQuantityText';
6
5
  import { formatNumber } from '../utils/formatNumber';
7
6
  import { Typography } from '../common/Typography';
7
+ import { Icon } from '../common/Icon';
8
8
 
9
9
  const EntitlementName = styled(Typography)`
10
10
  margin: 0;
@@ -12,22 +12,20 @@ const EntitlementName = styled(Typography)`
12
12
 
13
13
  const EntitlementRowContainer = styled.div`
14
14
  display: flex;
15
- align-items: baseline;
15
+ align-items: center;
16
+ gap: 16px;
16
17
  `;
17
18
 
18
19
  const EntitlementIconContainer = styled.div`
19
20
  display: flex;
20
21
  align-items: center;
21
- margin-right: 16px;
22
- height: ${({ theme }) => theme.stigg.typography.h3.fontSize};
23
22
  flex-shrink: 0;
24
23
  `;
25
24
 
26
- const EntitlementCheckIcon = styled(CheckUrl)`
27
- flex-shrink: 0;
28
- * {
29
- fill: ${({ theme }) => theme.stigg.palette.text.disabled};
30
- }
25
+ const EntitlementIcon = styled(Icon)`
26
+ width: 24px;
27
+ height: 24px;
28
+ color: ${({ theme }) => theme.stigg.palette.primary};
31
29
  `;
32
30
 
33
31
  type EntitlementRowProps = {
@@ -113,7 +111,7 @@ export function EntitlementRow(props: EntitlementRowProps) {
113
111
  return (
114
112
  <EntitlementRowContainer className={`stigg-entitlement-row-container stigg-entitlement-${restProps.feature?.id}`}>
115
113
  <EntitlementIconContainer>
116
- <EntitlementCheckIcon className="stigg-entitlement-row-icon" />
114
+ <EntitlementIcon icon="EntitlementCheck" className="stigg-entitlement-row-icon" />
117
115
  </EntitlementIconContainer>
118
116
  <EntitlementName className="stigg-entitlement-name" variant="h6" color="secondary">
119
117
  {displayNameOverride || displayName}
@@ -61,6 +61,7 @@ type PaywallProps = {
61
61
  locale: string;
62
62
  shouldHidePlan?: ShouldHidePlanFn;
63
63
  selectDefaultTierIndex?: SelectDefaultTierIndexFn;
64
+ hideCompatibleAddons?: boolean;
64
65
  };
65
66
 
66
67
  export const Paywall = ({
@@ -78,6 +79,7 @@ export const Paywall = ({
78
79
  shouldHidePlan,
79
80
  selectDefaultTierIndex,
80
81
  currentSubscriptionOverride,
82
+ hideCompatibleAddons,
81
83
  }: PaywallProps) => {
82
84
  const { stigg } = useStiggContext();
83
85
  const discountRate = calculatePaywallDiscountRate(plans);
@@ -176,6 +178,7 @@ export const Paywall = ({
176
178
  customer={customer}
177
179
  isCustomerInCustomPlan={isCustomerInCustomPlan}
178
180
  selectDefaultTierIndex={selectDefaultTierIndex}
181
+ hideCompatibleAddons={hideCompatibleAddons}
179
182
  />
180
183
  ))}
181
184
  </PaywallPlansContainer>
@@ -30,6 +30,7 @@ export type PaywallContainerProps = {
30
30
  billingCountryCode?: string;
31
31
  shouldHidePlan?: ShouldHidePlanFn;
32
32
  selectDefaultTierIndex?: SelectDefaultTierIndexFn;
33
+ hideCompatibleAddons?: boolean;
33
34
  // This is used to override the current subscription in case a many to one plan mapping is needed
34
35
  // Many plans mapped to one (fictive) plan
35
36
  currentSubscriptionOverride?: CurrentSubscriptionOverrideFn;
@@ -48,6 +49,7 @@ export const PaywallContainer = ({
48
49
  shouldHidePlan,
49
50
  selectDefaultTierIndex,
50
51
  currentSubscriptionOverride: currentSubscriptionOverrideFn,
52
+ hideCompatibleAddons,
51
53
  }: PaywallContainerProps) => {
52
54
  const hasCustomerPortalContext = useCheckContextExists(CustomerPortalContext);
53
55
  let isCustomerPortalLoading = false;
@@ -104,6 +106,7 @@ export const PaywallContainer = ({
104
106
  locale={locale}
105
107
  shouldHidePlan={shouldHidePlan}
106
108
  selectDefaultTierIndex={selectDefaultTierIndex}
109
+ hideCompatibleAddons={hideCompatibleAddons}
107
110
  />
108
111
  );
109
112
 
@@ -0,0 +1,125 @@
1
+ import React, { useState } from 'react';
2
+ import { Addon, WidgetType } from '@stigg/js-client-sdk';
3
+ import MuiButton from '@mui/material/Button';
4
+ import styled from '@emotion/styled/macro';
5
+ import classNames from 'classnames';
6
+ import { Typography } from '../common/Typography';
7
+ import { PaywallLocalization } from './paywallTextOverrides';
8
+ import { Icon } from '../common/Icon';
9
+
10
+ export const StyledButton = styled(MuiButton)`
11
+ padding: 0px;
12
+ text-transform: none;
13
+ margin-left: 0px;
14
+ `;
15
+
16
+ const ButtonText = styled(Typography)`
17
+ font-size: 14px;
18
+ font-weight: 500;
19
+ `;
20
+
21
+ const AddonsBox = styled.div`
22
+ width: 100%;
23
+ gap: 10px;
24
+ `;
25
+
26
+ const AddonsList = styled.div`
27
+ margin-top: 10px;
28
+ overflow: hidden;
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 4px;
32
+ width: 100%;
33
+ `;
34
+
35
+ const AddonRow = styled.div`
36
+ display: flex;
37
+ flex-direction: row;
38
+ flex-wrap: nowrap;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ padding: 8px 0px;
42
+ max-height: 44px;
43
+ gap: 16px;
44
+ `;
45
+
46
+ const AddonName = styled(Typography)`
47
+ overflow: hidden;
48
+ font-feature-settings: 'liga' off, 'clig' off;
49
+ text-overflow: ellipsis;
50
+ font-family: 'DM Sans';
51
+ letter-spacing: 0.15px;
52
+ white-space: nowrap;
53
+ min-width: 0;
54
+ flex-shrink: 1;
55
+ `;
56
+
57
+ const AddonIcon = styled(Icon)`
58
+ width: 24px;
59
+ height: 24px;
60
+ color: ${({ theme }) => theme.stigg.palette.primary};
61
+ `;
62
+
63
+ const AddonContent = styled.div`
64
+ flex: 1;
65
+ min-width: 0;
66
+ `;
67
+
68
+ const Footer = styled.div`
69
+ margin-top: 16px;
70
+ display: flex;
71
+ `;
72
+
73
+ const MAX_ADDONS = 4;
74
+
75
+ type PlanCompatibleAddonsProps = {
76
+ addons: Addon[];
77
+ paywallLocale: PaywallLocalization;
78
+ };
79
+
80
+ export function PlanCompatibleAddons({ addons, paywallLocale }: PlanCompatibleAddonsProps) {
81
+ const [showAllAddons, setShowAllAddons] = useState(false);
82
+
83
+ if (!addons || addons.length === 0) return null;
84
+
85
+ const visibleAddons = addons.filter((addon) => !addon.hiddenFromWidgets?.includes(WidgetType.Paywall));
86
+
87
+ const toggleShowAllAddons = () => {
88
+ setShowAllAddons(!showAllAddons);
89
+ };
90
+
91
+ const displayedAddons = showAllAddons ? visibleAddons : visibleAddons.slice(0, MAX_ADDONS);
92
+
93
+ return (
94
+ <AddonsBox className="stigg-compatible-addons-container">
95
+ <Typography className="stigg-compatible-addons-header" color="secondary" variant="body1" bold>
96
+ {paywallLocale.addonsTitle}
97
+ </Typography>
98
+ <AddonsList className="stigg-compatible-addons-list">
99
+ {displayedAddons.map((addon) => {
100
+ return (
101
+ <AddonRow
102
+ key={addon.id}
103
+ className={classNames(`stigg-compatible-addon--${addon.id}`, 'stigg-compatible-addon-item')}>
104
+ <AddonIcon icon="Add" className="stigg-compatible-addon-icon" />
105
+ <AddonContent className="stigg-compatible-addon-content">
106
+ <AddonName variant="body1" color="primary" className="stigg-compatible-addon-name">
107
+ {addon.displayName}
108
+ </AddonName>
109
+ </AddonContent>
110
+ </AddonRow>
111
+ );
112
+ })}
113
+ </AddonsList>
114
+ {visibleAddons.length > MAX_ADDONS && (
115
+ <Footer>
116
+ <StyledButton variant="text" onClick={toggleShowAllAddons}>
117
+ <ButtonText color="primary.main">
118
+ {showAllAddons ? 'Show less' : `Show ${visibleAddons.length - MAX_ADDONS} more`}
119
+ </ButtonText>
120
+ </StyledButton>
121
+ </Footer>
122
+ )}
123
+ </AddonsBox>
124
+ );
125
+ }
@@ -16,7 +16,7 @@ function getTitle(plan: PaywallPlan, paywallLocale: PaywallLocalization, hasFeat
16
16
  if (paywallLocale.entitlementsTitle) {
17
17
  return paywallLocale.entitlementsTitle(plan);
18
18
  }
19
- let title = hasFeatures ? `${plan.displayName} includes` : '';
19
+ let title = hasFeatures ? `Includes:` : '';
20
20
  if (plan.basePlan) {
21
21
  title = `Everything in ${plan.basePlan.displayName}${hasFeatures ? ', plus:' : ''}`;
22
22
  }
@@ -12,6 +12,7 @@ import { Typography } from '../common/Typography';
12
12
  import MiniSchedule from '../../assets/mini-schedule.svg';
13
13
  import { PlanPrice } from './PlanPrice';
14
14
  import { getTiersPerUnitQuantities } from '../utils/priceTierUtils';
15
+ import { PlanCompatibleAddons } from './PlanCompatibleAddons';
15
16
 
16
17
  const PlanOfferingButtonHeight = '66px';
17
18
 
@@ -32,6 +33,10 @@ const PlanOfferingContainer = styled.div<{ $isHighlighted: boolean; $isCurrentPl
32
33
  position: relative;
33
34
  `;
34
35
 
36
+ const AddonsContainer = styled.div<{ $planHasEntitlements: boolean }>`
37
+ margin-top: ${({ $planHasEntitlements }) => ($planHasEntitlements ? '32px' : '0px')};
38
+ `;
39
+
35
40
  const PlanHeader = styled(Typography)`
36
41
  padding-bottom: 8px;
37
42
  `;
@@ -87,6 +92,7 @@ type PlanOfferingProps = {
87
92
  withStartingAtRow: boolean;
88
93
  isCustomerInCustomPlan: boolean;
89
94
  selectDefaultTierIndex?: SelectDefaultTierIndexFn;
95
+ hideCompatibleAddons?: boolean;
90
96
  };
91
97
 
92
98
  const NextPlanTagContainer = styled.div`
@@ -140,6 +146,7 @@ export function PlanOffering({
140
146
  withStartingAtRow,
141
147
  isCustomerInCustomPlan,
142
148
  selectDefaultTierIndex,
149
+ hideCompatibleAddons,
143
150
  }: PlanOfferingProps) {
144
151
  const isNextPlan = plan.isNextPlan && plan.isNextPlan(billingPeriod);
145
152
  const planPrices = plan.pricePoints.filter((pricePoint) => pricePoint.billingPeriod === billingPeriod);
@@ -238,6 +245,12 @@ export function PlanOffering({
238
245
  </HeaderWrapper>
239
246
 
240
247
  <PlanEntitlements plan={plan} billingPeriod={billingPeriod} paywallLocale={paywallLocale} />
248
+
249
+ <AddonsContainer $planHasEntitlements={plan.entitlements.length > 0} className="stigg-addons-container">
250
+ {!hideCompatibleAddons && plan.compatibleAddons && plan.compatibleAddons.length > 0 && (
251
+ <PlanCompatibleAddons addons={plan.compatibleAddons} paywallLocale={paywallLocale} />
252
+ )}
253
+ </AddonsContainer>
241
254
  </PlanOfferingContainer>
242
255
  );
243
256
  }
@@ -1,3 +1,4 @@
1
1
  export { PaywallContainer as Paywall, PaywallContainerProps as PaywallProps } from './PaywallContainer';
2
2
  export * from './types';
3
3
  export { PaywallLocalization, PlanPriceText, CurrentPlanParams } from './paywallTextOverrides';
4
+ export { PlanCompatibleAddons } from './PlanCompatibleAddons';
@@ -21,6 +21,7 @@ export type CurrentPlanParams = {
21
21
  export type PaywallLocalization = {
22
22
  highlightChip: string;
23
23
  entitlementsTitle?: (plan: PaywallPlan) => string;
24
+ addonsTitle?: string;
24
25
  planCTAButton: {
25
26
  upgrade: string | ((plan: PaywallPlan) => string);
26
27
  downgrade: string | ((plan: PaywallPlan) => string);
@@ -51,6 +52,7 @@ export type PaywallLocalization = {
51
52
  export function getResolvedPaywallLocalize(localizeOverride?: DeepPartial<PaywallLocalization>) {
52
53
  const paywallDefaultLocalization: PaywallLocalization = {
53
54
  highlightChip: 'Recommended',
55
+ addonsTitle: 'Available add-ons:',
54
56
  planCTAButton: {
55
57
  upgrade: 'Upgrade',
56
58
  downgrade: 'Downgrade',
@@ -40,6 +40,7 @@ const Template: ComponentStory<any> = (args) => (
40
40
  billingCountryCode={args.billingCountryCode}
41
41
  preferredBillingPeriod={args.preferredBillingPeriod}
42
42
  shouldHidePlan={args.shouldHidePlan}
43
+ hideCompatibleAddons={args.hideCompatibleAddons}
43
44
  />
44
45
  );
45
46
 
@@ -121,3 +122,18 @@ CustomerResourcePaywall.args = {
121
122
  productId: 'product-site',
122
123
  resourceId: 'example.com',
123
124
  };
125
+
126
+ export const PaywallWithAddons = Template.bind({});
127
+ PaywallWithAddons.args = {
128
+ ...defaultArgs,
129
+ highlightedPlanId: 'plan-revvenu-essentials',
130
+ hideCompatibleAddons: false,
131
+ textOverrides: {
132
+ highlightChip: 'Best value',
133
+ planCTAButton: {
134
+ startTrial: () => 'Start trial',
135
+ upgrade: 'Start now',
136
+ custom: 'Contact us',
137
+ },
138
+ },
139
+ };