@stigg/react-sdk 4.2.3 → 4.3.0-beta.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.
Files changed (164) hide show
  1. package/README.md +1 -1
  2. package/dist/components/checkout/Checkout.d.ts +5 -0
  3. package/dist/components/checkout/CheckoutContainer.d.ts +22 -0
  4. package/dist/components/checkout/CheckoutContainer.style.d.ts +29 -0
  5. package/dist/components/checkout/CheckoutProvider.d.ts +33 -0
  6. package/dist/components/checkout/CheckoutSummary.d.ts +9 -0
  7. package/dist/components/checkout/components/Button.d.ts +6 -0
  8. package/dist/components/checkout/components/InputField.d.ts +8 -0
  9. package/dist/components/checkout/components/index.d.ts +2 -0
  10. package/dist/components/checkout/formatting.d.ts +2 -0
  11. package/dist/components/checkout/hooks/index.d.ts +8 -0
  12. package/dist/components/checkout/hooks/useAddonsStepModel.d.ts +21 -0
  13. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +9 -0
  14. package/dist/components/checkout/hooks/useCouponModel.d.ts +7 -0
  15. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +13 -0
  16. package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +16 -0
  17. package/dist/components/checkout/hooks/usePlanStepModel.d.ts +23 -0
  18. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +13 -0
  19. package/dist/components/checkout/hooks/useProgressBarModel.d.ts +26 -0
  20. package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +5 -0
  21. package/dist/components/checkout/hooks/useSubscriptionState.d.ts +2 -0
  22. package/dist/components/checkout/index.d.ts +3 -0
  23. package/dist/components/checkout/planHeader/PlanHeader.d.ts +7 -0
  24. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +25 -0
  25. package/dist/components/checkout/planHeader/index.d.ts +1 -0
  26. package/dist/components/checkout/progressBar/CheckoutProgressBar.d.ts +2 -0
  27. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +45 -0
  28. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +5 -0
  29. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +7 -0
  30. package/dist/components/checkout/promotionCode/AppliedPromotionCode.d.ts +6 -0
  31. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +5 -0
  32. package/dist/components/checkout/promotionCode/index.d.ts +1 -0
  33. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.d.ts +2 -0
  34. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.style.d.ts +93 -0
  35. package/dist/components/checkout/steps/addons/addon.utils.d.ts +15 -0
  36. package/dist/components/checkout/steps/addons/index.d.ts +1 -0
  37. package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +19 -0
  38. package/dist/components/checkout/steps/payment/PaymentMethods.style.d.ts +113 -0
  39. package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -0
  40. package/dist/components/checkout/steps/payment/index.d.ts +1 -0
  41. package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -0
  42. package/dist/components/checkout/steps/payment/stripe/index.d.ts +3 -0
  43. package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +33 -0
  44. package/dist/components/checkout/steps/payment/stripe/useStripeIntegration.d.ts +5 -0
  45. package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +10 -0
  46. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +9 -0
  47. package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +52 -0
  48. package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +16 -0
  49. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +4 -0
  50. package/dist/components/checkout/steps/plan/CheckoutPlanStep.style.d.ts +12 -0
  51. package/dist/components/checkout/steps/plan/index.d.ts +1 -0
  52. package/dist/components/checkout/steps/surprise/SurpriseStep.d.ts +2 -0
  53. package/dist/components/checkout/textOverrides.d.ts +28 -0
  54. package/dist/components/checkout/theme.d.ts +8 -0
  55. package/dist/components/checkout/types.d.ts +7 -0
  56. package/dist/components/common/Icon.d.ts +3 -2
  57. package/dist/components/common/PoweredByStigg.d.ts +1 -1
  58. package/dist/components/{paywall/TiersLayout.d.ts → common/TiersSelectContainer.d.ts} +2 -3
  59. package/dist/components/common/customIcons.d.ts +17 -5
  60. package/dist/components/common/mapExternalTheme.d.ts +2 -0
  61. package/dist/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.d.ts +1 -1
  62. package/dist/components/hooks/useChargeSort.d.ts +3 -0
  63. package/dist/components/utils/calculateDiscountRate.d.ts +1 -0
  64. package/dist/components/utils/currencyUtils.d.ts +1 -1
  65. package/dist/components/{paywall/planPriceTier.d.ts → utils/priceTierUtils.d.ts} +3 -1
  66. package/dist/components/utils/priceUtils.d.ts +2 -0
  67. package/dist/index.d.ts +1 -0
  68. package/dist/react-sdk.cjs.development.js +3446 -197
  69. package/dist/react-sdk.cjs.development.js.map +1 -1
  70. package/dist/react-sdk.cjs.production.min.js +1 -1
  71. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  72. package/dist/react-sdk.esm.js +3589 -203
  73. package/dist/react-sdk.esm.js.map +1 -1
  74. package/dist/stories/Checkout.stories.d.ts +3 -0
  75. package/dist/theme/getResolvedTheme.d.ts +1 -0
  76. package/dist/theme/types.d.ts +1 -0
  77. package/package.json +7 -4
  78. package/src/assets/arrow-forward.svg +3 -0
  79. package/src/assets/arrow-right.svg +6 -0
  80. package/src/assets/close.svg +3 -0
  81. package/src/assets/nyancat.svg +634 -0
  82. package/src/assets/outlined-checked-circle.svg +6 -0
  83. package/src/assets/outlined-circle.svg +3 -0
  84. package/src/assets/payment-method.svg +11 -0
  85. package/src/assets/plus-icon.svg +6 -0
  86. package/src/assets/trash.svg +8 -0
  87. package/src/components/StiggProvider.tsx +5 -5
  88. package/src/components/checkout/Checkout.tsx +30 -0
  89. package/src/components/checkout/CheckoutContainer.style.ts +34 -0
  90. package/src/components/checkout/CheckoutContainer.tsx +92 -0
  91. package/src/components/checkout/CheckoutProvider.tsx +135 -0
  92. package/src/components/checkout/CheckoutSummary.tsx +361 -0
  93. package/src/components/checkout/components/Button.tsx +30 -0
  94. package/src/components/checkout/components/InputField.tsx +23 -0
  95. package/src/components/checkout/components/index.ts +2 -0
  96. package/src/components/checkout/formatting.ts +12 -0
  97. package/src/components/checkout/hooks/index.ts +8 -0
  98. package/src/components/checkout/hooks/useAddonsStepModel.ts +96 -0
  99. package/src/components/checkout/hooks/useCheckoutModel.ts +31 -0
  100. package/src/components/checkout/hooks/useCouponModel.ts +28 -0
  101. package/src/components/checkout/hooks/useLoadCheckout.ts +40 -0
  102. package/src/components/checkout/hooks/usePaymentStepModel.ts +49 -0
  103. package/src/components/checkout/hooks/usePlanStepModel.ts +170 -0
  104. package/src/components/checkout/hooks/usePreviewSubscription.ts +82 -0
  105. package/src/components/checkout/hooks/useProgressBarModel.ts +89 -0
  106. package/src/components/checkout/hooks/useSubscriptionModel.ts +16 -0
  107. package/src/components/checkout/hooks/useSubscriptionState.ts +26 -0
  108. package/src/components/checkout/index.ts +3 -0
  109. package/src/components/checkout/planHeader/PlanHeader.style.tsx +23 -0
  110. package/src/components/checkout/planHeader/PlanHeader.tsx +61 -0
  111. package/src/components/checkout/planHeader/index.ts +1 -0
  112. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +29 -0
  113. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +48 -0
  114. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +85 -0
  115. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +39 -0
  116. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +37 -0
  117. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +27 -0
  118. package/src/components/checkout/promotionCode/index.ts +1 -0
  119. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +24 -0
  120. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +125 -0
  121. package/src/components/checkout/steps/addons/addon.utils.ts +68 -0
  122. package/src/components/checkout/steps/addons/index.ts +1 -0
  123. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +26 -0
  124. package/src/components/checkout/steps/payment/PaymentMethods.tsx +83 -0
  125. package/src/components/checkout/steps/payment/PaymentStep.tsx +41 -0
  126. package/src/components/checkout/steps/payment/index.ts +1 -0
  127. package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +43 -0
  128. package/src/components/checkout/steps/payment/stripe/index.ts +3 -0
  129. package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +109 -0
  130. package/src/components/checkout/steps/payment/stripe/useStripeIntegration.ts +27 -0
  131. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +100 -0
  132. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +46 -0
  133. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +63 -0
  134. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +138 -0
  135. package/src/components/checkout/steps/plan/CheckoutPlanStep.style.tsx +6 -0
  136. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +22 -0
  137. package/src/components/checkout/steps/plan/index.ts +1 -0
  138. package/src/components/checkout/steps/surprise/SurpriseStep.tsx +27 -0
  139. package/src/components/checkout/textOverrides.ts +58 -0
  140. package/src/components/checkout/theme.ts +26 -0
  141. package/src/components/checkout/types.ts +7 -0
  142. package/src/components/common/Icon.tsx +17 -22
  143. package/src/components/common/PoweredByStigg.tsx +1 -1
  144. package/src/components/{paywall/TiersLayout.tsx → common/TiersSelectContainer.tsx} +8 -7
  145. package/src/components/common/Typography.tsx +11 -1
  146. package/src/components/common/customIcons.ts +17 -28
  147. package/src/components/common/mapExternalTheme.ts +6 -0
  148. package/src/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.tsx +6 -12
  149. package/src/components/hooks/useChargeSort.ts +17 -0
  150. package/src/components/paywall/Paywall.tsx +1 -1
  151. package/src/components/paywall/PlanOffering.tsx +1 -1
  152. package/src/components/paywall/PlanOfferingButton.tsx +1 -1
  153. package/src/components/paywall/PlanPrice.tsx +3 -3
  154. package/src/components/paywall/utils/calculateUnitQuantityText.ts +9 -4
  155. package/src/components/utils/calculateDiscountRate.ts +1 -1
  156. package/src/components/utils/currencyUtils.ts +1 -1
  157. package/src/components/utils/getPaidPriceText.ts +2 -2
  158. package/src/components/{paywall/planPriceTier.ts → utils/priceTierUtils.ts} +25 -3
  159. package/src/components/utils/priceUtils.ts +10 -0
  160. package/src/index.ts +1 -0
  161. package/src/stories/Checkout.stories.tsx +59 -0
  162. package/src/theme/Theme.tsx +9 -8
  163. package/src/theme/getResolvedTheme.ts +1 -0
  164. package/src/theme/types.ts +1 -0
@@ -0,0 +1,100 @@
1
+ import { ApplySubscription, ApplySubscriptionResults } from '@stigg/js-client-sdk';
2
+ import { useElements, useStripe } from '@stripe/react-stripe-js';
3
+
4
+ import { useStiggContext } from '../../../../..';
5
+ import { useCheckoutModel } from '../../../hooks';
6
+ import { usePaymentStepModel } from '../../../hooks/usePaymentStepModel';
7
+ import { useSubscriptionState } from '../../../hooks/useSubscriptionState';
8
+ import { CheckoutContainerProps, CheckoutResult } from '../../../CheckoutContainer';
9
+ import { handleNewPaymentMethod, handleStripeFormValidations, handleStripeNextAction } from './stripe.utils';
10
+
11
+ export type HandleSubmitResult = { results?: ApplySubscriptionResults; errorMessage?: string } | undefined;
12
+
13
+ export function useSubmit({
14
+ onCheckout,
15
+ onCheckoutCompleted,
16
+ }: Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted'>) {
17
+ const { stigg } = useStiggContext();
18
+ const { useNewPaymentMethod } = usePaymentStepModel();
19
+ const subscriptionState = useSubscriptionState();
20
+ const { checkoutState, widgetState, setWidgetLoading } = useCheckoutModel();
21
+ const { setupSecret: setupIntentClientSecret } = checkoutState || {};
22
+ const stripe = useStripe();
23
+ const elements = useElements();
24
+
25
+ const handleSubmit = async (e: any): Promise<HandleSubmitResult> => {
26
+ e.preventDefault();
27
+
28
+ if (!subscriptionState) {
29
+ return;
30
+ }
31
+
32
+ let checkoutResults: ApplySubscriptionResults | undefined;
33
+ let errorMessage: string | undefined;
34
+ let paymentMethodId: string | undefined;
35
+
36
+ setWidgetLoading(true);
37
+
38
+ if (useNewPaymentMethod) {
39
+ const { success } = await handleStripeFormValidations({ elements });
40
+ if (!success) {
41
+ setWidgetLoading(false);
42
+ return;
43
+ }
44
+
45
+ const paymentMethodResults = await handleNewPaymentMethod({ elements, stripe, setupIntentClientSecret });
46
+ if (!paymentMethodResults.success) {
47
+ errorMessage = paymentMethodResults.errorMessage;
48
+ }
49
+
50
+ paymentMethodId = paymentMethodResults.paymentMethodId;
51
+ }
52
+
53
+ if (!errorMessage) {
54
+ const checkoutParams: ApplySubscription = { ...subscriptionState, paymentMethodId };
55
+
56
+ const checkoutAction = async (): Promise<CheckoutResult> => {
57
+ try {
58
+ const applySubscriptionResults = await stigg.applySubscription(checkoutParams);
59
+ const nextActionResults = await handleStripeNextAction({
60
+ applySubscriptionResults,
61
+ stripe,
62
+ });
63
+
64
+ checkoutResults = nextActionResults;
65
+ if (nextActionResults.errorMessage) {
66
+ errorMessage = nextActionResults.errorMessage;
67
+ }
68
+
69
+ return { success: !nextActionResults.errorMessage, errorMessage: nextActionResults.errorMessage };
70
+ } catch (e) {
71
+ console.error(e);
72
+ errorMessage = (e as any)?.message;
73
+ return { success: false, errorMessage };
74
+ }
75
+ };
76
+
77
+ if (onCheckout) {
78
+ const externalCheckoutResults = await onCheckout({ checkoutParams, checkoutAction });
79
+ if (!externalCheckoutResults.success && externalCheckoutResults.errorMessage) {
80
+ errorMessage = externalCheckoutResults.errorMessage;
81
+ }
82
+ } else {
83
+ await checkoutAction();
84
+ }
85
+ }
86
+
87
+ setWidgetLoading(false);
88
+
89
+ if (onCheckoutCompleted) {
90
+ await onCheckoutCompleted({
91
+ success: !errorMessage && !!checkoutResults?.subscription,
92
+ error: errorMessage,
93
+ });
94
+ }
95
+
96
+ return { results: checkoutResults, errorMessage };
97
+ };
98
+
99
+ return { handleSubmit, isLoading: !!widgetState?.isLoading };
100
+ }
@@ -0,0 +1,46 @@
1
+ import styled from '@emotion/styled/macro';
2
+ import { Box, Chip } from '@mui/material';
3
+
4
+ export const BillingPeriodPickerContainer = styled(Box)`
5
+ padding: 16px 0;
6
+ `;
7
+
8
+ export const BillingPeriodButton = styled.button<{ $isActive?: boolean; $disabled?: boolean }>`
9
+ cursor: ${({ $disabled }) => ($disabled ? 'default' : 'pointer')};
10
+ flex: 1;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: flex-start;
14
+ height: 80px;
15
+ border-radius: 10px;
16
+ border: ${({ theme, $isActive }) =>
17
+ `1px solid ${$isActive ? theme.stigg.palette.outlinedRestingBorder : theme.stigg.palette.outlinedBorder}`};
18
+ background: ${({ $isActive }) => ($isActive ? '#e5f2ff' : '#fff')};
19
+ text-transform: none;
20
+ text-align: start;
21
+
22
+ &.MuiButton-root {
23
+ padding: 0 16px 0 8px;
24
+ &:hover {
25
+ background: none;
26
+ }
27
+ }
28
+ `;
29
+
30
+ export const BillingPeriodOptions = styled(Box)`
31
+ display: flex;
32
+ gap: 16px;
33
+ margin-top: 16px;
34
+ `;
35
+
36
+ export const BillingPeriodPrice = styled.div`
37
+ display: flex;
38
+ align-items: baseline;
39
+ gap: 8px;
40
+ `;
41
+
42
+ export const DiscountChip = styled(Chip)`
43
+ & .MuiChip-label {
44
+ font-size: 12px;
45
+ }
46
+ `;
@@ -0,0 +1,63 @@
1
+ import { partition } from 'lodash';
2
+ import React from 'react';
3
+ import { FontWeight } from 'styled-typography';
4
+
5
+ import { Box, Radio } from '@mui/material';
6
+ import { BillingPeriod, Plan } from '@stigg/js-client-sdk';
7
+
8
+ import { Typography } from '../../../common/Typography';
9
+ import { formatBillingPeriod } from '../../formatting';
10
+ import { usePlanStepModel } from '../../hooks/usePlanStepModel';
11
+ import { BillingPeriodButton, BillingPeriodOptions, BillingPeriodPickerContainer } from './BillingPeriodPicker.style';
12
+ import { CheckoutLocalization } from '../../textOverrides';
13
+
14
+ const BillingPeriodOption = ({ billingPeriod }: { billingPeriod: BillingPeriod }) => {
15
+ const { billingPeriod: selectedBillingPeriod, setBillingPeriod } = usePlanStepModel();
16
+ const isActive = selectedBillingPeriod === billingPeriod;
17
+
18
+ return (
19
+ <BillingPeriodButton onClick={() => setBillingPeriod(billingPeriod)} $isActive={isActive}>
20
+ <Radio
21
+ checked={isActive}
22
+ onChange={() => setBillingPeriod(billingPeriod)}
23
+ value={billingPeriod}
24
+ inputProps={{ 'aria-label': formatBillingPeriod(billingPeriod) }}
25
+ />
26
+
27
+ <Box>
28
+ <Typography variant="h6" color="primary" fontWeight={FontWeight.Medium}>
29
+ {formatBillingPeriod(billingPeriod)}
30
+ </Typography>
31
+ </Box>
32
+ </BillingPeriodButton>
33
+ );
34
+ };
35
+
36
+ type BillingPeriodPickerProps = {
37
+ plan?: Plan;
38
+ checkoutLocalization: CheckoutLocalization;
39
+ };
40
+
41
+ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPeriodPickerProps) => {
42
+ const [monthlyPrices, annualPrices] = partition(
43
+ plan?.pricePoints,
44
+ (price) => price.billingPeriod === BillingPeriod.Monthly,
45
+ );
46
+
47
+ return (
48
+ <BillingPeriodPickerContainer>
49
+ <Typography variant="h6" color="primary" fontWeight={FontWeight.Medium}>
50
+ {checkoutLocalization.billingPeriodsTitle}
51
+ </Typography>
52
+
53
+ <BillingPeriodOptions>
54
+ {!!monthlyPrices?.length && (
55
+ <BillingPeriodOption key={BillingPeriod.Monthly} billingPeriod={BillingPeriod.Monthly} />
56
+ )}
57
+ {!!annualPrices?.length && (
58
+ <BillingPeriodOption key={BillingPeriod.Annually} billingPeriod={BillingPeriod.Annually} />
59
+ )}
60
+ </BillingPeriodOptions>
61
+ </BillingPeriodPickerContainer>
62
+ );
63
+ };
@@ -0,0 +1,138 @@
1
+ import React from 'react';
2
+
3
+ import styled from '@emotion/styled';
4
+ import { Box } from '@mui/material';
5
+ import { BillableFeatureInput } from '@stigg/api-client-js/src/generated/sdk';
6
+ import { BillingModel, BillingPeriod, Plan, Price, PriceTierFragment } from '@stigg/js-client-sdk';
7
+
8
+ import { Typography } from '../../../common/Typography';
9
+ import { useChargesSort } from '../../../hooks/useChargeSort';
10
+ import { calculateUnitQuantityText } from '../../../paywall/utils/calculateUnitQuantityText';
11
+ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
12
+ import { InputField } from '../../components';
13
+ import { usePlanStepModel } from '../../hooks';
14
+ import { TiersSelectContainer } from '../../../common/TiersSelectContainer';
15
+ import { getPriceFeatureUnit, getTierByQuantity } from '../../../utils/priceTierUtils';
16
+ import { getValidPriceQuantity } from '../../../utils/priceUtils';
17
+
18
+ export type UsePlanStepModel = ReturnType<typeof usePlanStepModel>;
19
+
20
+ type CheckoutChargeListProps = {
21
+ plan?: Plan;
22
+ billingPeriod: BillingPeriod;
23
+ };
24
+
25
+ const StyledPlanCharge = styled.div`
26
+ display: flex;
27
+ flex-direction: row;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+ min-height: 60px;
31
+ margin-top: 16px;
32
+ `;
33
+
34
+ export function PlanCharge({
35
+ charge,
36
+ setBillableFeature,
37
+ billableFeature,
38
+ }: {
39
+ charge: Price;
40
+ billableFeature?: BillableFeatureInput;
41
+ setBillableFeature: UsePlanStepModel['setBillableFeature'];
42
+ }) {
43
+ const featureId = charge.feature?.featureId;
44
+ const isBaseCharge = !featureId;
45
+ const isPayAsYouGo = charge.pricingModel === BillingModel.UsageBased;
46
+ const displayName = isBaseCharge ? 'Base charge' : charge.feature?.displayName;
47
+ const hasQuantityRestrictions = !!(charge?.minUnitQuantity || charge?.maxUnitQuantity);
48
+
49
+ const handleQuantityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
50
+ if (isBaseCharge || !featureId) return;
51
+
52
+ const value = event?.target?.value ? Number(event?.target?.value) : charge.minUnitQuantity || 1;
53
+ const quantity = getValidPriceQuantity(charge, value || 1);
54
+
55
+ setBillableFeature(featureId, quantity);
56
+ };
57
+
58
+ let chargeRow;
59
+
60
+ if (isBaseCharge || isPayAsYouGo) {
61
+ const formattedAmount = currencyPriceFormatter({
62
+ amount: charge.amount!,
63
+ currency: charge.currency,
64
+ locale: 'en-us',
65
+ });
66
+
67
+ chargeRow = `${formattedAmount}`;
68
+ if (isPayAsYouGo) {
69
+ chargeRow += ' / unit';
70
+ }
71
+ } else if (charge.isTieredPrice) {
72
+ const tier = getTierByQuantity(charge.tiers!, billableFeature!.quantity || 1);
73
+ chargeRow = (
74
+ <TiersSelectContainer
75
+ componentId={`${featureId}-tiers`}
76
+ tiers={charge.tiers}
77
+ tierUnits={getPriceFeatureUnit(charge)}
78
+ selectedTier={tier}
79
+ handleTierChange={(tier: PriceTierFragment) => {
80
+ setBillableFeature(featureId!, tier!.upTo);
81
+ }}
82
+ />
83
+ );
84
+ } else {
85
+ chargeRow = (
86
+ <InputField
87
+ sx={{ width: 120 }}
88
+ id={`${featureId}-input`}
89
+ type="number"
90
+ InputProps={
91
+ hasQuantityRestrictions ? { inputProps: { min: charge.minUnitQuantity, max: charge.maxUnitQuantity } } : {}
92
+ }
93
+ value={billableFeature?.quantity || charge.minUnitQuantity || 1}
94
+ onChange={handleQuantityChange}
95
+ />
96
+ );
97
+ }
98
+
99
+ return (
100
+ <StyledPlanCharge>
101
+ <Box display="flex" flexDirection="column">
102
+ <Typography variant="h6" color="primary" lineHeight="24px">
103
+ {displayName}
104
+ </Typography>
105
+ {hasQuantityRestrictions && (
106
+ <Typography variant="body1" color="secondary" lineHeight="20px">
107
+ {calculateUnitQuantityText(charge.minUnitQuantity, charge.maxUnitQuantity, charge.feature?.unitsPlural)}
108
+ </Typography>
109
+ )}
110
+ </Box>
111
+
112
+ <Typography variant="h6" color="primary">
113
+ {chargeRow}
114
+ </Typography>
115
+ </StyledPlanCharge>
116
+ );
117
+ }
118
+
119
+ export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListProps) {
120
+ const { billableFeatures, setBillableFeature } = usePlanStepModel();
121
+ const planCharges = useChargesSort(plan?.pricePoints?.filter(p => p.billingPeriod === billingPeriod) || []);
122
+
123
+ return (
124
+ <div>
125
+ {planCharges?.map(charge => {
126
+ const billableFeature = billableFeatures.find(x => x.featureId === charge.feature?.featureId);
127
+ return (
128
+ <PlanCharge
129
+ key={charge.feature?.featureId || 'base-charge'}
130
+ charge={charge}
131
+ setBillableFeature={setBillableFeature}
132
+ billableFeature={billableFeature}
133
+ />
134
+ );
135
+ })}
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,6 @@
1
+ import styled from '@emotion/styled/macro';
2
+ import { Box } from '@mui/material';
3
+
4
+ export const CheckoutPlanContainer = styled(Box)`
5
+ width: 100%;
6
+ `;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+
3
+ import { useCheckoutModel } from '../../hooks/useCheckoutModel';
4
+ import { usePlanStepModel } from '../../hooks/usePlanStepModel';
5
+ import { BillingPeriodPicker } from './BillingPeriodPicker';
6
+ import { CheckoutChargeList } from './CheckoutChargeList';
7
+ import { CheckoutPlanContainer } from './CheckoutPlanStep.style';
8
+
9
+ type CheckoutPlanStepProps = {};
10
+
11
+ export const CheckoutPlanStep = ({}: CheckoutPlanStepProps) => {
12
+ const { checkoutState, checkoutLocalization } = useCheckoutModel();
13
+ const { plan } = checkoutState || {};
14
+ const { billingPeriod } = usePlanStepModel();
15
+
16
+ return (
17
+ <CheckoutPlanContainer>
18
+ <BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
19
+ <CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
20
+ </CheckoutPlanContainer>
21
+ );
22
+ };
@@ -0,0 +1 @@
1
+ export * from './CheckoutPlanStep';
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import NyanCat from '../../../../assets/nyancat.svg';
3
+ import styled from '@emotion/styled/macro';
4
+
5
+ const StyledNyanCat = styled(NyanCat)`
6
+ background: #000;
7
+ width: 100%;
8
+ max-width: 920px;
9
+ height: 548px;
10
+ `;
11
+
12
+ const SurpriseContainer = styled.div`
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-content: center;
16
+ justify-content: center;
17
+ min-height: 430px;
18
+ width: 100%;
19
+ `;
20
+
21
+ export const SurpriseStep = () => {
22
+ return (
23
+ <SurpriseContainer>
24
+ <StyledNyanCat />
25
+ </SurpriseContainer>
26
+ );
27
+ };
@@ -0,0 +1,58 @@
1
+ import { BillingPeriod } from '@stigg/js-client-sdk';
2
+ import moment from 'moment';
3
+ import merge from 'lodash/merge';
4
+ import { DeepPartial } from '../../types';
5
+ import { formatBillingPeriod } from './formatting';
6
+
7
+ export type CheckoutLocalization = {
8
+ changePlan: string;
9
+ billingPeriodsTitle: string;
10
+ addAddonText: string;
11
+ newPaymentMethodText: string;
12
+ newPaymentMethodCardTitle: string;
13
+ newPaymentMethodBillingAddressTitle: string;
14
+ baseChargeText: string | ((params: { billingPeriod: BillingPeriod }) => string);
15
+ totalText: string;
16
+ subTotalText: string;
17
+ addCouponCodeText: string;
18
+ couponCodeTitle: string;
19
+ addonsSectionTitle: string;
20
+ changesWillApplyAtEndOfBillingPeriod: string | ((params: { billingPeriodEnd: Date }) => string);
21
+ checkoutButton: {
22
+ nextText: string;
23
+ downgradeText: string;
24
+ upgradeText: string;
25
+ purchaseText: string;
26
+ };
27
+ };
28
+
29
+ export function getResolvedCheckoutLocalize(
30
+ localizeOverride?: DeepPartial<CheckoutLocalization>,
31
+ ): CheckoutLocalization {
32
+ const checkoutDefaultLocalization: CheckoutLocalization = {
33
+ changePlan: 'Change plan',
34
+ billingPeriodsTitle: 'Billing cycle',
35
+ addAddonText: 'Add',
36
+ newPaymentMethodText: 'New payment method',
37
+ newPaymentMethodBillingAddressTitle: 'Billing address',
38
+ newPaymentMethodCardTitle: 'Payment method',
39
+ baseChargeText: ({ billingPeriod }) => `${formatBillingPeriod(billingPeriod)} charge`,
40
+ totalText: 'Total due today',
41
+ subTotalText: 'Subtotal',
42
+ addCouponCodeText: 'Add coupon code',
43
+ couponCodeTitle: 'Coupon code',
44
+ addonsSectionTitle: 'Add-ons',
45
+ changesWillApplyAtEndOfBillingPeriod: ({ billingPeriodEnd }) =>
46
+ `Your changes will apply on the end of your current billing cycle on ${moment(billingPeriodEnd).format(
47
+ 'MMM D, YYYY',
48
+ )}.`,
49
+ checkoutButton: {
50
+ nextText: 'Next',
51
+ downgradeText: 'Downgrade',
52
+ upgradeText: 'Upgrade',
53
+ purchaseText: 'Purchase',
54
+ },
55
+ };
56
+
57
+ return merge(checkoutDefaultLocalization, localizeOverride);
58
+ }
@@ -0,0 +1,26 @@
1
+ import { StiggTheme } from '../../theme/types';
2
+ import { DeepPartial } from '../../types';
3
+ import { CheckoutConfiguration } from './types';
4
+
5
+ export type CheckoutTheme = {
6
+ // sectionTitleColor: string;
7
+ // planNameColor: string;
8
+ backgroundColor: string;
9
+ borderColor: string;
10
+ // listItemBackgroundColor: string;
11
+ // tabMaxHeight: string;
12
+ // iconsColor: string;
13
+ };
14
+
15
+ export function getResolvedCheckoutTheme(
16
+ _globalTheme: StiggTheme,
17
+ themeOverride?: DeepPartial<CheckoutTheme>,
18
+ remoteConfiguration?: CheckoutConfiguration | null,
19
+ ) {
20
+ // todo: implement theme customization
21
+ const checkoutDefaultTheme: CheckoutTheme = {
22
+ backgroundColor: themeOverride?.backgroundColor || remoteConfiguration?.palette?.backgroundColor || 'white',
23
+ borderColor: themeOverride?.borderColor || remoteConfiguration?.palette?.borderColor || 'rgba(0, 30, 108, 0.15)',
24
+ };
25
+ return checkoutDefaultTheme;
26
+ }
@@ -0,0 +1,7 @@
1
+ /** placeholder types until we have backend support **/
2
+ export type CheckoutConfiguration = {
3
+ palette?: {
4
+ backgroundColor: string;
5
+ borderColor: string;
6
+ };
7
+ };
@@ -2,12 +2,14 @@ import React from 'react';
2
2
  import styled from '@emotion/styled/macro';
3
3
  import { CSSProperties } from 'react';
4
4
  import { css, Theme, useTheme } from '@emotion/react';
5
- import { ICON_COMPONENTS, Icons } from './customIcons';
6
5
  import { getIconColor, IconColor } from './iconColor';
6
+ import * as customIcons from './customIcons';
7
7
 
8
- const IconWrapper = styled.div<{ $pathColor?: string, $rectColor?: string; $strokeColor?: string }>`
8
+ export type Icons = keyof typeof customIcons;
9
+
10
+ const IconWrapper = styled.div<{ $pathColor?: string; $rectColor?: string; $strokeColor?: string }>`
9
11
  ${({ $pathColor }) =>
10
- $pathColor &&
12
+ $pathColor &&
11
13
  css`
12
14
  path {
13
15
  fill: ${$pathColor};
@@ -15,20 +17,20 @@ const IconWrapper = styled.div<{ $pathColor?: string, $rectColor?: string; $stro
15
17
  `}
16
18
 
17
19
  ${({ $rectColor }) =>
18
- $rectColor &&
19
- css`
20
- svg rect {
21
- fill: ${$rectColor};
22
- }
20
+ $rectColor &&
21
+ css`
22
+ svg rect {
23
+ fill: ${$rectColor};
24
+ }
23
25
  `}
24
26
 
25
27
 
26
28
  ${({ $strokeColor }) =>
27
- $strokeColor &&
28
- css`
29
- g {
30
- stroke: ${$strokeColor};
31
- }
29
+ $strokeColor &&
30
+ css`
31
+ g {
32
+ stroke: ${$strokeColor};
33
+ }
32
34
  `}
33
35
  `;
34
36
 
@@ -41,15 +43,8 @@ export type IconProps = {
41
43
  svgStrokeColor?: IconColor | string;
42
44
  };
43
45
 
44
- export function Icon({
45
- icon,
46
- className,
47
- style,
48
- svgPathColor,
49
- svgRectColor,
50
- svgStrokeColor,
51
- }: IconProps) {
52
- const IconComponent = ICON_COMPONENTS[icon];
46
+ export function Icon({ icon, className, style, svgPathColor, svgRectColor, svgStrokeColor }: IconProps) {
47
+ const IconComponent = (customIcons as any)[icon];
53
48
  const theme = useTheme() as Theme;
54
49
 
55
50
  return (
@@ -4,7 +4,7 @@ import styled from '@emotion/styled/macro';
4
4
 
5
5
  export const STIGG_WATERMARK_CLASSNAME = 'stigg-watermark';
6
6
 
7
- export type PoweredByStiggSources = 'paywall' | 'customer_portal';
7
+ export type PoweredByStiggSources = 'paywall' | 'customer_portal' | 'checkout';
8
8
 
9
9
  const PoweredByStiggThemedSvg = styled(PoweredByStiggSvg)`
10
10
  * {
@@ -1,10 +1,10 @@
1
- import { PaywallPlan } from './types';
2
1
  import { PriceTierFragment } from '@stigg/js-client-sdk';
3
2
  import { MenuItem, OutlinedInput, Select, SelectChangeEvent } from '@mui/material';
4
3
  import { Typography } from '../common/Typography';
5
4
  import { map } from 'lodash';
6
5
  import React, { ReactNode } from 'react';
7
6
  import styled from '@emotion/styled/macro';
7
+ import { getTierByQuantity } from '../utils/priceTierUtils';
8
8
 
9
9
  const TierSelect = styled(Select)`
10
10
  border-radius: 10px;
@@ -24,32 +24,33 @@ const TierInput = styled(OutlinedInput)`
24
24
  }
25
25
  `;
26
26
 
27
- export function TiersLayout({
28
- plan,
27
+ export function TiersSelectContainer({
28
+ componentId,
29
29
  tiers,
30
30
  tierUnits,
31
31
  selectedTier,
32
32
  handleTierChange,
33
33
  }: {
34
- plan: PaywallPlan;
34
+ componentId: string;
35
35
  tiers?: PriceTierFragment[] | null;
36
36
  tierUnits?: string;
37
37
  selectedTier?: PriceTierFragment;
38
38
  handleTierChange: (tier: PriceTierFragment) => void;
39
39
  }) {
40
40
  const handleChange = (event: SelectChangeEvent<unknown>, _: ReactNode) => {
41
- handleTierChange(tiers!.find(tier => tier.upTo.toString() === event.target.value)!);
41
+ if (!tiers) return;
42
+
43
+ handleTierChange(getTierByQuantity(tiers, event.target.value as number)!);
42
44
  };
43
45
 
44
46
  return (
45
47
  <Typography className="stigg-price-tier-select" style={{ minHeight: '46px' }}>
46
48
  {tiers ? (
47
49
  <TierSelect
48
- labelId="demo-simple-select-label"
49
- id={`select_${plan.id}`}
50
50
  value={selectedTier ? selectedTier.upTo.toString() : tiers[0].upTo.toString()}
51
51
  fullWidth
52
52
  onChange={handleChange}
53
+ id={componentId}
53
54
  input={<TierInput />}
54
55
  MenuProps={{
55
56
  MenuListProps: { disablePadding: true },
@@ -86,7 +86,17 @@ function getFontWeight(theme: Theme, variant: TypographyProps['variant']) {
86
86
  }
87
87
 
88
88
  export const Typography = forwardRef((props: TypographyProps, ref) => {
89
- const { children, span, style, fontWeight, variant = 'body1', color = 'primary', overrideColor, bold, ...rest } = props;
89
+ const {
90
+ children,
91
+ span,
92
+ style,
93
+ fontWeight,
94
+ variant = 'body1',
95
+ color = 'primary',
96
+ overrideColor,
97
+ bold,
98
+ ...rest
99
+ } = props;
90
100
  const theme = useTheme() as Theme;
91
101
  const level = getLevel(variant);
92
102
  const levelClassName = `typography-level-${level}`;