Skip to content

Commit

Permalink
Theme Tiers: Switching away from a theme requires confirmation (#99109)
Browse files Browse the repository at this point in the history
* We now require explicit confirmation that the user understands that by switching away from a theme that has changed the required plan he will not be able to switch back without upgrading

* Added the active theme id to the accept and decline events

* updated message shown to the user

* Refactored the isThemeAllowedOnSite to a selector

* Fixed argument order

* Exported selector in index file

* Update client/state/themes/selectors/is-theme-allowed-on-site.ts

Co-authored-by: Miguel Torres <[email protected]>

* Centered the checkbox with respect to the text

* Improved messaging around why the user can not switch back to the active theme

* We now show the checked icon when checking the checkbox

---------

Co-authored-by: Miguel Torres <[email protected]>
  • Loading branch information
rcrdortiz and mmtr authored Feb 5, 2025
1 parent fa23dc8 commit a286085
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 21 deletions.
86 changes: 76 additions & 10 deletions client/my-sites/themes/activation-modal.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { recordTracksEvent } from '@automattic/calypso-analytics';
import { Dialog, Gridicon, Button, ScreenReaderText } from '@automattic/components';
import { getPlan } from '@automattic/calypso-products';
import { Button, Dialog, Gridicon, ScreenReaderText } from '@automattic/components';
import { Onboard } from '@automattic/data-stores';
import { localizeUrl } from '@automattic/i18n-utils';
import { CheckboxControl } from '@wordpress/components';
import { translate } from 'i18n-calypso';
import PropTypes from 'prop-types';
import { Component } from 'react';
import { connect } from 'react-redux';
import { THEME_TIERS } from 'calypso/components/theme-tier/constants';
import TrackComponentView from 'calypso/lib/analytics/track-component-view';
import { getSiteDomain } from 'calypso/state/sites/selectors';
import {
acceptActivationModal,
dismissActivationModal,
activate as activateTheme,
dismissActivationModal,
} from 'calypso/state/themes/actions';
import {
getActiveTheme,
getCanonicalTheme,
getThemeIdToActivate,
getThemeTierForTheme,
isActivatingTheme,
isThemeActive,
isThemeAllowedOnSite,
shouldShowActivationModal,
getThemeIdToActivate,
getActiveTheme,
} from 'calypso/state/themes/selectors';
import { getSelectedSiteId } from 'calypso/state/ui/selectors';

Expand All @@ -45,28 +50,52 @@ export class ActivationModal extends Component {
newThemeId: PropTypes.string,
};

constructor( props ) {
super( props );
const { isCurrentThemeAllowedOnSite } = props;

this.state = {
checkboxChecked: isCurrentThemeAllowedOnSite,
};
}

onCheckboxChange = ( isChecked ) => {
this.setState( { checkboxChecked: isChecked } );
};

closeModalHandler =
( action = 'dismiss' ) =>
() => {
const { newThemeId, siteId, source } = this.props;
const { newThemeId, activeTheme, siteId, source, isCurrentThemeAllowedOnSite } = this.props;
if ( 'activeTheme' === action ) {
this.props.acceptActivationModal( newThemeId );
const eventName = ! isCurrentThemeAllowedOnSite
? 'calypso_theme_switch_plan_warning_accepted'
: 'calypso_theme_autoloading_homepage_modal_activate_click';

recordTracksEvent( 'calypso_theme_autoloading_homepage_modal_activate_click', {
recordTracksEvent( eventName, {
theme: newThemeId,
activeTheme: activeTheme.id,
} );
return this.props.activateTheme( newThemeId, siteId, { source } );
} else if ( 'dismiss' === action ) {
recordTracksEvent( 'calypso_theme_autoloading_homepage_modal_dismiss', {
const eventName = ! isCurrentThemeAllowedOnSite
? 'calypso_theme_switch_plan_warning_declined'
: 'calypso_theme_autoloading_homepage_modal_dismiss';

recordTracksEvent( eventName, {
action: 'escape',
theme: newThemeId,
activeTheme: activeTheme.id,
} );
return this.props.dismissActivationModal();
}
};

render() {
const {
isCurrentThemeAllowedOnSite,
activeThemeRequiredPlan,
newTheme,
activeTheme,
isActivating,
Expand All @@ -89,6 +118,10 @@ export class ActivationModal extends Component {
return null;
}

const eventName = ! isCurrentThemeAllowedOnSite
? 'calypso_theme_switch_plan_warning_modal_view'
: 'calypso_theme_autoloading_homepage_modal_view';

const isAIAssembler = siteIntent === SiteIntent.AIAssembler && activeTheme.id === 'assembler';
const translationArgs = {
args: {
Expand Down Expand Up @@ -127,8 +160,8 @@ export class ActivationModal extends Component {
onClose={ this.closeModalHandler( 'dismiss' ) }
>
<TrackComponentView
eventName="calypso_theme_autoloading_homepage_modal_view"
eventProperties={ { theme: newTheme.id } }
eventName={ eventName }
eventProperties={ { theme: newTheme.id, activeTheme: activeTheme.id } }
/>
<Button
className="themes__activation-modal-close-icon"
Expand All @@ -145,8 +178,34 @@ export class ActivationModal extends Component {
} ) }
</h1>
<p className="activation-modal__description">{ message }</p>
{ ! isCurrentThemeAllowedOnSite && (
<CheckboxControl
className="activation-modal__lower-tier-warning"
checked={ this.state.checkboxChecked }
onChange={ this.onCheckboxChange }
label={ translate(
'I understand I will not be able to switch back to %(themeName)s without upgrading my plan.',
{
args: { themeName: activeTheme.name },
}
) }
help={
activeThemeRequiredPlan &&
translate(
"%(themeName)s is no longer included in your plan, so you won't be able to activate it again unless you upgrade to the %(plan)s plan.",
{
args: { plan: activeThemeRequiredPlan.getTitle(), themeName: activeTheme.name },
}
)
}
/>
) }
<div className="activation-modal__actions">
<Button primary onClick={ this.closeModalHandler( 'activeTheme' ) }>
<Button
primary
disabled={ ! this.state.checkboxChecked }
onClick={ this.closeModalHandler( 'activeTheme' ) }
>
{ translate( 'Activate %(themeName)s', {
args: { themeName: newTheme.name },
} ) }
Expand All @@ -163,6 +222,11 @@ export default connect(
const siteId = getSelectedSiteId( state );
const newThemeId = getThemeIdToActivate( state );
const activeThemeId = getActiveTheme( state, siteId );
const isCurrentThemeAllowedOnSite = isThemeAllowedOnSite( state, siteId, activeThemeId );
const activeThemeTier = getThemeTierForTheme( state, activeThemeId );
const activeThemeMinimumUpsellPlan = THEME_TIERS[ activeThemeTier?.slug ]?.minimumUpsellPlan;
const activeThemeRequiredPlan =
activeThemeMinimumUpsellPlan && getPlan( activeThemeMinimumUpsellPlan );

return {
siteId,
Expand All @@ -173,6 +237,8 @@ export default connect(
isActivating: !! isActivatingTheme( state, siteId ),
isCurrentTheme: isThemeActive( state, newThemeId, siteId ),
isVisible: shouldShowActivationModal( state, newThemeId ),
isCurrentThemeAllowedOnSite,
activeThemeRequiredPlan,
};
},
{
Expand Down
4 changes: 4 additions & 0 deletions client/my-sites/themes/activation-modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
}
}

.activation-modal__lower-tier-warning {
margin-bottom: 36px;
}

.themes__activation-modal-close-icon {
position: absolute;
top: 4px;
Expand Down
13 changes: 2 additions & 11 deletions client/state/themes/hooks/use-is-theme-allowed-on-site.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { useSelector } from 'calypso/state';
import siteHasFeature from 'calypso/state/selectors/site-has-feature';
import { getThemeTierForTheme } from 'calypso/state/themes/selectors';
import { isThemeAllowedOnSite } from 'calypso/state/themes/selectors/is-theme-allowed-on-site';

export function useIsThemeAllowedOnSite( siteId: number | null, themeId: string ) {
return useSelector( ( state ) => {
const themeTier = getThemeTierForTheme( state, themeId );
const features = themeTier?.featureList ?? [ themeTier?.feature ];

return features.some(
( feature: string | null | undefined ) =>
! feature || siteHasFeature( state, siteId, feature )
);
} );
return useSelector( ( state ) => isThemeAllowedOnSite( state, siteId, themeId ) );

/* @SEE https://github.com/Automattic/dotcom-forge/issues/8028
const retainedBenefits = useTierRetainedBenefitsQuery( siteId, themeId );
Expand Down
1 change: 1 addition & 0 deletions client/state/themes/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export { isSiteEligibleForBundledSoftware } from 'calypso/state/themes/selectors
export { isSiteEligibleForManagedExternalThemes } from 'calypso/state/themes/selectors/is-site-eligible-for-managed-external-themes';
export { isThemeActivationSyncStarted } from 'calypso/state/themes/selectors/is-theme-activation-sync-started';
export { isThemeActive } from 'calypso/state/themes/selectors/is-theme-active';
export { isThemeAllowedOnSite } from 'calypso/state/themes/selectors/is-theme-allowed-on-site';
export { isThemeGutenbergFirst } from 'calypso/state/themes/selectors/is-theme-gutenberg-first';
export { isThemePremium } from 'calypso/state/themes/selectors/is-theme-premium';
export { isThemePurchased } from 'calypso/state/themes/selectors/is-theme-purchased';
Expand Down
12 changes: 12 additions & 0 deletions client/state/themes/selectors/is-theme-allowed-on-site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import siteHasFeature from 'calypso/state/selectors/site-has-feature';
import { getThemeTierForTheme } from 'calypso/state/themes/selectors';
import { IAppState } from 'calypso/state/types';

export function isThemeAllowedOnSite( state: IAppState, siteId: number | null, themeId: string ) {
const themeTier = getThemeTierForTheme( state, themeId );
const features = themeTier?.featureList ?? [ themeTier?.feature ];

return features.some(
( feature: string | null | undefined ) => ! feature || siteHasFeature( state, siteId, feature )
);
}

0 comments on commit a286085

Please sign in to comment.