diff --git a/README.md b/README.md index 8c96c2a..f41de75 100644 --- a/README.md +++ b/README.md @@ -149,3 +149,31 @@ an unexpected way, you can use this: ## How to setup the development environment for contributions Please our [Contributors Guide](CONTRIBUTING.md). + +## Filters + +- `pricing/show_annual_in_monthly` - Set the value to `false` to make the annual + pricing display number in annual cycle (instead of monthly cycle). +- `plugin_icon` - See + [documentation](https://freemius.com/help/documentation/wordpress-sdk/opt-in-message/#opt_in_icon_customization), + the same filter is used for the pricing page. +- `pricing/disable_single_package` - Set the value to `true` to disable the + enhanced appearance of the single package plan, where every pricing takes a + new column. +- `pricing/css_path` - Set the value to the path of your custom CSS file to + override the default CSS. The path should be absolute (just like the + `plugin_icon` or the `freemius_pricing_js_path` filters). + +### Adding custom CSS in your Plugin + +Set the custom CSS path using the `pricing/css_path` filter: + +```php +add_filter( 'pricing/css_path', 'my_custom_pricing_css_path' ); +``` diff --git a/package.json b/package.json index fa19d60..5b8e4ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freemius-pricing-page", - "version": "1.0.0", + "version": "1.1.0", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.30", diff --git a/src/components/FreemiusPricingMain.js b/src/components/FreemiusPricingMain.js index e30a966..1134bc2 100644 --- a/src/components/FreemiusPricingMain.js +++ b/src/components/FreemiusPricingMain.js @@ -70,6 +70,7 @@ class FreemiusPricingMain extends Component { selectedLicenseQuantity: this.getDefaultLicenseQuantity(), upgradingToPlanID: null, license: FSConfig.license, + showAnnualInMonthly: FSConfig.show_annual_in_monthly, }; this.changeBillingCycle = this.changeBillingCycle.bind(this); @@ -186,7 +187,7 @@ class FreemiusPricingMain extends Component { return ( @@ -586,15 +587,15 @@ class FreemiusPricingMain extends Component { let pricing = pricingCollection[pricingIndex]; - if (null != pricing.monthly_price) { + if (null != pricing.monthly_price && !pricing.is_hidden) { billingCycles[BillingCycleString.MONTHLY] = true; } - if (null != pricing.annual_price) { + if (null != pricing.annual_price && !pricing.is_hidden) { billingCycles[BillingCycleString.ANNUAL] = true; } - if (null != pricing.lifetime_price) { + if (null != pricing.lifetime_price && !pricing.is_hidden) { billingCycles[BillingCycleString.LIFETIME] = true; } diff --git a/src/components/Package/index.js b/src/components/Package/index.js index cfd1bf5..ebe9def 100644 --- a/src/components/Package/index.js +++ b/src/components/Package/index.js @@ -86,14 +86,27 @@ class Package extends Component { return 'upgrade'; } - if (PlanManager.getInstance().isFreePlan(contextPlan.pricing)) { + const isContextPricingFree = PlanManager.getInstance().isFreePlan( + contextPlan.pricing + ); + const isPlanPricingFree = PlanManager.getInstance().isFreePlan( + plan.pricing + ); + + // There are some cases where we will show the Free plan, especially if we are on free plan. + // For example, there's only one plan of the product and the plan doesn't have multiple pricings. + if (isContextPricingFree && isPlanPricingFree) { + return 'none'; + } + + if (isContextPricingFree) { return 'upgrade'; } // At this point, the install has a plan. Now we need to compare the given plan with the context plan. // If the given plan is free, then it is always a downgrade. - if (PlanManager.getInstance().isFreePlan(plan.pricing)) { + if (isPlanPricingFree) { return 'downgrade'; } @@ -174,7 +187,11 @@ class Package extends Component { } } - getUndiscountedPrice(planPackage, selectedPricing) { + getUndiscountedPrice( + planPackage, + selectedPricing, + selectedPricingCycleLabel + ) { if ( BillingCycleString.ANNUAL !== this.context.selectedBillingCycle || !(this.context.annualDiscount > 0) @@ -186,15 +203,26 @@ class Package extends Component { return ; } + let amount; + + if ('mo' === selectedPricingCycleLabel) { + amount = selectedPricing.getMonthlyAmount( + BillingCycle.MONTHLY, + true, + Package.locale + ); + } else { + amount = selectedPricing.getYearlyAmount( + BillingCycle.MONTHLY, + true, + Package.locale + ); + } + return (
Normally {this.context.currencySymbols[this.context.selectedCurrency]} - {selectedPricing.getMonthlyAmount( - BillingCycle.MONTHLY, - true, - Package.locale - )}{' '} - / mo + {amount} / {selectedPricingCycleLabel}
); } @@ -264,7 +292,9 @@ class Package extends Component { pricingCollection = {}, selectedPricing = null, selectedPricingAmount = null, - supportLabel = null; + supportLabel = null, + showAnnualInMonthly = this.context.showAnnualInMonthly, + selectedPricingCycleLabel = 'mo'; if (this.props.isFirstPlanPackage) { Package.contextInstallPlanFound = false; @@ -298,15 +328,37 @@ class Package extends Component { this.previouslySelectedPricingByPlan[planPackage.id] = selectedPricing; - selectedPricingAmount = ( - BillingCycleString.ANNUAL === this.context.selectedBillingCycle - ? // The 'en-US' is intentionally hard-coded here because we are spliting the decimal by '.'. - Helper.formatNumber( - selectedPricing.getMonthlyAmount(BillingCycle.ANNUAL), - 'en-US' - ) - : selectedPricing[`${this.context.selectedBillingCycle}_price`] - ).toString(); + if (BillingCycleString.ANNUAL === this.context.selectedBillingCycle) { + if ( + true === showAnnualInMonthly || + (Helper.isUndefinedOrNull(showAnnualInMonthly) && + selectedPricing.hasMonthlyPrice()) + ) { + // The 'en-US' is intentionally hard-coded here because we are spliting the decimal by '.'. + selectedPricingAmount = Helper.formatNumber( + selectedPricing.getMonthlyAmount(BillingCycle.ANNUAL), + 'en-US' + ); + } + + if ( + false === showAnnualInMonthly || + (Helper.isUndefinedOrNull(showAnnualInMonthly) && + !selectedPricing.hasMonthlyPrice()) + ) { + // The 'en-US' is intentionally hard-coded here because we are spliting the decimal by '.'. + selectedPricingAmount = Helper.formatNumber( + selectedPricing.getYearlyAmount(BillingCycle.ANNUAL), + 'en-US' + ); + selectedPricingCycleLabel = 'yr'; + } + } else { + selectedPricingAmount = + selectedPricing[ + `${this.context.selectedBillingCycle}_price` + ].toString(); + } } if (!planPackage.hasAnySupport()) { @@ -390,7 +442,11 @@ class Package extends Component {

{planPackage.description_lines}

- {this.getUndiscountedPrice(planPackage, selectedPricing)} + {this.getUndiscountedPrice( + planPackage, + selectedPricing, + selectedPricingCycleLabel + )}
{!planPackage.is_free_plan @@ -411,7 +467,9 @@ class Package extends Component { {!planPackage.is_free_plan && BillingCycleString.LIFETIME !== this.context.selectedBillingCycle && ( - / mo + + / {selectedPricingCycleLabel} + )}
diff --git a/src/components/PackagesContainer/index.js b/src/components/PackagesContainer/index.js index 4cb884b..a9d87f7 100644 --- a/src/components/PackagesContainer/index.js +++ b/src/components/PackagesContainer/index.js @@ -10,6 +10,7 @@ import Placeholder from '../Placeholder'; import { debounce } from '../../utils/debounce'; import './style.scss'; +import { FSConfig } from '../../index'; class PackagesContainer extends Component { static contextType = FSPricingContext; @@ -323,7 +324,11 @@ class PackagesContainer extends Component { currentLicenseQuantities = {}, isSinglePlan = false; - if (this.context.paidPlansCount > 1 || 1 === licenseQuantitiesCount) { + if ( + this.context.paidPlansCount > 1 || + 1 === licenseQuantitiesCount || + true === FSConfig.disable_single_package + ) { // If there are more than one paid plans, create a package component for each plan. packages = this.context.plans; } else { diff --git a/src/entities/Pricing.js b/src/entities/Pricing.js index 2757b2c..2f24db2 100644 --- a/src/entities/Pricing.js +++ b/src/entities/Pricing.js @@ -212,6 +212,38 @@ export class Pricing { return amount; } + /** + * @param {int} billingCycle One of the following: 1, 12, 0 (for lifetime). + * @param {boolean} [format] If true, the number 1299 for example will become 1,299. + * @param {string} [locale] The country code and language code combination (e.g. 'fr-FR'). + * + * @return {string|number} + */ + getYearlyAmount(billingCycle, format, locale) { + let amount = 0.0; + + switch (billingCycle) { + case BillingCycle.MONTHLY: + amount = this.hasMonthlyPrice() + ? this.monthly_price * 12 + : this.annual_price; + break; + case BillingCycle.ANNUAL: + amount = this.hasAnnualPrice() + ? this.annual_price + : this.monthly_price * 12; + break; + } + + amount = parseFloat(amount); + + if (format) { + amount = Helper.formatNumber(amount, locale); + } + + return amount; + } + getLicenses() { return this.isUnlimited() ? UnlimitedLicenses : this.licenses; }