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

Site settings: allow site to be deleted if only purchases are non-refundable premium themes #27917

Merged
merged 4 commits into from
Oct 23, 2018
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
22 changes: 11 additions & 11 deletions client/my-sites/site-settings/delete-site/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import page from 'page';
import { some } from 'lodash';
import Gridicon from 'gridicons';
import { localize } from 'i18n-calypso';

Expand All @@ -23,7 +22,7 @@ import ActionPanelFooter from 'components/action-panel/footer';
import Button from 'components/button';
import DeleteSiteWarningDialog from 'my-sites/site-settings/delete-site-warning-dialog';
import Dialog from 'components/dialog';
import { getSitePurchases, hasLoadedSitePurchasesFromServer } from 'state/purchases/selectors';
import { hasLoadedSitePurchasesFromServer } from 'state/purchases/selectors';
import { getSelectedSiteId, getSelectedSiteSlug } from 'state/ui/selectors';
import { getSite, getSiteDomain } from 'state/sites/selectors';
import Notice from 'components/notice';
Expand All @@ -32,6 +31,7 @@ import { deleteSite } from 'state/sites/actions';
import { setSelectedSiteId } from 'state/ui/actions';
import isSiteAutomatedTransfer from 'state/selectors/is-site-automated-transfer';
import FormLabel from 'components/forms/form-label';
import hasCancelableSitePurchases from 'state/selectors/has-cancelable-site-purchases';

class DeleteSite extends Component {
static propTypes = {
Expand All @@ -40,7 +40,6 @@ class DeleteSite extends Component {
siteDomain: PropTypes.string,
siteExists: PropTypes.bool,
siteId: PropTypes.number,
sitePurchases: PropTypes.array,
siteSlug: PropTypes.string,
translate: PropTypes.func.isRequired,
};
Expand Down Expand Up @@ -79,9 +78,7 @@ class DeleteSite extends Component {
return;
}

const hasActiveSubscriptions = some( this.props.sitePurchases, 'active' );

if ( hasActiveSubscriptions ) {
if ( this.props.hasCancelablePurchases ) {
this.setState( { showWarningDialog: true } );
} else {
this.setState( { showConfirmDialog: true } );
Expand All @@ -101,10 +98,10 @@ class DeleteSite extends Component {
page( '/settings/general/' + siteSlug );
};

componentWillReceiveProps( nextProps ) {
componentDidUpdate( prevProps ) {
const { siteId, siteExists } = this.props;

if ( siteId && siteExists && ! nextProps.siteExists ) {
if ( siteId && prevProps.siteExists && ! siteExists ) {
this.props.setSelectedSiteId( null );
page.redirect( '/stats' );
}
Expand Down Expand Up @@ -263,14 +260,17 @@ class DeleteSite extends Component {
<li className="delete-site__content-list-item">
{ translate( 'Purchased Upgrades' ) }
</li>
<li className="delete-site__content-list-item">
{ translate( 'Premium Themes' ) }
</li>
</ul>
</ActionPanelFigure>
{ ! isAtomic && (
<div>
<p>
{ translate(
'Deletion {{strong}}can not{{/strong}} be undone, ' +
bluefuton marked this conversation as resolved.
Show resolved Hide resolved
'and will remove all content, contributors, domains, and upgrades from this site.',
'and will remove all content, contributors, domains, themes and upgrades from this site.',
{
components: {
strong: <strong />,
Expand Down Expand Up @@ -299,7 +299,7 @@ class DeleteSite extends Component {
<p>
{ translate(
"To delete this site, you'll need to contact our support team. Deletion can not be undone, " +
bluefuton marked this conversation as resolved.
Show resolved Hide resolved
'and will remove all content, contributors, domains, and upgrades from this site.'
'and will remove all content, contributors, domains, themes and upgrades from this site.'
) }
</p>
<p>
Expand Down Expand Up @@ -379,9 +379,9 @@ export default connect(
isAtomic: isSiteAutomatedTransfer( state, siteId ),
siteDomain,
siteId,
sitePurchases: getSitePurchases( state, siteId ),
siteSlug,
siteExists: !! getSite( state, siteId ),
hasCancelablePurchases: hasCancelableSitePurchases( state, siteId ),
};
},
{
Expand Down
21 changes: 9 additions & 12 deletions client/my-sites/site-settings/site-tools/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
/**
* External dependencies
*/

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { find, some } from 'lodash';
import { find } from 'lodash';

/**
* Internal dependencies
Expand All @@ -23,12 +22,9 @@ import { isJetpackSite, getSiteAdminUrl } from 'state/sites/selectors';
import isSiteAutomatedTransfer from 'state/selectors/is-site-automated-transfer';
import isVipSite from 'state/selectors/is-vip-site';
import getRewindState from 'state/selectors/get-rewind-state';
import {
getSitePurchases,
hasLoadedSitePurchasesFromServer,
getPurchasesError,
} from 'state/purchases/selectors';
import { hasLoadedSitePurchasesFromServer, getPurchasesError } from 'state/purchases/selectors';
import notices from 'notices';
import hasCancelableSitePurchases from 'state/selectors/has-cancelable-site-purchases';

const trackDeleteSiteOption = option => {
tracks.recordEvent( 'calypso_settings_delete_site_options', {
Expand All @@ -42,9 +38,9 @@ class SiteTools extends Component {
showStartOverDialog: false,
};

componentWillReceiveProps( nextProps ) {
if ( nextProps.purchasesError ) {
notices.error( nextProps.purchasesError );
componentDidUpdate( prevProps ) {
if ( ! prevProps.purchasesError && this.props.purchasesError ) {
notices.error( this.props.purchasesError );
}
}

Expand Down Expand Up @@ -173,11 +169,12 @@ class SiteTools extends Component {
checkForSubscriptions = event => {
trackDeleteSiteOption( 'delete-site' );

if ( this.props.isAtomic || ! some( this.props.sitePurchases, 'active' ) ) {
if ( this.props.isAtomic || ! this.props.hasCancelablePurchases ) {
return true;
}

event.preventDefault();

this.setState( { showDialog: true } );
};

Expand Down Expand Up @@ -206,7 +203,6 @@ export default connect( state => {
return {
isAtomic,
siteSlug,
sitePurchases: getSitePurchases( state, siteId ),
purchasesError: getPurchasesError( state ),
importUrl,
exportUrl,
Expand All @@ -219,5 +215,6 @@ export default connect( state => {
showDeleteSite: ( ! isJetpack || isAtomic ) && ! isVip && sitePurchasesLoaded,
showManageConnection: isJetpack && ! isAtomic,
siteId,
hasCancelablePurchases: hasCancelableSitePurchases( state, siteId ),
};
} )( localize( SiteTools ) );
37 changes: 37 additions & 0 deletions client/state/selectors/has-cancelable-site-purchases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** @format */

/**
* Internal dependencies
*/
import { getSitePurchases } from 'state/purchases/selectors';

/**
* Does the site have any current purchases that can be canceled (i.e. purchases other than premium themes)?
*
* Note: there is an is_cancelable flag on the purchase object, but it returns true for premium themes.
*
* @param {Object} state global state
* @param {Number} siteId the site ID
* @return {Boolean} if the site currently has any purchases that can be canceled.
*/
export const hasCancelableSitePurchases = ( state, siteId ) => {
if ( ! state.purchases.hasLoadedSitePurchasesFromServer ) {
return false;
}

const purchases = getSitePurchases( state, siteId ).filter( purchase => {
if ( ! purchase.active ) {
return false;
}

if ( purchase.isRefundable ) {
return true;
}

return purchase.productSlug !== 'premium_theme';
} );

return purchases && purchases.length > 0;
};

export default hasCancelableSitePurchases;
177 changes: 177 additions & 0 deletions client/state/selectors/test/has-cancelable-site-purchases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/** @format */

/**
* External dependencies
*/
import deepFreeze from 'deep-freeze';

/**
* Internal dependencies
*/
import hasCancelableSitePurchases from 'state/selectors/has-cancelable-site-purchases';

describe( 'hasCancelableSitePurchases', () => {
const targetUserId = 123;
const targetSiteId = 1337;
const examplePurchases = [
{
ID: 1,
product_name: 'domain registration',
product_slug: 'domain_registration',
blog_id: targetSiteId,
user_id: targetUserId,
active: true,
},
{
ID: 2,
product_name: 'premium plan',
blog_id: targetSiteId,
user_id: targetUserId,
product_slug: 'premium_plan',
active: true,
},
{
ID: 3,
product_name: 'premium theme',
product_slug: 'premium_theme',
blog_id: targetSiteId,
user_id: targetUserId,
active: true,
},
];

test( 'should return false because there are no purchases', () => {
const state = deepFreeze( {
purchases: {
data: [],
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: false,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( false );
} );

test( 'should return true because there are purchases from the target site', () => {
const state = deepFreeze( {
purchases: {
data: examplePurchases,
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: false,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( true );
} );

test( 'should return false because there are no purchases for this site', () => {
const state = deepFreeze( {
purchases: {
data: examplePurchases,
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: false,
},
} );

expect( hasCancelableSitePurchases( state, 65535 ) ).toBe( false );
} );

test( 'should return false because the data is not ready', () => {
const state = deepFreeze( {
purchases: {
data: examplePurchases,
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: false,
hasLoadedUserPurchasesFromServer: false,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( false );
} );

test( 'should return false because the only purchase is a non-refundable theme', () => {
const state = deepFreeze( {
purchases: {
data: [
{
ID: 3,
product_name: 'premium theme',
product_slug: 'premium_theme',
blog_id: targetSiteId,
user_id: targetUserId,
is_refundable: false,
active: true,
},
],
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: true,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( false );
} );

test( 'should return true because one of the purchases is a refundable theme', () => {
const state = deepFreeze( {
purchases: {
data: [
{
ID: 3,
product_name: 'premium theme',
product_slug: 'premium_theme',
blog_id: targetSiteId,
user_id: targetUserId,
is_refundable: true,
active: true,
},
],
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: true,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( true );
} );

test( 'should return false if the only purchase is inactive', () => {
const state = deepFreeze( {
purchases: {
data: [
{
ID: 3,
product_name: 'premium_plan',
product_slug: 'premium_plan',
blog_id: targetSiteId,
user_id: targetUserId,
is_refundable: true,
active: false,
},
],
error: null,
isFetchingSitePurchases: false,
isFetchingUserPurchases: false,
hasLoadedSitePurchasesFromServer: true,
hasLoadedUserPurchasesFromServer: true,
},
} );

expect( hasCancelableSitePurchases( state, targetSiteId ) ).toBe( false );
} );
} );