From f51e4794ef766ed2844d982862e9407504c8b706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Thomas?= Date: Mon, 11 Jan 2016 14:25:37 +0100 Subject: [PATCH 1/6] Plans: Update wrong docblock comments for all actions --- client/state/sites/plans/actions.js | 35 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/client/state/sites/plans/actions.js b/client/state/sites/plans/actions.js index 1f0e0e513ddf4..74fe1eb4bb21d 100644 --- a/client/state/sites/plans/actions.js +++ b/client/state/sites/plans/actions.js @@ -19,11 +19,25 @@ import { } from './action-types'; /** - * Returns an action object to be used in fetching an object containing - * the plans for the given site. + * Clears plans for the given site. * - * @param {Object} siteId ID of the concerned site - * @return {Object} Action object + * @param {Number} siteId identifier of the site + * @returns {Function} the corresponding action thunk + */ +export function clearSitePlans( siteId ) { + return ( dispatch ) => { + dispatch( { + type: REMOVE_SITE_PLANS, + siteId + } ); + } +} + +/** + * Fetches plans for the given site. + * + * @param {Number} siteId identifier of the site + * @returns {Function} a promise that will resolve once fetching is completed */ export function fetchSitePlans( siteId ) { return ( dispatch ) => { @@ -50,9 +64,9 @@ export function fetchSitePlans( siteId ) { * Returns an action object to be used in signalling that an object containing * the plans for a given site have been received. * - * @param {Object} siteId ID of the concerned site - * @param {Object} plans Plans received - * @return {Object} Action object + * @param {Number} siteId identifier of the site + * @param {Object} plans list of plans received from the API + * @returns {Object} the corresponding action object */ export function fetchSitePlansCompleted( siteId, plans ) { plans = reject( plans, '_headers' ); @@ -65,11 +79,10 @@ export function fetchSitePlansCompleted( siteId, plans ) { } /** - * Returns an action object to be used in updating an object containing - * the plans for the given site. + * Clears plans and fetches them for the given site. * - * @param {Object} siteId ID of the concerned site - * @return {Object} Action object + * @param {Number} siteId identifier of the site + * @returns {Function} the corresponding action thunk */ export function refreshSitePlans( siteId ) { return ( dispatch ) => { From a0abc5d33cc9bbf61267e54d749f4cca0c9f0136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Thomas?= Date: Fri, 8 Jan 2016 15:25:45 +0100 Subject: [PATCH 2/6] Checkout: Redirect users on the Plans page instead of the Checkout page when starting a free trial --- client/my-sites/upgrades/checkout/checkout.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/my-sites/upgrades/checkout/checkout.jsx b/client/my-sites/upgrades/checkout/checkout.jsx index 864248dccb31e..99881ebf28a6a 100644 --- a/client/my-sites/upgrades/checkout/checkout.jsx +++ b/client/my-sites/upgrades/checkout/checkout.jsx @@ -130,9 +130,13 @@ module.exports = React.createClass( { getCheckoutCompleteRedirectPath: function() { var renewalItem; + if ( cartItems.hasRenewalItem( this.props.cart ) ) { renewalItem = cartItems.getRenewalItems( this.props.cart )[ 0 ]; + return purchasePaths.managePurchaseDestination( renewalItem.extra.purchaseDomain, renewalItem.extra.purchaseId, 'thank-you' ); + } else if ( cartItems.hasFreeTrial( this.props.cart ) ) { + return `/plans/${ this.props.sites.getSelectedSite().slug }/thank-you`; } return '/checkout/thank-you'; From 819d7b7cc308ba0a35421fbdf404bbcff12f6eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Thomas?= Date: Fri, 8 Jan 2016 16:14:44 +0100 Subject: [PATCH 3/6] Plans: Display a success notice on the Plan Overview page upon free trial subscription --- client/my-sites/plans/index.js | 2 +- client/my-sites/plans/main.jsx | 1 + client/my-sites/plans/plan-overview/index.jsx | 40 ++++++++++------- .../my-sites/plans/plan-overview/notice.jsx | 44 +++++++++++++++++++ 4 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 client/my-sites/plans/plan-overview/notice.jsx diff --git a/client/my-sites/plans/index.js b/client/my-sites/plans/index.js index e54a415878406..e0c74a0c45c37 100644 --- a/client/my-sites/plans/index.js +++ b/client/my-sites/plans/index.js @@ -43,7 +43,7 @@ module.exports = function() { ); page( - '/plans/:domain', + '/plans/:domain/:destinationType?', adTracking.retarget, controller.siteSelection, controller.navigation, diff --git a/client/my-sites/plans/main.jsx b/client/my-sites/plans/main.jsx index 5501195409f5a..38d96c7396f09 100644 --- a/client/my-sites/plans/main.jsx +++ b/client/my-sites/plans/main.jsx @@ -130,6 +130,7 @@ var Plans = React.createClass( { ); diff --git a/client/my-sites/plans/plan-overview/index.jsx b/client/my-sites/plans/plan-overview/index.jsx index 71f04e6fd6005..0eb48b8f0c5c6 100644 --- a/client/my-sites/plans/plan-overview/index.jsx +++ b/client/my-sites/plans/plan-overview/index.jsx @@ -8,6 +8,7 @@ import React from 'react'; */ import Main from 'components/main'; import PlanFeatures from 'my-sites/plans/plan-overview/plan-features'; +import PlanOverviewNotice from './notice.jsx'; import PlanStatus from 'my-sites/plans/plan-overview/plan-status'; import SidebarNavigation from 'my-sites/sidebar-navigation'; import UpgradesNavigation from 'my-sites/upgrades/navigation'; @@ -15,6 +16,7 @@ import UpgradesNavigation from 'my-sites/upgrades/navigation'; const PlanOverview = React.createClass( { propTypes: { cart: React.PropTypes.object.isRequired, + destinationType: React.PropTypes.string, plan: React.PropTypes.object.isRequired, path: React.PropTypes.string.isRequired, selectedSite: React.PropTypes.oneOfType( [ @@ -25,22 +27,28 @@ const PlanOverview = React.createClass( { render() { return ( -
- - - - - - - -
+
+ + +
+ + + + + + + +
+
); } } ); diff --git a/client/my-sites/plans/plan-overview/notice.jsx b/client/my-sites/plans/plan-overview/notice.jsx new file mode 100644 index 0000000000000..08584a9346619 --- /dev/null +++ b/client/my-sites/plans/plan-overview/notice.jsx @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import React from 'react'; + +/** + * Internal dependencies + */ +import Notice from 'components/notice'; + +const PlanOverviewNotice = React.createClass( { + propTypes: { + destinationType: React.PropTypes.string, + plan: React.PropTypes.object + }, + + getInitialState: function() { + return { + dismissed: false + }; + }, + + dismissNotice() { + this.setState( { dismissed: true } ); + }, + + render() { + if ( ! this.state.dismissed && 'thank-you' === this.props.destinationType ) { + return ( + + { + this.translate( 'Hooray, you just started your 14 day free trial of %(planName)s. Enjoy!', { + args: { planName: this.props.plan.productName } + } ) + } + + ); + } + + return null; + } +} ); + +export default PlanOverviewNotice; From 2d3c0cc0933c12f6ef07125e59fc7c61da06f969 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Sat, 9 Jan 2016 05:44:13 +0100 Subject: [PATCH 4/6] Plans: Inline success notice in Plan Overview page --- client/my-sites/plans/plan-overview/index.jsx | 26 +++++++++-- .../my-sites/plans/plan-overview/notice.jsx | 44 ------------------- 2 files changed, 22 insertions(+), 48 deletions(-) delete mode 100644 client/my-sites/plans/plan-overview/notice.jsx diff --git a/client/my-sites/plans/plan-overview/index.jsx b/client/my-sites/plans/plan-overview/index.jsx index 0eb48b8f0c5c6..440554fbc5b7b 100644 --- a/client/my-sites/plans/plan-overview/index.jsx +++ b/client/my-sites/plans/plan-overview/index.jsx @@ -2,14 +2,15 @@ * External dependencies */ import React from 'react'; +import page from 'page'; /** * Internal dependencies */ import Main from 'components/main'; import PlanFeatures from 'my-sites/plans/plan-overview/plan-features'; -import PlanOverviewNotice from './notice.jsx'; import PlanStatus from 'my-sites/plans/plan-overview/plan-status'; +import Notice from 'components/notice'; import SidebarNavigation from 'my-sites/sidebar-navigation'; import UpgradesNavigation from 'my-sites/upgrades/navigation'; @@ -25,12 +26,29 @@ const PlanOverview = React.createClass( { ] ).isRequired }, + redirectToDefault() { + page.redirect( `/plans/${ this.props.selectedSite.slug }` ); + }, + + renderNotice() { + if ( 'thank-you' === this.props.destinationType ) { + return ( + + { + this.translate( 'Hooray, you just started your 14 day free trial of %(planName)s. Enjoy!', { + args: { planName: this.props.plan.productName } + } ) + } + + ); + } + }, + render() { return (
- + + { this.renderNotice() }
diff --git a/client/my-sites/plans/plan-overview/notice.jsx b/client/my-sites/plans/plan-overview/notice.jsx deleted file mode 100644 index 08584a9346619..0000000000000 --- a/client/my-sites/plans/plan-overview/notice.jsx +++ /dev/null @@ -1,44 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; - -/** - * Internal dependencies - */ -import Notice from 'components/notice'; - -const PlanOverviewNotice = React.createClass( { - propTypes: { - destinationType: React.PropTypes.string, - plan: React.PropTypes.object - }, - - getInitialState: function() { - return { - dismissed: false - }; - }, - - dismissNotice() { - this.setState( { dismissed: true } ); - }, - - render() { - if ( ! this.state.dismissed && 'thank-you' === this.props.destinationType ) { - return ( - - { - this.translate( 'Hooray, you just started your 14 day free trial of %(planName)s. Enjoy!', { - args: { planName: this.props.plan.productName } - } ) - } - - ); - } - - return null; - } -} ); - -export default PlanOverviewNotice; From e126af70fff43637c57485acf632abb834d28cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Thomas?= Date: Mon, 11 Jan 2016 15:09:42 +0100 Subject: [PATCH 5/6] Checkout: Refactor checkout to compute the url of the final page dynamically This ensures the url of the Thank You page or any other destination page is computed when the actual checkout is performed rather than when mounting the Checkout page. This will allow us to perform some post checkout processing such as forcing refresh of data preemptively. --- client/my-sites/upgrades/checkout/checkout.jsx | 2 +- client/my-sites/upgrades/checkout/paypal-payment-box.jsx | 2 +- client/my-sites/upgrades/checkout/secure-payment-form.jsx | 3 ++- client/my-sites/upgrades/checkout/transaction-steps-mixin.jsx | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/my-sites/upgrades/checkout/checkout.jsx b/client/my-sites/upgrades/checkout/checkout.jsx index 99881ebf28a6a..98e8b89663a7f 100644 --- a/client/my-sites/upgrades/checkout/checkout.jsx +++ b/client/my-sites/upgrades/checkout/checkout.jsx @@ -166,7 +166,7 @@ module.exports = React.createClass( { cards={ this.props.cards } products={ this.props.productsList.get() } selectedSite={ selectedSite } - redirectTo={ this.getCheckoutCompleteRedirectPath() } /> + redirectTo={ this.getCheckoutCompleteRedirectPath } /> ); }, diff --git a/client/my-sites/upgrades/checkout/paypal-payment-box.jsx b/client/my-sites/upgrades/checkout/paypal-payment-box.jsx index 0b52eca6d36dd..333b0036ac9ec 100644 --- a/client/my-sites/upgrades/checkout/paypal-payment-box.jsx +++ b/client/my-sites/upgrades/checkout/paypal-payment-box.jsx @@ -75,7 +75,7 @@ module.exports = React.createClass( { } ); dataForApi = assign( {}, this.state, { - successUrl: origin + this.props.redirectTo, + successUrl: origin + this.props.redirectTo(), cancelUrl: origin + '/checkout/' + this.props.selectedSite.slug, cart: cart, domainDetails: transaction.domainDetails diff --git a/client/my-sites/upgrades/checkout/secure-payment-form.jsx b/client/my-sites/upgrades/checkout/secure-payment-form.jsx index c5f282bb8a5b4..60c35686abd2d 100644 --- a/client/my-sites/upgrades/checkout/secure-payment-form.jsx +++ b/client/my-sites/upgrades/checkout/secure-payment-form.jsx @@ -28,7 +28,8 @@ var SecurePaymentForm = React.createClass( { mixins: [ TransactionStepsMixin ], propTypes: { - products: React.PropTypes.object.isRequired + products: React.PropTypes.object.isRequired, + redirectTo: React.PropTypes.func.isRequired }, getInitialState: function() { diff --git a/client/my-sites/upgrades/checkout/transaction-steps-mixin.jsx b/client/my-sites/upgrades/checkout/transaction-steps-mixin.jsx index 4b97481f6103b..be0ff07c87780 100644 --- a/client/my-sites/upgrades/checkout/transaction-steps-mixin.jsx +++ b/client/my-sites/upgrades/checkout/transaction-steps-mixin.jsx @@ -173,7 +173,7 @@ var TransactionStepsMixin = { defer( () => { // The Thank You page throws a rendering error if this is not in a defer. - page( this.props.redirectTo ); + page( this.props.redirectTo() ); } ); } }; From 68f248919b11e5b2c99f312fccc72f8906dab3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Thomas?= Date: Mon, 11 Jan 2016 17:16:41 +0100 Subject: [PATCH 6/6] Checkout: Force a refresh of the list of sites to update the plan property This fixes the case where the plan label in the sidebar would otherwise mentions the plan name instead of the free trial. --- client/my-sites/upgrades/checkout/checkout.jsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/my-sites/upgrades/checkout/checkout.jsx b/client/my-sites/upgrades/checkout/checkout.jsx index 98e8b89663a7f..4713947f2b7f8 100644 --- a/client/my-sites/upgrades/checkout/checkout.jsx +++ b/client/my-sites/upgrades/checkout/checkout.jsx @@ -1,8 +1,9 @@ /** * External dependencies */ -var isEqual = require( 'lodash/lang/isEqual' ), +var Dispatcher = require( 'dispatcher' ), isEmpty = require( 'lodash/lang/isEmpty' ), + isEqual = require( 'lodash/lang/isEqual' ), page = require( 'page' ), React = require( 'react' ); @@ -14,6 +15,7 @@ var analytics = require( 'analytics' ), DomainDetailsForm = require( './domain-details-form' ), hasDomainDetails = require( 'lib/store-transactions' ).hasDomainDetails, observe = require( 'lib/mixins/data-observe' ), + planActions = require( 'state/sites/plans/actions' ), purchasePaths = require( 'me/purchases/paths' ), SecurePaymentForm = require( './secure-payment-form' ), upgradesActions = require( 'lib/upgrades/actions' ); @@ -120,11 +122,13 @@ module.exports = React.createClass( { if ( cartItems.hasRenewalItem( this.state.previousCart ) ) { renewalItem = cartItems.getRenewalItems( this.state.previousCart )[ 0 ]; + redirectTo = purchasePaths.managePurchase( renewalItem.extra.purchaseDomain, renewalItem.extra.purchaseId ); } } page.redirect( redirectTo ); + return true; }, @@ -136,6 +140,12 @@ module.exports = React.createClass( { return purchasePaths.managePurchaseDestination( renewalItem.extra.purchaseDomain, renewalItem.extra.purchaseId, 'thank-you' ); } else if ( cartItems.hasFreeTrial( this.props.cart ) ) { + planActions.clearSitePlans(); + + Dispatcher.handleServerAction( { + type: 'FETCH_SITES' + } ); + return `/plans/${ this.props.sites.getSelectedSite().slug }/thank-you`; }