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

Plans: First pass at adding Jetpack auto-config thank you page #11692

Merged
merged 1 commit into from
Mar 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions client/my-sites/upgrades/checkout-thank-you/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import GuidedTransferDetails from './guided-transfer-details';
import HappinessSupport from 'components/happiness-support';
import HeaderCake from 'components/header-cake';
import PlanThankYouCard from 'blocks/plan-thank-you-card';
import JetpackThankYouCard from './jetpack-thank-you-card';
import {
isChargeback,
isDomainMapping,
Expand Down Expand Up @@ -56,6 +57,7 @@ import { getFeatureByKey, shouldFetchSitePlans } from 'lib/plans';
import SiteRedirectDetails from './site-redirect-details';
import Notice from 'components/notice';
import upgradesPaths from 'my-sites/upgrades/paths';
import config from 'config';

function getPurchases( props ) {
return ( props.receipt.data && props.receipt.data.purchases ) || [];
Expand Down Expand Up @@ -227,6 +229,13 @@ const CheckoutThankYou = React.createClass( {
<PlanThankYouCard siteId={ this.props.selectedSite.ID } />
</Main>
);
} else if ( wasJetpackPlanPurchased && config.isEnabled( 'plans/jetpack-config-v2' ) ) {
return (
<Main className="checkout-thank-you">
{ this.renderConfirmationNotice() }
<JetpackThankYouCard siteId={ this.props.selectedSite.ID } />
</Main>
);
}

// standard thanks page
Expand Down
359 changes: 359 additions & 0 deletions client/my-sites/upgrades/checkout-thank-you/jetpack-thank-you-card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
/**
* External dependencies
*/
import React, { Component } from 'react';
import page from 'page';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import filter from 'lodash/filter';
import range from 'lodash/range';
import { localize } from 'i18n-calypso';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import PlanThankYouCard from 'blocks/plan-thank-you-card';
import FeatureExample from 'components/feature-example';
import Notice from 'components/notice';
import NoticeAction from 'components/notice/notice-action';
import Spinner from 'components/spinner';
import Gridicon from 'gridicons';
import QueryPluginKeys from 'components/data/query-plugin-keys';
import analytics from 'lib/analytics';
import JetpackSite from 'lib/site/jetpack';
import support from 'lib/url/support';

// Redux actions & selectors
import { getSelectedSite, getSelectedSiteId } from 'state/ui/selectors';
import { isJetpackSite, isRequestingSites, getRawSite } from 'state/sites/selectors';
import { getPlugin } from 'state/plugins/wporg/selectors';
import { fetchPluginData } from 'state/plugins/wporg/actions';
import { requestSites } from 'state/sites/actions';
import {
installPlugin,
Copy link
Contributor

Choose a reason for hiding this comment

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

this could go inline with import :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:)

} from 'state/plugins/premium/actions';
import {
getPluginsForSite,
getActivePlugin,
getNextPlugin,
isFinished,
isInstalling,
isRequesting,
hasRequested
} from 'state/plugins/premium/selectors';
// Store for existing plugins
import PluginsStore from 'lib/plugins/store';

class JetpackThankYouCard extends Component {
trackConfigFinished( eventName, options = null ) {
if ( ! this.sentTracks ) {
analytics.tracks.recordEvent( eventName, options );
}
this.sentTracks = true;
}

trackManualInstall() {
analytics.tracks.recordEvent( 'calypso_plans_autoconfig_click_manual_error' );
}

trackManagePlans() {
analytics.tracks.recordEvent( 'calypso_plans_autoconfig_click_manage_plans' );
}

trackContactSupport() {
analytics.tracks.recordEvent( 'calypso_plans_autoconfig_click_contact_support' );
}

// plugins for Jetpack sites require additional data from the wporg-data store
addWporgDataToPlugins( plugins ) {
return plugins.map( plugin => {
const pluginData = getPlugin( this.props.wporg, plugin.slug );
if ( ! pluginData ) {
this.props.fetchPluginData( plugin.slug );
}
return Object.assign( {}, plugin, pluginData );
} );
}

allPluginsHaveWporgData() {
const plugins = this.addWporgDataToPlugins( this.props.plugins );
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we shouldn't call addWporgDataToPlugins from allPluginsHaveWporgData

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may not need the wporg plugins data any longer since we're currently just using the plugin slug. I believe that's a holdover from the old UI where we wanted to show the plugin icon and the plugin name. I'll look into that in future iterations.

return ( plugins.length === filter( plugins, { wporg: true } ).length );
}

componentDidMount() {
window.addEventListener( 'beforeunload', this.warnIfNotFinished );
this.props.requestSites();

page.exit( '/checkout/thank-you/*', ( context, next ) => {
const confirmText = this.warnIfNotFinished( {} );
if ( ! confirmText ) {
return next();
}
if ( window.confirm( confirmText ) ) { // eslint-disable-line no-aler
next();
} else {
// save off the current path just in case context changes after this call
const currentPath = context.canonicalPath;
setTimeout( function() {
page.replace( currentPath, null, false, false );
}, 0 );
}
} );
}

componentWillUnmount() {
window.removeEventListener( 'beforeunload', this.warnIfNotFinished );
}

componentDidUpdate() {
const site = this.props.selectedSite;
if ( site &&
site.jetpack &&
site.canUpdateFiles &&
site.canManage() &&
this.allPluginsHaveWporgData() &&
! this.props.isInstalling &&
this.props.nextPlugin
) {
this.startNextPlugin( this.props.nextPlugin );
}
}

warnIfNotFinished( event ) {
const site = this.props && this.props.selectedSite;
if ( ! site ||
! site.jetpack ||
! site.canUpdateFiles ||
! site.canManage() ||
this.props.isFinished
) {
return;
}
analytics.tracks.recordEvent( 'calypso_plans_autoconfig_user_interrupt' );
const beforeUnloadText = this.props.translate( 'We haven\'t finished installing your plugins.' );
( event || window.event ).returnValue = beforeUnloadText;
return beforeUnloadText;
}

startNextPlugin( plugin ) {
// We're already installing.
if ( this.props.isInstalling ) {
return;
}

const install = this.props.installPlugin;
const site = this.props.selectedSite;

// Merge wporg info into the plugin object
plugin = Object.assign( {}, plugin, getPlugin( this.props.wporg, plugin.slug ) );

const getPluginFromStore = function() {
const sitePlugin = PluginsStore.getSitePlugin( site, plugin.slug );
if ( ! sitePlugin && PluginsStore.isFetchingSite( site ) ) {
// if the Plugins are still being fetched, we wait. We are not using flux
// store events because it would be more messy to handle the one-time-only
// callback with bound parameters than to do it this way.
return setTimeout( getPluginFromStore, 500 );
}
// Merge any site-specific info into the plugin object, setting a default plugin ID if needed
plugin = Object.assign( { id: plugin.slug }, plugin, sitePlugin );
install( plugin, site );
};
getPluginFromStore();
}

renderPlugin( key = 0, plugin ) {
const classes = classNames( 'checkout-thank-you__jetpack-plugin', {
'is-placeholder': ! plugin
} );
return (
<div key={ key } className={ classes } >
<div className="checkout-thank-you__jetpack-plugin-status-icon">
<span>
{ plugin
? this.getStatusIcon( plugin )
: this.getStatusIcon( { status: 'placeholder' } )
}
</span>
</div>
<div className="checkout-thank-you__jetpack-plugin-status-text">
<span>
{ plugin
? this.getStatusText( plugin )
: this.getStatusText( {
slug: 'placeholder',
status: 'placeholder'
} )
}
</span>
</div>
</div>
);
}

renderFeaturePlaceholders() {
const placeholderCount = !! this.props.whitelist ? 1 : 3;
return range( placeholderCount ).map( i => {
return this.renderPlugin( i );
} );
}

renderPlugins() {
const site = this.props.selectedSite;
let mappedPlugins;
if ( ! this.props.hasRequested || this.props.isRequesting || PluginsStore.isFetchingSite( site ) ) {
mappedPlugins = this.renderFeaturePlaceholders();
} else {
const plugins = this.addWporgDataToPlugins( this.props.plugins );
mappedPlugins = plugins.map( ( item ) => {
const plugin = Object.assign( {}, item, getPlugin( this.props.wporg, item.slug ) );
return this.renderPlugin( plugin.slug, plugin );
} );
}

return (
<div className="checkout-thank-you__jetpack-plugins">
{ mappedPlugins }
</div>
);
}

getStatusIcon( plugin ) {
switch ( plugin.status ) {
case 'done':
return <Gridicon icon="checkmark" size={ 18 } />;
case 'placeholder':
return 'x';
default:
return <Spinner size={ 18 } />;
}
}

getStatusText( plugin ) {
const { translate } = this.props;

if ( 'vaultpress' === plugin.slug ) {
switch ( plugin.status ) {
case 'done':
return translate( 'Backups and security active' );
default:
return translate( 'Activating backups and security' );
}
} else if ( 'akismet' === plugin.slug ) {
switch ( plugin.status ) {
case 'done':
return translate( 'Spam protection active' );
Copy link

Choose a reason for hiding this comment

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

Hi! I've found a possible matching string that has already been translated 27 times:
translate( 'Spam Protection' ) ES Score: 9
See 2 additional suggestions in the PR translation status page

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

default:
return translate( 'Activating spam protection' );
}
}

switch ( plugin.status ) {
case 'done':
return translate( 'Successfully installed & configured.' );
default:
return translate( 'Installing and configuring' );
}
}

isErrored() {
const { selectedSite } = this.props;
return selectedSite && ! selectedSite.canUpdateFiles;
}

renderErrorNotice() {
const { translate } = this.props;
if ( ! this.isErrored() ) {
return null;
}

return (
<Notice
className="checkout-thank-you__jetpack-error-notice"
showDismiss={ false }
status="is-error"
text={ translate( 'We had trouble setting up your plan.' ) }
>
<NoticeAction href={ support.JETPACK_CONTACT_SUPPORT }>
{ translate( 'Get Help' ) }
</NoticeAction>
</Notice>
);
}

renderManageNotice() {
const { translate, selectedSite } = this.props;
const manageUrl = selectedSite.getRemoteManagementURL() + '&section=plugins-setup';
return (
<Notice
className="checkout-thank-you__jetpack-manage-notice"
showDismiss={ false }
status="is-warning"
text={ translate(
'Jetpack Manage must be enabled for us to auto-configure your %(plan)s plan.',
{
args: { plan: selectedSite.plan.product_name_short }
}
) }
>
<NoticeAction href={ manageUrl }>
{ translate( 'Turn On Manage' ) }
Copy link

Choose a reason for hiding this comment

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

Hi! I've found a possible matching string that has already been translated 22 times:
translate( 'Turn on remote management' ) ES Score: 9

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

</NoticeAction>
</Notice>
);
}

render() {
const site = this.props.selectedSite;
const turnOnManage = site && ! site.canManage();
if ( ! site && this.props.isRequestingSites ) {
return (
<div className="checkout-thank-you__jetpack">
<PlanThankYouCard />
</div>
);
}

return (
<div className="checkout-thank-you__jetpack">
<QueryPluginKeys siteId={ site.ID } />
{ this.renderErrorNotice() }
{ turnOnManage && this.renderManageNotice() }
<PlanThankYouCard siteId={ site.ID } />
{ turnOnManage
? <FeatureExample>{ this.renderPlugins() }</FeatureExample>
: this.renderPlugins()
}
</div>
);
}
}

export default connect(
( state, ownProps ) => {
const siteId = getSelectedSiteId( state );
const site = getSelectedSite( state );
const whitelist = ownProps.whitelist || false;

// We need to pass the raw redux site to JetpackSite() in order to properly build the site.
const selectedSite = site && isJetpackSite( state, siteId )
? JetpackSite( getRawSite( state, siteId ) )
: site;

return {
wporg: state.plugins.wporg.items,
isRequesting: isRequesting( state, siteId ),
hasRequested: hasRequested( state, siteId ),
isInstalling: isInstalling( state, siteId, whitelist ),
isFinished: isFinished( state, siteId, whitelist ),
plugins: getPluginsForSite( state, siteId, whitelist ),
activePlugin: getActivePlugin( state, siteId, whitelist ),
nextPlugin: getNextPlugin( state, siteId, whitelist ),
selectedSite: selectedSite,
isRequestingSites: isRequestingSites( state ),
siteId
};
},
dispatch => bindActionCreators( { requestSites, fetchPluginData, installPlugin }, dispatch )
)( localize( JetpackThankYouCard ) );
Loading