Skip to content

Commit

Permalink
Plan step test: Update the plan features list UI and copy (#39621)
Browse files Browse the repository at this point in the history
* Add feature title, audience and short description copy updates for the plan step ab test

* More alignment changes to match the new abtest UI

* 1. Fix import statement 2. Variable name change

* Change 'Popular' badge text to 'Most popular'

* Change CTA text to Get Started

* Exclude launch flow from test

* Fix bug that caused the variant's description text to show for control group.

* Convert to sentence case
  • Loading branch information
niranjan-uma-shankar authored Feb 27, 2020
1 parent 3a001be commit ef4e5e9
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 59 deletions.
89 changes: 58 additions & 31 deletions client/lib/plans/plans-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import i18n from 'i18n-calypso';
import { isEnabled } from 'config';
import * as constants from './constants';

const WPComGetBillingTimeframe = () => i18n.translate( 'per month, billed annually' );
const WPComGetBillingTimeframe = annualPriceText =>
annualPriceText
? `Billed at ${ annualPriceText } per year`
: i18n.translate( 'per month, billed annually' );
const WPComGetBiennialBillingTimeframe = () => i18n.translate( '/month, billed every two years' );

const getPlanBloggerDetails = () => ( {
Expand Down Expand Up @@ -72,8 +75,12 @@ const getPlanBloggerDetails = () => ( {
const getPlanPersonalDetails = () => ( {
group: constants.GROUP_WPCOM,
type: constants.TYPE_PERSONAL,
getTitle: () => i18n.translate( 'Personal' ),
getAudience: () => i18n.translate( 'Best for personal use' ),
getTitle: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates ? 'Personal plan' : i18n.translate( 'Personal' ),
getAudience: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'WordPress essentials for a basic site.'
: i18n.translate( 'Best for personal use' ),
getBlogAudience: () => i18n.translate( 'Best for personal use' ),
getPortfolioAudience: () => i18n.translate( 'Best for personal use' ),
getStoreAudience: () => i18n.translate( 'Best for personal use' ),
Expand All @@ -90,11 +97,13 @@ const getPlanPersonalDetails = () => ( {
},
}
),
getShortDescription: () =>
i18n.translate(
'Boost your website with a custom domain name, and remove all WordPress.com advertising. ' +
'Get access to high-quality email and live chat support.'
),
getShortDescription: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'Build your starter online home with basic site-editing tools, a custom domain name, and access to live chat support.'
: i18n.translate(
'Boost your website with a custom domain name, and remove all WordPress.com advertising. ' +
'Get access to high-quality email and live chat support.'
),
getPlanCompareFeatures: () => [
// pay attention to ordering, shared features should align on /plan page
constants.FEATURE_CUSTOM_DOMAIN,
Expand Down Expand Up @@ -128,8 +137,12 @@ const getPlanPersonalDetails = () => ( {
const getPlanEcommerceDetails = () => ( {
group: constants.GROUP_WPCOM,
type: constants.TYPE_ECOMMERCE,
getTitle: () => i18n.translate( 'eCommerce' ),
getAudience: () => i18n.translate( 'Best for online stores' ),
getTitle: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates ? 'eCommerce plan' : i18n.translate( 'eCommerce' ),
getAudience: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'Build a professional online store.'
: i18n.translate( 'Best for online stores' ),
getBlogAudience: () => i18n.translate( 'Best for online stores' ),
getPortfolioAudience: () => i18n.translate( 'Best for online stores' ),
getStoreAudience: () => i18n.translate( 'Best for online stores' ),
Expand All @@ -147,12 +160,14 @@ const getPlanEcommerceDetails = () => ( {
}
);
},
getShortDescription: () =>
i18n.translate(
'Sell products or services with this powerful, ' +
'all-in-one online store experience. This plan includes premium integrations and is extendable, ' +
'so it’ll grow with you as your business grows.'
),
getShortDescription: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'Start selling in no time, and create the best shopping, payment, and delivery experience for your customers.'
: i18n.translate(
'Sell products or services with this powerful, ' +
'all-in-one online store experience. This plan includes premium integrations and is extendable, ' +
'so it’ll grow with you as your business grows.'
),
getTagline: () =>
i18n.translate(
'Learn more about everything included with eCommerce and take advantage of its powerful marketplace features.'
Expand Down Expand Up @@ -219,8 +234,12 @@ const getPlanEcommerceDetails = () => ( {
const getPlanPremiumDetails = () => ( {
group: constants.GROUP_WPCOM,
type: constants.TYPE_PREMIUM,
getTitle: () => i18n.translate( 'Premium' ),
getAudience: () => i18n.translate( 'Best for freelancers' ),
getTitle: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates ? 'Premium plan' : i18n.translate( 'Premium' ),
getAudience: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'Powerful tools at a great value.'
: i18n.translate( 'Best for freelancers' ),
getBlogAudience: () => i18n.translate( 'Best for freelancers' ),
getPortfolioAudience: () => i18n.translate( 'Best for freelancers' ),
getStoreAudience: () => i18n.translate( 'Best for freelancers' ),
Expand All @@ -238,12 +257,14 @@ const getPlanPremiumDetails = () => ( {
},
}
),
getShortDescription: () =>
i18n.translate(
'Build a unique website with advanced design tools, CSS editing, lots of space for audio and video,' +
' Google Analytics support,' +
' and the ability to monetize your site with ads.'
),
getShortDescription: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'Build a sleek site with beautiful themes, robust design and monetization tools, custom CSS, and Google Analytics.'
: i18n.translate(
'Build a unique website with advanced design tools, CSS editing, lots of space for audio and video,' +
' Google Analytics support,' +
' and the ability to monetize your site with ads.'
),
getPlanCompareFeatures: () =>
compact( [
// pay attention to ordering, shared features should align on /plan page
Expand Down Expand Up @@ -290,8 +311,12 @@ const getPlanPremiumDetails = () => ( {
const getPlanBusinessDetails = () => ( {
group: constants.GROUP_WPCOM,
type: constants.TYPE_BUSINESS,
getTitle: () => i18n.translate( 'Business' ),
getAudience: () => i18n.translate( 'Best for small businesses' ),
getTitle: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates ? 'Business plan' : i18n.translate( 'Business' ),
getAudience: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'All you need for a growing business.'
: i18n.translate( 'Best for small businesses' ),
getBlogAudience: () => i18n.translate( 'Best for small businesses' ),
getPortfolioAudience: () => i18n.translate( 'Best for small businesses' ),
getStoreAudience: () => i18n.translate( 'The plan for small businesses' ),
Expand All @@ -308,11 +333,13 @@ const getPlanBusinessDetails = () => ( {
},
}
),
getShortDescription: () =>
i18n.translate(
'Power your business website with custom plugins and themes, unlimited premium and business theme templates,' +
' 200 GB storage, and the ability to remove WordPress.com branding.'
),
getShortDescription: isEligibleForPlanStepUpdates =>
isEligibleForPlanStepUpdates
? 'The full power of WordPress, unlocked: from plugins and custom themes to SFTP and phpMyAdmin, this plan has it all.'
: i18n.translate(
'Power your business website with custom plugins and themes, unlimited premium and business theme templates,' +
' 200 GB storage, and the ability to remove WordPress.com branding.'
),
getTagline: () =>
i18n.translate(
'Learn more about everything included with Business and take advantage of its professional features.'
Expand Down
13 changes: 8 additions & 5 deletions client/my-sites/plan-features/actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const PlanFeaturesActions = ( {
isPopular,
isInSignup,
isLaunchPage,
isEligibleForPlanStepTest,
onUpgradeClick = noop,
planName,
planType,
Expand Down Expand Up @@ -85,11 +86,13 @@ const PlanFeaturesActions = ( {
} );
}
} else if ( isInSignup ) {
buttonText = translate( 'Start with %(plan)s', {
args: {
plan: planName,
},
} );
buttonText = isEligibleForPlanStepTest
? 'Get started'
: translate( 'Start with %(plan)s', {
args: {
plan: planName,
},
} );
}

if (
Expand Down
17 changes: 14 additions & 3 deletions client/my-sites/plan-features/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class PlanFeaturesHeader extends Component {
selectedPlan,
title,
audience,
isEligibleForPlanStepTest,
translate,
} = this.props;

Expand All @@ -103,7 +104,12 @@ export class PlanFeaturesHeader extends Component {
{ planLevelsMatch( selectedPlan, planType ) && (
<PlanPill>{ translate( 'Suggested' ) }</PlanPill>
) }
{ popular && ! selectedPlan && <PlanPill>{ translate( 'Popular' ) }</PlanPill> }
{ popular && ! selectedPlan && ! isEligibleForPlanStepTest && (
<PlanPill>{ translate( 'Popular' ) }</PlanPill>
) }
{ popular && ! selectedPlan && isEligibleForPlanStepTest && (
<PlanPill>{ translate( 'Most popular' ) }</PlanPill>
) }
{ newPlan && ! selectedPlan && <PlanPill>{ translate( 'New' ) }</PlanPill> }
{ bestValue && ! selectedPlan && <PlanPill>{ translate( 'Best Value' ) }</PlanPill> }
</header>
Expand Down Expand Up @@ -168,6 +174,7 @@ export class PlanFeaturesHeader extends Component {
hideMonthly,
isInSignup,
plansWithScroll,
isEligibleForPlanStepTest,
} = this.props;

const isDiscounted = !! discountPrice;
Expand All @@ -177,8 +184,11 @@ export class PlanFeaturesHeader extends Component {
} );

if ( isInSignup || plansWithScroll ) {
const classes = classNames( 'plan-features__header-billing-info', {
'plan-features__header-billing-info-plan-step-test': isEligibleForPlanStepTest,
} );
return (
<div className={ 'plan-features__header-billing-info' }>
<div className={ classes }>
<span>{ billingTimeFrame }</span>
</div>
);
Expand Down Expand Up @@ -257,7 +267,7 @@ export class PlanFeaturesHeader extends Component {
}

renderPriceGroup( fullPrice, discountedPrice = null ) {
const { currencyCode, isInSignup, plansWithScroll } = this.props;
const { currencyCode, isInSignup, plansWithScroll, isEligibleForPlanStepTest } = this.props;
const displayFlatPrice = isInSignup && ! plansWithScroll;

if ( fullPrice && discountedPrice ) {
Expand Down Expand Up @@ -287,6 +297,7 @@ export class PlanFeaturesHeader extends Component {
currencyCode={ currencyCode }
rawPrice={ fullPrice }
isInSignup={ displayFlatPrice }
isEligibleForPlanStepTest={ isEligibleForPlanStepTest }
/>
);
}
Expand Down
54 changes: 43 additions & 11 deletions client/my-sites/plan-features/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ReactDOM from 'react-dom';
import { compact, get, findIndex, last, map, noop, reduce } from 'lodash';
import { connect } from 'react-redux';
import { localize } from 'i18n-calypso';
import formatCurrency from '@automattic/format-currency';
import formatCurrency, { getCurrencyObject } from '@automattic/format-currency';

/**
* Internal dependencies
Expand Down Expand Up @@ -130,7 +130,15 @@ export class PlanFeatures extends Component {
}

render() {
const { isInSignup, planProperties, plans, selectedPlan, withScroll, translate } = this.props;
const {
isInSignup,
isEligibleForPlanStepTest,
planProperties,
plans,
selectedPlan,
withScroll,
translate,
} = this.props;
const tableClasses = classNames(
'plan-features__table',
`has-${ planProperties.length }-cols`
Expand Down Expand Up @@ -176,8 +184,14 @@ export class PlanFeatures extends Component {
<tbody>
<tr>{ this.renderPlanHeaders() }</tr>
{ ! withScroll && planDescriptions }
<tr>{ this.renderTopButtons() }</tr>
{ withScroll && planDescriptions }
{ isEligibleForPlanStepTest && (
<>
{ withScroll && planDescriptions }
<tr>{ this.renderTopButtons() }</tr>
</>
) }
<tr>{ ! isEligibleForPlanStepTest && this.renderTopButtons() }</tr>
{ ! isEligibleForPlanStepTest && withScroll && planDescriptions }
{ this.renderPlanFeatureRows() }
{ ! withScroll && ! isInSignup && bottomButtons }
</tbody>
Expand Down Expand Up @@ -348,7 +362,7 @@ export class PlanFeatures extends Component {
return ReactDOM.createPortal(
<Notice className="plan-features__notice" showDismiss={ false } status="is-info">
{ translate(
"This plan was purchased by a different WordPress.com account. To manage this plan, log in to that account or contact the account owner."
'This plan was purchased by a different WordPress.com account. To manage this plan, log in to that account or contact the account owner.'
) }
</Notice>,
bannerContainer
Expand Down Expand Up @@ -462,6 +476,7 @@ export class PlanFeatures extends Component {
displayJetpackPlans,
isInSignup,
isJetpack,
isEligibleForPlanStepTest,
planProperties,
selectedPlan,
siteType,
Expand All @@ -483,11 +498,19 @@ export class PlanFeatures extends Component {
isPlaceholder,
hideMonthly,
rawPrice,
rawPriceAnnual,
} = properties;
let { discountPrice } = properties;
const classes = classNames( 'plan-features__table-item', 'has-border-top' );
let audience = planConstantObj.getAudience();
let billingTimeFrame = planConstantObj.getBillingTimeFrame();
let audience = planConstantObj.getAudience( isEligibleForPlanStepTest );

let annualPriceText;
if ( rawPriceAnnual ) {
const annualPriceObj = getCurrencyObject( rawPriceAnnual, currencyCode );
annualPriceText = `${ annualPriceObj.symbol }${ annualPriceObj.integer }`;
}

let billingTimeFrame = planConstantObj.getBillingTimeFrame( annualPriceText );

if ( disableBloggerPlanWithNonBlogDomain || this.props.nonDotBlogDomains.length > 0 ) {
if ( planMatches( planName, { type: TYPE_BLOGGER } ) ) {
Expand All @@ -507,7 +530,7 @@ export class PlanFeatures extends Component {
audience = planConstantObj.getStoreAudience();
break;
default:
audience = planConstantObj.getAudience();
audience = planConstantObj.getAudience( isEligibleForPlanStepTest );
}
}

Expand Down Expand Up @@ -537,16 +560,17 @@ export class PlanFeatures extends Component {
relatedMonthlyPlan={ relatedMonthlyPlan }
selectedPlan={ selectedPlan }
showPlanCreditsApplied={ true === showPlanCreditsApplied && ! this.hasDiscountNotice() }
title={ planConstantObj.getTitle() }
title={ planConstantObj.getTitle( isEligibleForPlanStepTest ) }
plansWithScroll={ withScroll }
isEligibleForPlanStepTest={ isEligibleForPlanStepTest }
/>
</th>
);
} );
}

renderPlanDescriptions() {
const { planProperties, withScroll } = this.props;
const { planProperties, withScroll, isEligibleForPlanStepTest } = this.props;

return map( planProperties, properties => {
const { planName, planConstantObj, isPlaceholder } = properties;
Expand All @@ -558,7 +582,7 @@ export class PlanFeatures extends Component {

let description = null;
if ( withScroll ) {
description = planConstantObj.getShortDescription( abtest );
description = planConstantObj.getShortDescription( isEligibleForPlanStepTest );
} else {
description = planConstantObj.getDescription( abtest );
}
Expand Down Expand Up @@ -623,6 +647,7 @@ export class PlanFeatures extends Component {
isInSignup,
isLandingPage,
isLaunchPage,
isEligibleForPlanStepTest,
planProperties,
selectedPlan,
selectedSiteSlug,
Expand Down Expand Up @@ -672,6 +697,7 @@ export class PlanFeatures extends Component {
isInSignup={ isInSignup }
isLandingPage={ isLandingPage }
isLaunchPage={ isLaunchPage }
isEligibleForPlanStepTest={ isEligibleForPlanStepTest }
manageHref={ `/plans/my-plan/${ selectedSiteSlug }` }
onUpgradeClick={ () => this.handleUpgradeClick( properties ) }
planName={ planConstantObj.getTitle() }
Expand Down Expand Up @@ -978,6 +1004,11 @@ export default connect(
}
const siteIsPrivateAndGoingAtomic = siteIsPrivate && isWpComEcommercePlan( plan );

const rawPriceAnnual =
showMonthlyPrice &&
ownProps.isEligibleForPlanStepTest &&
getPlanRawPrice( state, planProductId, false );

return {
availableForPurchase,
cartItemForPlan: getCartItemForPlan( getPlanSlug( state, planProductId ) ),
Expand Down Expand Up @@ -1005,6 +1036,7 @@ export default connect(
bestValue ||
plans.length === 1,
rawPrice: getPlanRawPrice( state, planProductId, showMonthlyPrice ),
rawPriceAnnual,
relatedMonthlyPlan,
siteIsPrivateAndGoingAtomic,
};
Expand Down
Loading

0 comments on commit ef4e5e9

Please sign in to comment.