Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better handling on My Jetpack product interstitials if site already has prodcut #36570

Merged
merged 9 commits into from
Mar 27, 2024
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const ProductDetailCard = ( {
pricingForUi,
isBundle,
supportedProducts,
hasRequiredPlan,
hasPaidPlanForProduct,
status,
pluginSlug,
postCheckoutUrl,
Expand Down Expand Up @@ -132,7 +132,7 @@ const ProductDetailCard = ( {
* Or when:
* - it's a quantity-based product
*/
const needsPurchase = ( ! isFree && ! hasRequiredPlan ) || quantity != null;
const needsPurchase = ( ! isFree && ! hasPaidPlanForProduct ) || quantity != null;

// Redirect to the referrer URL when the `redirect_to_referrer` query param is present.
const referrerURL = useRedirectToReferrer();
Expand Down Expand Up @@ -261,12 +261,13 @@ const ProductDetailCard = ( {
);
}

const hasTrialButton = ( ! isBundle || ( isBundle && ! hasRequiredPlan ) ) && trialAvailable;
const hasTrialButton =
( ! isBundle || ( isBundle && ! hasPaidPlanForProduct ) ) && trialAvailable;

// If we prefer the product name, use that everywhere instead of the title
const productMoniker = name && preferProductName ? name : title;
const defaultCtaLabel =
! isBundle && hasRequiredPlan
! isBundle && hasPaidPlanForProduct
? sprintf(
/* translators: placeholder is product name. */
__( 'Install %s', 'jetpack-my-jetpack' ),
Expand Down Expand Up @@ -361,7 +362,7 @@ const ProductDetailCard = ( {
</div>
) }

{ ( ! isBundle || ( isBundle && ! hasRequiredPlan ) ) && (
{ ( ! isBundle || ( isBundle && ! hasPaidPlanForProduct ) ) && (
<Text
component={ ProductDetailButton }
onClick={ clickHandler }
Expand All @@ -375,7 +376,7 @@ const ProductDetailCard = ( {
</Text>
) }

{ ! isBundle && trialAvailable && ! hasRequiredPlan && (
{ ! isBundle && trialAvailable && ! hasPaidPlanForProduct && (
<Text
component={ ProductDetailButton }
onClick={ trialClickHandler }
Expand Down Expand Up @@ -415,7 +416,7 @@ const ProductDetailCard = ( {
</div>
) }

{ isBundle && hasRequiredPlan && (
{ isBundle && hasPaidPlanForProduct && (
<div className={ styles[ 'product-has-required-plan' ] }>
<CheckmarkIcon size={ 36 } />
<Text>{ __( 'Active on your site', 'jetpack-my-jetpack' ) }</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { useRedirectToReferrer } from '../../hooks/use-redirect-to-referrer';
* @param {boolean} props.isFetching - True if there is a pending request to load the product.
* @param {string} props.tier - Product tier slug, i.e. 'free' or 'upgraded'.
* @param {Function} props.trackProductButtonClick - Tracks click event for the product button.
* @param {boolean} props.preferProductName - Whether to show the product name instead of the title.
* @returns {object} - ProductDetailTableColumn component.
*/
const ProductDetailTableColumn = ( {
Expand All @@ -37,15 +38,19 @@ const ProductDetailTableColumn = ( {
isFetching,
tier,
trackProductButtonClick,
preferProductName,
} ) => {
const { siteSuffix = '', myJetpackCheckoutUri = '' } = getMyJetpackWindowInitialState();

// Extract the product details.
const {
name,
featuresByTier = [],
pricingForUi: { tiers: tiersPricingForUi },
title,
postCheckoutUrl,
isBundle,
hasPaidPlanForProduct,
} = detail;

// Extract the pricing details for the provided tier.
Expand Down Expand Up @@ -120,31 +125,39 @@ const ProductDetailTableColumn = ( {
/* dummy arg to avoid bad minification */ 0
);

const callToAction =
customCallToAction ||
( isFree
? __( 'Start for Free', 'jetpack-my-jetpack' )
const productMoniker = name && preferProductName ? name : title;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, I had to lookup what the meaning/definition of Moniker is... I never heard that term before! 😆
Anyway... makes sense. 👍

const defaultCtaLabel =
! isBundle && hasPaidPlanForProduct
? sprintf(
/* translators: placeholder is product name. */
__( 'Install %s', 'jetpack-my-jetpack' ),
productMoniker
)
: sprintf(
/* translators: placeholder is product name. */
__( 'Get %s', 'jetpack-my-jetpack' ),
title,
/* dummy arg to avoid bad minification */ 0
) );
productMoniker
);
const callToAction =
customCallToAction ||
( isFree ? __( 'Start for Free', 'jetpack-my-jetpack' ) : defaultCtaLabel );

return (
<PricingTableColumn primary={ ! isFree }>
<PricingTableHeader>
{ isFree ? (
<ProductPrice price={ 0 } legend={ '' } currency={ 'USD' } hidePriceFraction />
) : (
<ProductPrice
price={ price }
offPrice={ offPrice }
legend={ priceDescription }
currency={ currencyCode }
hideDiscountLabel={ isOneMonthOffer }
hidePriceFraction
/>
! hasPaidPlanForProduct && (
<ProductPrice
price={ price }
offPrice={ offPrice }
legend={ priceDescription }
currency={ currencyCode }
hideDiscountLabel={ isOneMonthOffer }
hidePriceFraction
/>
)
) }
<Button
fullWidth
Expand Down Expand Up @@ -200,6 +213,7 @@ ProductDetailTableColumn.propTypes = {
detail: PropTypes.object.isRequired,
tier: PropTypes.string.isRequired,
trackProductButtonClick: PropTypes.func.isRequired,
preferProductName: PropTypes.bool.isRequired,
};

/**
Expand All @@ -212,18 +226,29 @@ ProductDetailTableColumn.propTypes = {
* @param {Function} props.onProductButtonClick - Click handler for the product button.
* @param {Function} props.trackProductButtonClick - Tracks click event for the product button.
* @param {boolean} props.isFetching - True if there is a pending request to load the product.
* @param {boolean} props.preferProductName - Whether to show the product name instead of the title.
* @returns {object} - ProductDetailTable react component.
*/
const ProductDetailTable = ( {
slug,
onProductButtonClick,
trackProductButtonClick,
isFetching,
preferProductName,
} ) => {
const { fileSystemWriteAccess = 'no' } = getMyJetpackWindowInitialState();

const { detail } = useProduct( slug );
const { description, featuresByTier = [], pluginSlug, status, tiers = [], title } = detail;
const {
description,
featuresByTier = [],
pluginSlug,
status,
tiers = [],
hasPaidPlanForProduct,
title,
pricingForUi: { tiers: tiersPricingForUi },
} = detail;

// If the plugin can not be installed automatically, the user will have to take extra steps.
const cantInstallPlugin = 'plugin_absent' === status && 'no' === fileSystemWriteAccess;
Expand Down Expand Up @@ -269,23 +294,36 @@ const ProductDetailTable = ( {
[ featuresByTier ]
);

const tierIsFree = tier => {
const { isFree } = tiersPricingForUi[ tier ];
return isFree;
};

return (
<>
{ cantInstallPluginNotice }

<PricingTable title={ description } items={ pricingTableItems }>
{ tiers.map( ( tier, index ) => (
<ProductDetailTableColumn
key={ index }
tier={ tier }
detail={ detail }
isFetching={ isFetching }
onProductButtonClick={ onProductButtonClick }
trackProductButtonClick={ trackProductButtonClick }
primary={ index === 0 }
cantInstallPlugin={ cantInstallPlugin }
/>
) ) }
{ tiers.map( ( tier, index ) => {
// Don't show the column if this is a free offering and we already have a plan
if ( hasPaidPlanForProduct && tierIsFree( tier ) ) {
return null;
}

return (
<ProductDetailTableColumn
key={ index }
tier={ tier }
detail={ detail }
isFetching={ isFetching }
onProductButtonClick={ onProductButtonClick }
trackProductButtonClick={ trackProductButtonClick }
primary={ index === 0 }
cantInstallPlugin={ cantInstallPlugin }
preferProductName={ preferProductName }
/>
);
} ) }
</PricingTable>
</>
);
Expand All @@ -296,6 +334,7 @@ ProductDetailTable.propTypes = {
onProductButtonClick: PropTypes.func.isRequired,
trackProductButtonClick: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
preferProductName: PropTypes.bool.isRequired,
};

export default ProductDetailTable;
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,17 @@ export default function ProductInterstitial( {
postCheckoutUrl = activatedProduct?.post_checkout_url
? activatedProduct.post_checkout_url
: myJetpackCheckoutUri;
const hasRequiredPlan = tier
? product?.hasRequiredTier?.[ tier ]
: product?.hasRequiredPlan;
// there is a separate hasRequiredTier, but it is not implemented
const hasPaidPlanForProduct = product?.hasPaidPlanForProduct;
const isFree = tier
? product?.pricingForUi?.tiers?.[ tier ]?.isFree
: product?.pricingForUi?.isFree;
const needsPurchase = ! isFree && ! hasRequiredPlan;
const needsPurchase = ! isFree && ! hasPaidPlanForProduct;

// If the product is CRM, redirect the user to the Jetpack CRM pricing page.
// This is done because CRM is not part of the WP billing system
// and we can't send them to checkout like we can with the rest of the products
if ( product.pluginSlug === 'zero-bs-crm' && ! hasRequiredPlan ) {
if ( product.pluginSlug === 'zero-bs-crm' && ! hasPaidPlanForProduct ) {
window.location.href = 'https://jetpackcrm.com/pricing/';
return;
}
Expand Down Expand Up @@ -218,6 +217,7 @@ export default function ProductInterstitial( {
clickHandler={ clickHandler }
onProductButtonClick={ clickHandler }
trackProductButtonClick={ trackProductClick }
preferProductName={ preferProductName }
isFetching={ isActivating || siteIsRegistering }
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fixed

Better handling on product interstitial pages if the site already has a paid product
2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "4.19.x-dev"
"dev-trunk": "4.20.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-initializer.php"
Expand Down
2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-my-jetpack",
"version": "4.19.1-alpha",
"version": "4.20.0-alpha",
"description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/my-jetpack/#readme",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/src/class-initializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '4.19.1-alpha';
const PACKAGE_VERSION = '4.20.0-alpha';

/**
* HTML container ID for the IDC screen on My Jetpack page.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


4 changes: 2 additions & 2 deletions projects/plugins/backup/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


4 changes: 2 additions & 2 deletions projects/plugins/boost/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: other
Comment: Updated composer.lock.


4 changes: 2 additions & 2 deletions projects/plugins/jetpack/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion projects/plugins/jetpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@automattic/jetpack-components": "workspace:*",
"@automattic/jetpack-connection": "workspace:*",
"@automattic/jetpack-licensing": "workspace:*",
"@automattic/jetpack-my-jetpack": "workspace:4.19.1-alpha",
"@automattic/jetpack-my-jetpack": "workspace:4.20.0-alpha",
"@automattic/jetpack-partner-coupon": "workspace:*",
"@automattic/jetpack-publicize-components": "workspace:*",
"@automattic/jetpack-shared-extension-utils": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Updated composer.lock.


Loading
Loading