@stigg/react-sdk 4.3.1 → 4.4.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.
- package/README.md +1 -1
- package/dist/components/checkout/Checkout.d.ts +5 -0
- package/dist/components/checkout/CheckoutContainer.d.ts +22 -0
- package/dist/components/checkout/CheckoutContainer.style.d.ts +26 -0
- package/dist/components/checkout/CheckoutProvider.d.ts +34 -0
- package/dist/components/checkout/components/Button.d.ts +8 -0
- package/dist/components/checkout/components/ContentLoadingSkeleton.d.ts +2 -0
- package/dist/components/checkout/components/InputField.d.ts +8 -0
- package/dist/components/checkout/components/Skeletons.style.d.ts +97 -0
- package/dist/components/checkout/components/index.d.ts +3 -0
- package/dist/components/checkout/formatting.d.ts +2 -0
- package/dist/components/checkout/hooks/index.d.ts +8 -0
- package/dist/components/checkout/hooks/useAddonsStepModel.d.ts +21 -0
- package/dist/components/checkout/hooks/useCheckoutModel.d.ts +10 -0
- package/dist/components/checkout/hooks/useCouponModel.d.ts +7 -0
- package/dist/components/checkout/hooks/useLoadCheckout.d.ts +11 -0
- package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +16 -0
- package/dist/components/checkout/hooks/usePlanStepModel.d.ts +23 -0
- package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +13 -0
- package/dist/components/checkout/hooks/useProgressBarModel.d.ts +26 -0
- package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +5 -0
- package/dist/components/checkout/hooks/useSubscriptionState.d.ts +2 -0
- package/dist/components/checkout/index.d.ts +3 -0
- package/dist/components/checkout/planHeader/PlanHeader.d.ts +7 -0
- package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +25 -0
- package/dist/components/checkout/planHeader/index.d.ts +1 -0
- package/dist/components/checkout/progressBar/CheckoutProgressBar.d.ts +2 -0
- package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +48 -0
- package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +5 -0
- package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +7 -0
- package/dist/components/checkout/promotionCode/AppliedPromotionCode.d.ts +6 -0
- package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +5 -0
- package/dist/components/checkout/promotionCode/index.d.ts +1 -0
- package/dist/components/checkout/steps/addons/CheckoutAddonsStep.d.ts +2 -0
- package/dist/components/checkout/steps/addons/CheckoutAddonsStep.style.d.ts +93 -0
- package/dist/components/checkout/steps/addons/addon.utils.d.ts +15 -0
- package/dist/components/checkout/steps/addons/index.d.ts +1 -0
- package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +20 -0
- package/dist/components/checkout/steps/payment/PaymentMethods.style.d.ts +117 -0
- package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -0
- package/dist/components/checkout/steps/payment/index.d.ts +1 -0
- package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -0
- package/dist/components/checkout/steps/payment/stripe/index.d.ts +3 -0
- package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +33 -0
- package/dist/components/checkout/steps/payment/stripe/useStripeIntegration.d.ts +5 -0
- package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +13 -0
- package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +9 -0
- package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +52 -0
- package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +16 -0
- package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -0
- package/dist/components/checkout/steps/plan/CheckoutPlanStep.style.d.ts +12 -0
- package/dist/components/checkout/steps/plan/index.d.ts +1 -0
- package/dist/components/checkout/steps/surprise/SurpriseStep.d.ts +2 -0
- package/dist/components/checkout/summary/CheckoutSuccess.d.ts +3 -0
- package/dist/components/checkout/summary/CheckoutSummary.d.ts +16 -0
- package/dist/components/checkout/summary/CheckoutSummarySkeleton.d.ts +2 -0
- package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +11 -0
- package/dist/components/checkout/summary/components/LineItems.d.ts +36 -0
- package/dist/components/checkout/summary/components/WithSkeleton.d.ts +6 -0
- package/dist/components/checkout/summary/index.d.ts +2 -0
- package/dist/components/checkout/textOverrides.d.ts +32 -0
- package/dist/components/checkout/theme.d.ts +12 -0
- package/dist/components/common/Icon.d.ts +3 -2
- package/dist/components/common/PoweredByStigg.d.ts +1 -1
- package/dist/components/{paywall/TiersLayout.d.ts → common/TiersSelectContainer.d.ts} +2 -3
- package/dist/components/common/customIcons.d.ts +19 -5
- package/dist/components/common/mapExternalTheme.d.ts +2 -1
- package/dist/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.d.ts +1 -1
- package/dist/components/hooks/useChargeSort.d.ts +3 -0
- package/dist/components/paywall/paywallTextOverrides.d.ts +0 -4
- package/dist/components/utils/calculateDiscountRate.d.ts +1 -0
- package/dist/components/utils/currencyUtils.d.ts +1 -1
- package/dist/components/utils/getPaidPriceText.d.ts +1 -3
- package/dist/components/{paywall/planPriceTier.d.ts → utils/priceTierUtils.d.ts} +3 -1
- package/dist/components/utils/priceUtils.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/react-sdk.cjs.development.js +6934 -238
- 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 +7093 -242
- package/dist/react-sdk.esm.js.map +1 -1
- package/dist/stories/Checkout.stories.d.ts +3 -0
- package/dist/stories/CustomerPortal.stories.d.ts +1 -1
- package/dist/theme/getResolvedTheme.d.ts +4 -0
- package/dist/theme/types.d.ts +4 -0
- package/package.json +11 -5
- package/src/assets/arrow-forward.svg +3 -0
- package/src/assets/arrow-right.svg +6 -0
- package/src/assets/check.svg +5 -0
- package/src/assets/close.svg +3 -0
- package/src/assets/lottie/checkout-success.json +1 -0
- package/src/assets/nyancat.svg +634 -0
- package/src/assets/outlined-checked-circle-disabled.svg +6 -0
- package/src/assets/outlined-checked-circle.svg +6 -0
- package/src/assets/outlined-circle.svg +3 -0
- package/src/assets/payment-method.svg +11 -0
- package/src/assets/plus-icon.svg +6 -0
- package/src/assets/trash.svg +8 -0
- package/src/components/checkout/Checkout.tsx +30 -0
- package/src/components/checkout/CheckoutContainer.style.ts +35 -0
- package/src/components/checkout/CheckoutContainer.tsx +99 -0
- package/src/components/checkout/CheckoutProvider.tsx +149 -0
- package/src/components/checkout/components/Button.tsx +51 -0
- package/src/components/checkout/components/ContentLoadingSkeleton.tsx +41 -0
- package/src/components/checkout/components/InputField.tsx +22 -0
- package/src/components/checkout/components/Skeletons.style.ts +27 -0
- package/src/components/checkout/components/index.ts +3 -0
- package/src/components/checkout/formatting.ts +12 -0
- package/src/components/checkout/hooks/index.ts +8 -0
- package/src/components/checkout/hooks/useAddonsStepModel.ts +96 -0
- package/src/components/checkout/hooks/useCheckoutModel.ts +32 -0
- package/src/components/checkout/hooks/useCouponModel.ts +28 -0
- package/src/components/checkout/hooks/useLoadCheckout.ts +39 -0
- package/src/components/checkout/hooks/usePaymentStepModel.ts +49 -0
- package/src/components/checkout/hooks/usePlanStepModel.ts +169 -0
- package/src/components/checkout/hooks/usePreviewSubscription.ts +82 -0
- package/src/components/checkout/hooks/useProgressBarModel.ts +89 -0
- package/src/components/checkout/hooks/useSubscriptionModel.ts +16 -0
- package/src/components/checkout/hooks/useSubscriptionState.ts +26 -0
- package/src/components/checkout/index.ts +3 -0
- package/src/components/checkout/planHeader/PlanHeader.style.tsx +23 -0
- package/src/components/checkout/planHeader/PlanHeader.tsx +59 -0
- package/src/components/checkout/planHeader/index.ts +1 -0
- package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +34 -0
- package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +53 -0
- package/src/components/checkout/promotionCode/AddPromotionCode.tsx +85 -0
- package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +39 -0
- package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +37 -0
- package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +27 -0
- package/src/components/checkout/promotionCode/index.ts +1 -0
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +24 -0
- package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +125 -0
- package/src/components/checkout/steps/addons/addon.utils.ts +68 -0
- package/src/components/checkout/steps/addons/index.ts +1 -0
- package/src/components/checkout/steps/payment/PaymentMethods.style.ts +26 -0
- package/src/components/checkout/steps/payment/PaymentMethods.tsx +86 -0
- package/src/components/checkout/steps/payment/PaymentStep.tsx +50 -0
- package/src/components/checkout/steps/payment/index.ts +1 -0
- package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +45 -0
- package/src/components/checkout/steps/payment/stripe/index.ts +3 -0
- package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +109 -0
- package/src/components/checkout/steps/payment/stripe/useStripeIntegration.ts +27 -0
- package/src/components/checkout/steps/payment/stripe/useSubmit.ts +104 -0
- package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +46 -0
- package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +63 -0
- package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +138 -0
- package/src/components/checkout/steps/plan/CheckoutPlanStep.style.tsx +6 -0
- package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +20 -0
- package/src/components/checkout/steps/plan/index.ts +1 -0
- package/src/components/checkout/steps/surprise/SurpriseStep.tsx +27 -0
- package/src/components/checkout/summary/CheckoutSuccess.tsx +32 -0
- package/src/components/checkout/summary/CheckoutSummary.tsx +288 -0
- package/src/components/checkout/summary/CheckoutSummarySkeleton.tsx +40 -0
- package/src/components/checkout/summary/components/CheckoutCaptions.tsx +120 -0
- package/src/components/checkout/summary/components/LineItems.tsx +177 -0
- package/src/components/checkout/summary/components/WithSkeleton.tsx +16 -0
- package/src/components/checkout/summary/index.ts +2 -0
- package/src/components/checkout/textOverrides.ts +62 -0
- package/src/components/checkout/theme.ts +43 -0
- package/src/components/common/Icon.tsx +17 -22
- package/src/components/common/PoweredByStigg.tsx +1 -1
- package/src/components/{paywall/TiersLayout.tsx → common/TiersSelectContainer.tsx} +8 -7
- package/src/components/common/Typography.tsx +11 -1
- package/src/components/common/customIcons.ts +19 -28
- package/src/components/common/mapExternalTheme.ts +29 -9
- package/src/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.tsx +6 -12
- package/src/components/hooks/useChargeSort.ts +17 -0
- package/src/components/paywall/Paywall.tsx +1 -1
- package/src/components/paywall/PlanOffering.tsx +1 -1
- package/src/components/paywall/PlanOfferingButton.tsx +1 -1
- package/src/components/paywall/PlanPrice.tsx +5 -13
- package/src/components/paywall/paywallTextOverrides.ts +0 -3
- package/src/components/paywall/utils/calculateUnitQuantityText.ts +9 -4
- package/src/components/utils/calculateDiscountRate.ts +1 -1
- package/src/components/utils/currencyUtils.ts +1 -1
- package/src/components/utils/getPaidPriceText.ts +4 -10
- package/src/components/utils/getPlanPrice.ts +1 -1
- package/src/components/{paywall/planPriceTier.ts → utils/priceTierUtils.ts} +25 -3
- package/src/components/utils/priceUtils.ts +10 -0
- package/src/index.ts +1 -0
- package/src/stories/Checkout.stories.tsx +61 -0
- package/src/stories/CustomerPortal.stories.tsx +1 -1
- package/src/theme/Theme.tsx +6 -6
- package/src/theme/getResolvedTheme.ts +7 -1
- package/src/theme/types.ts +4 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { BillingPeriod, SubscriptionPreviewTaxDetails } 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
|
+
appliedCreditsTitle: string;
|
|
28
|
+
taxTitle: (params: { taxDetails: SubscriptionPreviewTaxDetails }) => string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function getResolvedCheckoutLocalize(
|
|
32
|
+
localizeOverride?: DeepPartial<CheckoutLocalization>,
|
|
33
|
+
): CheckoutLocalization {
|
|
34
|
+
const checkoutDefaultLocalization: CheckoutLocalization = {
|
|
35
|
+
changePlan: 'Change plan',
|
|
36
|
+
billingPeriodsTitle: 'Billing cycle',
|
|
37
|
+
addAddonText: 'Add',
|
|
38
|
+
newPaymentMethodText: 'New payment method',
|
|
39
|
+
newPaymentMethodBillingAddressTitle: 'Billing address',
|
|
40
|
+
newPaymentMethodCardTitle: 'Payment method',
|
|
41
|
+
baseChargeText: ({ billingPeriod }) => `${formatBillingPeriod(billingPeriod)} charge`,
|
|
42
|
+
totalText: 'Total due today',
|
|
43
|
+
subTotalText: 'Subtotal',
|
|
44
|
+
addCouponCodeText: 'Add coupon code',
|
|
45
|
+
couponCodeTitle: 'Coupon code',
|
|
46
|
+
addonsSectionTitle: 'Add-ons',
|
|
47
|
+
changesWillApplyAtEndOfBillingPeriod: ({ billingPeriodEnd }) =>
|
|
48
|
+
`Your changes will apply on the end of your current billing cycle on ${moment(billingPeriodEnd).format(
|
|
49
|
+
'MMM D, YYYY',
|
|
50
|
+
)}.`,
|
|
51
|
+
checkoutButton: {
|
|
52
|
+
nextText: 'Next',
|
|
53
|
+
downgradeText: 'Downgrade',
|
|
54
|
+
upgradeText: 'Upgrade',
|
|
55
|
+
purchaseText: 'Purchase',
|
|
56
|
+
},
|
|
57
|
+
appliedCreditsTitle: 'Applied credits',
|
|
58
|
+
taxTitle: ({ taxDetails }) => `${taxDetails.displayName} (${taxDetails?.percentage}%)`,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return merge(checkoutDefaultLocalization, localizeOverride);
|
|
62
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CheckoutConfiguration } from '@stigg/js-client-sdk';
|
|
2
|
+
import { StiggTheme } from '../../theme/types';
|
|
3
|
+
import { DeepPartial } from '../../types';
|
|
4
|
+
|
|
5
|
+
export type CheckoutTheme = {
|
|
6
|
+
primary: string;
|
|
7
|
+
textColor: string;
|
|
8
|
+
backgroundColor: string;
|
|
9
|
+
borderColor: string;
|
|
10
|
+
selectionColor: string;
|
|
11
|
+
summaryBackgroundColor: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const defaultCheckoutTheme: CheckoutTheme = {
|
|
15
|
+
primary: 'rgb(50, 126, 238)',
|
|
16
|
+
textColor: 'rgb(0, 30, 108)',
|
|
17
|
+
backgroundColor: 'rgb(255, 255, 255)',
|
|
18
|
+
borderColor: 'rgb(235, 237, 243)',
|
|
19
|
+
selectionColor: 'rgb(229, 242, 255)',
|
|
20
|
+
summaryBackgroundColor: 'rgb(109, 121, 144)',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function getResolvedCheckoutTheme(
|
|
24
|
+
globalTheme: StiggTheme,
|
|
25
|
+
themeOverride?: DeepPartial<CheckoutTheme>,
|
|
26
|
+
_remoteConfiguration?: CheckoutConfiguration | null, // all the remote colors override theme colors, no need to use them here
|
|
27
|
+
): CheckoutTheme {
|
|
28
|
+
const { palette: globalPalette } = globalTheme || {};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
primary: themeOverride?.primary || globalPalette?.primary || defaultCheckoutTheme.primary,
|
|
32
|
+
textColor: themeOverride?.textColor || globalPalette?.text.primary || defaultCheckoutTheme.textColor,
|
|
33
|
+
backgroundColor:
|
|
34
|
+
themeOverride?.backgroundColor || globalPalette?.backgroundPaper || defaultCheckoutTheme.backgroundColor,
|
|
35
|
+
borderColor: themeOverride?.borderColor || globalPalette?.outlinedBorder || defaultCheckoutTheme.borderColor,
|
|
36
|
+
selectionColor:
|
|
37
|
+
themeOverride?.selectionColor || globalPalette?.backgroundSection || defaultCheckoutTheme.selectionColor,
|
|
38
|
+
summaryBackgroundColor:
|
|
39
|
+
themeOverride?.summaryBackgroundColor ||
|
|
40
|
+
globalPalette?.backgroundHighlight ||
|
|
41
|
+
defaultCheckoutTheme.summaryBackgroundColor,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -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
|
-
|
|
8
|
+
export type Icons = keyof typeof customIcons;
|
|
9
|
+
|
|
10
|
+
const IconWrapper = styled.div<{ $pathColor?: string; $rectColor?: string; $strokeColor?: string }>`
|
|
9
11
|
${({ $pathColor }) =>
|
|
10
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
$rectColor &&
|
|
21
|
+
css`
|
|
22
|
+
svg rect {
|
|
23
|
+
fill: ${$rectColor};
|
|
24
|
+
}
|
|
23
25
|
`}
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
${({ $strokeColor }) =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
28
|
-
|
|
27
|
+
export function TiersSelectContainer({
|
|
28
|
+
componentId,
|
|
29
29
|
tiers,
|
|
30
30
|
tierUnits,
|
|
31
31
|
selectedTier,
|
|
32
32
|
handleTierChange,
|
|
33
33
|
}: {
|
|
34
|
-
|
|
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
|
-
|
|
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 {
|
|
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}`;
|
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Restore,
|
|
21
|
-
MiniSchedule,
|
|
22
|
-
DollarCoin,
|
|
23
|
-
Promotions,
|
|
24
|
-
Addons,
|
|
25
|
-
ContactSupport,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type Icons = keyof typeof ICON_COMPONENTS;
|
|
1
|
+
export { default as Addons } from '../../assets/addons.svg';
|
|
2
|
+
export { default as BillingInfoCustomer } from '../../assets/billing-info-customer.svg';
|
|
3
|
+
export { default as ContactSupport } from '../../assets/contact-support.svg';
|
|
4
|
+
export { default as CreditCard } from '../../assets/credit-card.svg';
|
|
5
|
+
export { default as DollarCoin } from '../../assets/dollar-coin.svg';
|
|
6
|
+
export { default as MiniSchedule } from '../../assets/mini-schedule.svg';
|
|
7
|
+
export { default as Promotions } from '../../assets/promotions.svg';
|
|
8
|
+
export { default as Restore } from '../../assets/restore.svg';
|
|
9
|
+
export { default as ScheduleBox } from '../../assets/schedule-box.svg';
|
|
10
|
+
export { default as ScheduleClock } from '../../assets/schedule.svg';
|
|
11
|
+
export { default as RenewSubscription } from '../../assets/subscription-renews-icon.svg';
|
|
12
|
+
export { default as Trash } from '../../assets/trash.svg';
|
|
13
|
+
export { default as PaymentMethod } from '../../assets/payment-method.svg';
|
|
14
|
+
export { default as OutlinedCircle } from '../../assets/outlined-circle.svg';
|
|
15
|
+
export { default as OutlinedCheckedCircle } from '../../assets/outlined-checked-circle.svg';
|
|
16
|
+
export { default as OutlinedCheckedCircleDisabled } from '../../assets/outlined-checked-circle-disabled.svg';
|
|
17
|
+
export { default as ArrowForward } from '../../assets/arrow-forward.svg';
|
|
18
|
+
export { default as Close } from '../../assets/close.svg';
|
|
19
|
+
export { default as Check } from '../../assets/check.svg';
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Alignment,
|
|
3
|
+
CheckoutConfiguration,
|
|
3
4
|
CustomerPortalConfiguration,
|
|
4
5
|
FontWeight as JSFontWeight,
|
|
5
6
|
PaywallConfiguration,
|
|
7
|
+
TypographyConfiguration,
|
|
6
8
|
} from '@stigg/js-client-sdk';
|
|
7
9
|
import { CustomizedTheme } from '../../theme/Theme';
|
|
8
10
|
import { HorizontalAlignment, FontWeight } from '../../theme/types';
|
|
9
11
|
|
|
10
|
-
function addCssUnits(value?: number | null, unit
|
|
12
|
+
function addCssUnits(value?: number | null, unit = 'px') {
|
|
11
13
|
if (!value) {
|
|
12
14
|
return undefined;
|
|
13
15
|
}
|
|
@@ -29,10 +31,7 @@ function mapFontWeight(defaultValue: FontWeight, fontWeight?: JSFontWeight | nul
|
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function mapTypography(
|
|
33
|
-
configuration: CustomerPortalConfiguration | PaywallConfiguration,
|
|
34
|
-
): CustomizedTheme['typography'] {
|
|
35
|
-
const { typography } = configuration;
|
|
34
|
+
function mapTypography(typography?: TypographyConfiguration | null): CustomizedTheme['typography'] {
|
|
36
35
|
return {
|
|
37
36
|
fontFamilyUrl: typography?.fontFamily || undefined,
|
|
38
37
|
h1: {
|
|
@@ -58,6 +57,7 @@ function mapAlignment(alignment?: Alignment | null): HorizontalAlignment | undef
|
|
|
58
57
|
if (!alignment) {
|
|
59
58
|
return undefined;
|
|
60
59
|
}
|
|
60
|
+
|
|
61
61
|
switch (alignment) {
|
|
62
62
|
case Alignment.Left:
|
|
63
63
|
return 'left';
|
|
@@ -65,11 +65,14 @@ function mapAlignment(alignment?: Alignment | null): HorizontalAlignment | undef
|
|
|
65
65
|
return 'center';
|
|
66
66
|
case Alignment.Right:
|
|
67
67
|
return 'right';
|
|
68
|
+
default: {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export function mapCustomerPortalConfiguration(configuration: CustomerPortalConfiguration): CustomizedTheme {
|
|
72
|
-
const { palette } = configuration;
|
|
75
|
+
const { palette, typography } = configuration;
|
|
73
76
|
return {
|
|
74
77
|
palette: {
|
|
75
78
|
primary: palette?.primary || undefined,
|
|
@@ -80,12 +83,12 @@ export function mapCustomerPortalConfiguration(configuration: CustomerPortalConf
|
|
|
80
83
|
primary: palette?.textColor || undefined,
|
|
81
84
|
},
|
|
82
85
|
},
|
|
83
|
-
typography: mapTypography(
|
|
86
|
+
typography: mapTypography(typography),
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
export function mapPaywallConfiguration(paywallConfiguration: PaywallConfiguration): CustomizedTheme {
|
|
88
|
-
const { palette, layout, customCss } = paywallConfiguration;
|
|
91
|
+
const { palette, layout, customCss, typography } = paywallConfiguration;
|
|
89
92
|
return {
|
|
90
93
|
customCss: customCss || undefined,
|
|
91
94
|
palette: {
|
|
@@ -97,7 +100,7 @@ export function mapPaywallConfiguration(paywallConfiguration: PaywallConfigurati
|
|
|
97
100
|
primary: palette?.textColor || undefined,
|
|
98
101
|
},
|
|
99
102
|
},
|
|
100
|
-
typography: mapTypography(
|
|
103
|
+
typography: mapTypography(typography),
|
|
101
104
|
layout: {
|
|
102
105
|
ctaAlignment: mapAlignment(layout?.alignment),
|
|
103
106
|
headerAlignment: mapAlignment(layout?.alignment),
|
|
@@ -109,3 +112,20 @@ export function mapPaywallConfiguration(paywallConfiguration: PaywallConfigurati
|
|
|
109
112
|
},
|
|
110
113
|
};
|
|
111
114
|
}
|
|
115
|
+
|
|
116
|
+
export function mapCheckoutConfiguration(configuration: CheckoutConfiguration): CustomizedTheme {
|
|
117
|
+
const { palette, typography } = configuration;
|
|
118
|
+
return {
|
|
119
|
+
palette: {
|
|
120
|
+
primary: palette?.primary || undefined,
|
|
121
|
+
backgroundPaper: palette?.backgroundColor || undefined,
|
|
122
|
+
outlinedBorder: palette?.borderColor || undefined,
|
|
123
|
+
text: {
|
|
124
|
+
primary: palette?.textColor || undefined,
|
|
125
|
+
},
|
|
126
|
+
backgroundHighlight: palette?.selectionColor || undefined,
|
|
127
|
+
backgroundSection: palette?.summaryBackgroundColor || undefined,
|
|
128
|
+
},
|
|
129
|
+
typography: mapTypography(typography),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -10,8 +10,7 @@ import { CustomerPortalSection } from '../../CustomerPortal';
|
|
|
10
10
|
import { CustomerPortalLocalization } from '../../customerPortalTextOverrides';
|
|
11
11
|
import { StyledTabs, TabContent, TabsLayout } from './SubscriptionTabs.style';
|
|
12
12
|
import { CustomerPortalTheme } from '../../customerPortalTheme';
|
|
13
|
-
import { Icon } from '../../../common/Icon';
|
|
14
|
-
import { Icons } from '../../../common/customIcons';
|
|
13
|
+
import { Icon, Icons } from '../../../common/Icon';
|
|
15
14
|
import { FontWeight } from 'styled-typography';
|
|
16
15
|
|
|
17
16
|
type TabPanelProps = {
|
|
@@ -41,21 +40,16 @@ export type SubscriptionTabsProps = {
|
|
|
41
40
|
function TabTitle({ isSelected, label, icon }: { isSelected: boolean; label: string; icon: Icons }) {
|
|
42
41
|
const color = isSelected ? 'primary' : 'secondary';
|
|
43
42
|
return (
|
|
44
|
-
<div style={{display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
43
|
+
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
45
44
|
<Icon icon={icon} svgStrokeColor={color} />
|
|
46
|
-
<Typography variant="h6" bold={isSelected} fontWeight={FontWeight.Medium}
|
|
45
|
+
<Typography variant="h6" bold={isSelected} fontWeight={FontWeight.Medium} color={color}>
|
|
47
46
|
{label}
|
|
48
47
|
</Typography>
|
|
49
48
|
</div>
|
|
50
49
|
);
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
export function SubscriptionTabs({
|
|
54
|
-
customerPortal,
|
|
55
|
-
hiddenSections,
|
|
56
|
-
textOverrides,
|
|
57
|
-
theme,
|
|
58
|
-
}: SubscriptionTabsProps) {
|
|
52
|
+
export function SubscriptionTabs({ customerPortal, hiddenSections, textOverrides, theme }: SubscriptionTabsProps) {
|
|
59
53
|
const allAddons = flatMap(customerPortal.subscriptions, subscription => subscription.addons);
|
|
60
54
|
const isSectionHidden = (sectionName: CustomerPortalSection) =>
|
|
61
55
|
hiddenSections?.some(section => section === sectionName);
|
|
@@ -81,14 +75,14 @@ export function SubscriptionTabs({
|
|
|
81
75
|
<Tab
|
|
82
76
|
sx={{ textTransform: 'none' }}
|
|
83
77
|
value={1}
|
|
84
|
-
label={<TabTitle isSelected={value === 1} label={textOverrides.addonsTabTitle} icon=
|
|
78
|
+
label={<TabTitle isSelected={value === 1} label={textOverrides.addonsTabTitle} icon="Addons" />}
|
|
85
79
|
/>
|
|
86
80
|
) : null}
|
|
87
81
|
{showPromotions ? (
|
|
88
82
|
<Tab
|
|
89
83
|
sx={{ textTransform: 'none' }}
|
|
90
84
|
value={2}
|
|
91
|
-
label={<TabTitle isSelected={value === 2} label={textOverrides.promotionsTabTitle} icon=
|
|
85
|
+
label={<TabTitle isSelected={value === 2} label={textOverrides.promotionsTabTitle} icon="Promotions" />}
|
|
92
86
|
/>
|
|
93
87
|
) : null}
|
|
94
88
|
</StyledTabs>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import sortBy from 'lodash/sortBy';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { BillingModel, Price } from '@stigg/js-client-sdk';
|
|
5
|
+
|
|
6
|
+
const CHARGES_BILLING_MODEL_ORDER = [BillingModel.FlatFee, BillingModel.PerUnit, BillingModel.UsageBased];
|
|
7
|
+
|
|
8
|
+
export const sortCharges = (charges: Price[]) => {
|
|
9
|
+
return sortBy(charges, [
|
|
10
|
+
charge => CHARGES_BILLING_MODEL_ORDER.indexOf(charge.pricingModel),
|
|
11
|
+
charge => charge.feature?.displayName,
|
|
12
|
+
]);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useChargesSort = (charges: Price[]) => {
|
|
16
|
+
return useMemo(() => sortCharges(charges), [charges]);
|
|
17
|
+
};
|
|
@@ -11,7 +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 '
|
|
14
|
+
import { getSelectedTier } from '../utils/priceTierUtils';
|
|
15
15
|
|
|
16
16
|
const PaywallPlansContainer = styled.div`
|
|
17
17
|
color: ${({ theme }) => theme.stigg.palette.text.primary};
|
|
@@ -11,7 +11,7 @@ import classNames from 'classnames';
|
|
|
11
11
|
import { Grid } from '@mui/material';
|
|
12
12
|
import MiniSchedule from '../../assets/mini-schedule.svg';
|
|
13
13
|
import { PlanPrice } from './PlanPrice';
|
|
14
|
-
import { getSelectedTier } from '
|
|
14
|
+
import { getSelectedTier } from '../utils/priceTierUtils';
|
|
15
15
|
|
|
16
16
|
const PlanOfferingContainer = styled.div<{ $isHighlighted: boolean; $isCurrentPlan: boolean }>`
|
|
17
17
|
position: relative;
|
|
@@ -9,7 +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 '
|
|
12
|
+
import { compareSelectedTierToCurrentTier, PriceTierComparison } from '../utils/priceTierUtils';
|
|
13
13
|
|
|
14
14
|
const LoadingIndicator = styled(ClipLoader)`
|
|
15
15
|
margin-left: 10px;
|
|
@@ -5,7 +5,7 @@ import { Typography } from '../common/Typography';
|
|
|
5
5
|
import React, { useEffect, useState } from 'react';
|
|
6
6
|
import styled from '@emotion/styled/macro';
|
|
7
7
|
import { BillingPeriod, PriceTierFragment } from '@stigg/js-client-sdk';
|
|
8
|
-
import {
|
|
8
|
+
import { TiersSelectContainer } from '../common/TiersSelectContainer';
|
|
9
9
|
|
|
10
10
|
const EMPTY_CHAR = '';
|
|
11
11
|
|
|
@@ -28,21 +28,14 @@ type PriceBillingPeriodProps = {
|
|
|
28
28
|
billingPeriod: BillingPeriod;
|
|
29
29
|
hasMonthlyPrice: boolean;
|
|
30
30
|
hasAnnuallyPrice: boolean;
|
|
31
|
-
paywallLocale: PaywallLocalization;
|
|
32
31
|
};
|
|
33
32
|
|
|
34
|
-
function PriceBillingPeriod({
|
|
35
|
-
plan,
|
|
36
|
-
billingPeriod,
|
|
37
|
-
hasMonthlyPrice,
|
|
38
|
-
hasAnnuallyPrice,
|
|
39
|
-
paywallLocale,
|
|
40
|
-
}: PriceBillingPeriodProps) {
|
|
33
|
+
function PriceBillingPeriod({ plan, billingPeriod, hasMonthlyPrice, hasAnnuallyPrice }: PriceBillingPeriodProps) {
|
|
41
34
|
const hasPrice = plan.pricePoints.find(pricePoint => pricePoint.billingPeriod === billingPeriod);
|
|
42
35
|
|
|
43
36
|
let content = EMPTY_CHAR;
|
|
44
37
|
if (hasPrice && hasMonthlyPrice && hasAnnuallyPrice) {
|
|
45
|
-
content =
|
|
38
|
+
content = `, billed ${billingPeriod.toLowerCase()}`;
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
return (
|
|
@@ -140,14 +133,13 @@ export const PlanPrice = ({
|
|
|
140
133
|
billingPeriod={billingPeriod}
|
|
141
134
|
hasAnnuallyPrice={hasAnnuallyPrice}
|
|
142
135
|
hasMonthlyPrice={hasMonthlyPrice}
|
|
143
|
-
paywallLocale={paywallLocale}
|
|
144
136
|
/>
|
|
145
137
|
</Typography>
|
|
146
138
|
)}
|
|
147
139
|
|
|
148
140
|
{withTiersRow && (
|
|
149
|
-
<
|
|
150
|
-
|
|
141
|
+
<TiersSelectContainer
|
|
142
|
+
componentId={`${plan.id}_${featureId}_tier`}
|
|
151
143
|
tiers={tiers}
|
|
152
144
|
tierUnits={tierUnits}
|
|
153
145
|
selectedTier={selectedTier}
|
|
@@ -26,8 +26,6 @@ export type PaywallLocalization = {
|
|
|
26
26
|
};
|
|
27
27
|
price: {
|
|
28
28
|
startingAtCaption: string;
|
|
29
|
-
billingPeriod?: (billingPeriod: BillingPeriod) => string;
|
|
30
|
-
pricePeriod: (billingPeriod: BillingPeriod) => string;
|
|
31
29
|
custom: string;
|
|
32
30
|
priceNotSet: string;
|
|
33
31
|
free: PlanPriceText | ((currency?: PaywallCurrency) => PlanPriceText);
|
|
@@ -58,7 +56,6 @@ export function getResolvedPaywallLocalize(localizeOverride?: DeepPartial<Paywal
|
|
|
58
56
|
},
|
|
59
57
|
price: {
|
|
60
58
|
startingAtCaption: 'Starts at',
|
|
61
|
-
pricePeriod: (billingPeriod: BillingPeriod) => (billingPeriod === BillingPeriod.Monthly ? '/ month' : '/ year'),
|
|
62
59
|
free: currency => ({
|
|
63
60
|
price: `${currency?.symbol}0`,
|
|
64
61
|
}),
|
|
@@ -3,16 +3,21 @@ export function calculateUnitQuantityText(
|
|
|
3
3
|
maxUnitQuantity?: number | null,
|
|
4
4
|
unitsPlural?: string | null,
|
|
5
5
|
) {
|
|
6
|
+
const unitsText = unitsPlural ? ` ${unitsPlural}` : '';
|
|
7
|
+
|
|
6
8
|
// copy for only maxunit
|
|
7
9
|
if ((!minUnitQuantity || minUnitQuantity === 1) && maxUnitQuantity) {
|
|
8
|
-
return `Up to ${maxUnitQuantity}
|
|
10
|
+
return `Up to ${maxUnitQuantity}${unitsText}`;
|
|
9
11
|
}
|
|
12
|
+
|
|
10
13
|
// both limits
|
|
11
14
|
if (minUnitQuantity && minUnitQuantity > 1 && maxUnitQuantity) {
|
|
12
|
-
return `${minUnitQuantity}-${maxUnitQuantity}
|
|
15
|
+
return `${minUnitQuantity}-${maxUnitQuantity}${unitsText}`;
|
|
13
16
|
}
|
|
17
|
+
|
|
14
18
|
if (minUnitQuantity && minUnitQuantity > 1) {
|
|
15
|
-
return `Minimum ${minUnitQuantity}
|
|
19
|
+
return `Minimum ${minUnitQuantity}${unitsText}`;
|
|
16
20
|
}
|
|
21
|
+
|
|
17
22
|
return '';
|
|
18
|
-
}
|
|
23
|
+
}
|
|
@@ -2,7 +2,7 @@ import { BillingPeriod, PaywallCalculatedPricePoint, Price } from '@stigg/js-cli
|
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { PaywallPlan } from '../paywall';
|
|
4
4
|
|
|
5
|
-
function calculateDiscountRate(monthlyPrice?: number | null, annuallyPrice?: number | null) {
|
|
5
|
+
export function calculateDiscountRate(monthlyPrice?: number | null, annuallyPrice?: number | null) {
|
|
6
6
|
if (!isNil(monthlyPrice) && !isNil(annuallyPrice)) {
|
|
7
7
|
const annuallyPerMonthPrice = annuallyPrice / 12;
|
|
8
8
|
return Math.round(((monthlyPrice - annuallyPerMonthPrice) / monthlyPrice) * 100);
|