Skip to content

Commit

Permalink
Odyssey: Add opt-out notice for odyssey (#72937)
Browse files Browse the repository at this point in the history
* add NoticeBanner component

* add opt out notice to traffice highlight section

* dismiss opt out notice

* merge same style

* replace stats setting link with search stats term

* changed key to has_opt_out_new_stats_notice

* revert unnecessary change

* notice id set to opt-out-new-stats

* rename dismissJITMDirectCall to dismissJITMDirect

* use isOdyssey to conditionally show notice

* use an array for notices

* add comment for doDismissJITMDirect
  • Loading branch information
kangzj committed Feb 7, 2023
1 parent c3e08b6 commit 366863a
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 4 deletions.
3 changes: 2 additions & 1 deletion apps/odyssey-stats/src/styles/wp-admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
& .is-section-stats .has-fixed-nav {
padding-top: 64px;
}
& .highlight-cards.has-background-color {
& .highlight-cards.has-background-color,
& .inner-notice-container.has-background-color {
background-color: var(--jetpack-white-off);
}
#wpcontent {
Expand Down
3 changes: 2 additions & 1 deletion client/lib/wpcom-xhr-wrapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export async function jetpack_site_xhr_wrapper( params, callback ) {
'X-WP-Nonce': config( 'nonce' ),
},
isRestAPI: false,
apiNamespace: 'jetpack/v4/stats-app',
apiNamespace:
params.apiNamespace === 'jetpack/v4' ? params.apiNamespace : 'jetpack/v4/stats-app',
};

return xhr( params, async function ( error, response, headers ) {
Expand Down
43 changes: 42 additions & 1 deletion client/my-sites/stats/site.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import config from '@automattic/calypso-config';
import { getUrlParts } from '@automattic/calypso-url';
import { eye } from '@automattic/components/src/icons';
import NoticeBanner from '@automattic/components/src/notice-banner';
import { Icon, people, starEmpty, commentContent } from '@wordpress/icons';
import classNames from 'classnames';
import { localize, translate } from 'i18n-calypso';
Expand Down Expand Up @@ -31,10 +32,12 @@ import {
withAnalytics,
} from 'calypso/state/analytics/actions';
import { activateModule } from 'calypso/state/jetpack/modules/actions';
import { dismissJITMDirect } from 'calypso/state/jitm/actions';
import getCurrentRouteParameterized from 'calypso/state/selectors/get-current-route-parameterized';
import isJetpackModuleActive from 'calypso/state/selectors/is-jetpack-module-active';
import isPrivateSite from 'calypso/state/selectors/is-private-site';
import { isJetpackSite } from 'calypso/state/sites/selectors';
import hasOptOutNewStatsNotice from 'calypso/state/sites/selectors/has-opt-out-new-stats-notice';
import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors';
import HighlightsSection from './highlights-section';
import MiniCarousel from './mini-carousel';
Expand Down Expand Up @@ -117,6 +120,7 @@ class StatsSite extends Component {
state = {
activeTab: null,
activeLegend: null,
isOptOutNoticeDismissed: false,
};

static getDerivedStateFromProps( props, state ) {
Expand Down Expand Up @@ -157,7 +161,16 @@ class StatsSite extends Component {
};

renderStats() {
const { date, siteId, slug, isJetpack, isSitePrivate, isOdysseyStats, context } = this.props;
const {
date,
siteId,
slug,
isJetpack,
isSitePrivate,
isOdysseyStats,
context,
showOptOutNotice,
} = this.props;

const queryDate = date.format( 'YYYY-MM-DD' );
const { period, endOf } = this.props.period;
Expand All @@ -179,6 +192,11 @@ class StatsSite extends Component {
'is-period-year': period === 'year',
} );

const dismissOptOutNotice = () => {
this.setState( { isOptOutNoticeDismissed: true } );
context.store.dispatch( dismissJITMDirect( 'opt-out-new-stats', 'opt-out-new-stats' ) );
};

return (
<div className="stats">
{ ! isOdysseyStats && (
Expand Down Expand Up @@ -215,6 +233,28 @@ class StatsSite extends Component {
slug={ slug }
/>

{ isOdysseyStats && showOptOutNotice && ! this.state.isOptOutNoticeDismissed && (
<div className="inner-notice-container has-background-color">
<NoticeBanner
level="success"
title={ translate( 'Welcome to the new Jetpack Stats!' ) }
onClose={ dismissOptOutNotice }
>
{ translate(
'{{p}}Enjoy a more modern and mobile friendly experience with new stats and insights to help you grow your site.{{/p}}{{p}}If you prefer to continue using the traditional stats, {{manageYourSettingsLink}}manage your settings{{/manageYourSettingsLink}}.{{/p}}',
{
components: {
p: <p />,
manageYourSettingsLink: (
<a href="/wp-admin/admin.php?page=jetpack#/settings?term=stats" />
),
},
}
) }
</NoticeBanner>
</div>
) }

<HighlightsSection siteId={ siteId } />

<div id="my-stats-content" className={ wrapperClass }>
Expand Down Expand Up @@ -440,6 +480,7 @@ export default connect(
showEnableStatsModule,
path: getCurrentRouteParameterized( state, siteId ),
isOdysseyStats,
showOptOutNotice: hasOptOutNewStatsNotice( state, siteId ),
};
},
{ recordGoogleEvent, enableJetpackStatsModule, recordTracksEvent }
Expand Down
7 changes: 7 additions & 0 deletions client/my-sites/stats/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@
}
}

.inner-notice-container {
padding-top: 32px;
p {
margin-bottom: 0;
}
}

// Stats section scoped styles
.is-section-stats {
&.color-scheme.is-classic-dark {
Expand Down
1 change: 1 addition & 0 deletions client/state/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ export const JETPACK_USER_CONNECTION_DATA_REQUEST_FAILURE =
export const JETPACK_USER_CONNECTION_DATA_REQUEST_SUCCESS =
'JETPACK_USER_CONNECTION_DATA_REQUEST_SUCCESS';
export const JITM_DISMISS = 'JITM_DISMISS';
export const JITM_DISMISS_DIRECT = 'JITM_DISMISS_DIRECT';
export const JITM_FETCH = 'JITM_FETCH';
export const JITM_OPEN_HELP_CENTER = 'JITM_OPEN_HELP_CENTER';
export const JITM_SET = 'JITM_SET';
Expand Down
31 changes: 30 additions & 1 deletion client/state/data-layer/wpcom/sites/jitm/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import moment from 'moment/moment';
import makeJsonSchemaParser from 'calypso/lib/make-json-schema-parser';
import { JITM_DISMISS, JITM_FETCH } from 'calypso/state/action-types';
import { JITM_DISMISS, JITM_FETCH, JITM_DISMISS_DIRECT } from 'calypso/state/action-types';
import { registerHandlers } from 'calypso/state/data-layer/handler-registry';
import { http } from 'calypso/state/data-layer/wpcom-http/actions';
import { dispatchRequest } from 'calypso/state/data-layer/wpcom-http/utils';
Expand Down Expand Up @@ -97,6 +97,28 @@ export const doDismissJITM = ( action ) =>
action
);

/**
* Dismisses a jitm on the jetpack site.
* The difference between this and doDismissJITM is that this action sends requests directly to the Jetpack site,
* instead of going through the WordPress.com rest-api endpoint.
*
* @param {Object} action The dismissal action
* @returns {Object} The HTTP fetch action
*/
export const doDismissJITMDirect = ( action ) =>
http(
{
method: 'POST',
path: '/jitm',
apiNamespace: 'jetpack/v4',
body: {
feature_class: action.featureClass,
id: action.id,
},
},
action
);

/**
* Called when the http layer receives a valid jitm
*
Expand Down Expand Up @@ -141,4 +163,11 @@ registerHandlers( 'state/data-layer/wpcom/sites/jitm/index.js', {
onError: noop,
} ),
],
[ JITM_DISMISS_DIRECT ]: [
dispatchRequest( {
fetch: doDismissJITMDirect,
onSuccess: noop,
onError: noop,
} ),
],
} );
14 changes: 14 additions & 0 deletions client/state/jitm/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
JITM_FETCH,
JITM_SET,
JITM_OPEN_HELP_CENTER,
JITM_DISMISS_DIRECT,
} from 'calypso/state/action-types';
import 'calypso/state/data-layer/wpcom/sites/jitm';
import 'calypso/state/jitm/init';
Expand All @@ -25,6 +26,19 @@ export const dismissJITM = ( siteId, id, featureClass ) => ( {
featureClass,
} );

/**
* Dismisses a jitm directly
*
* @param {string} id The id of the jitm to dismiss
* @param {string} featureClass The feature class of the jitm to dismiss
* @returns {Object} The dismiss action
*/
export const dismissJITMDirect = ( id, featureClass ) => ( {
type: JITM_DISMISS_DIRECT,
id,
featureClass,
} );

/**
* Inserts a jitm into the store for display
*
Expand Down
18 changes: 18 additions & 0 deletions client/state/sites/selectors/has-opt-out-new-stats-notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'calypso/state/ui/init';
import getRawSite from 'calypso/state/selectors/get-raw-site';

/**
* Returns whether the opt-out notice should be shown.
*
* @param {Object} state Global state tree
* @param siteId The site ID.
* @returns {?boolean} hasOptOutNotice
*/
export default function hasOptOutNewStatsNotice( state, siteId ) {
if ( ! siteId ) {
return null;
}
const site = getRawSite( state, siteId );

return site?.stats_notices?.opt_out_new_stats;
}
96 changes: 96 additions & 0 deletions packages/components/src/notice-banner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Icon, warning, info, check, close } from '@wordpress/icons';
import classNames from 'classnames';
import React from 'react';
import './style.scss';

type NoticeBannerProps = {
/** The severity of the alert. */
level: 'error' | 'warning' | 'info' | 'success';

/** The title of the NoticeBanner */
title: string;

/** A list of action elements to show across the bottom */
actions?: React.ReactNode[];

/** Hide close button */
hideCloseButton?: boolean;

/** Method to call when the close button is clicked */
onClose?: () => void;

/** Children to be rendered inside the alert. */
children: React.ReactNode;
};

const getIconByLevel = ( level: NoticeBannerProps[ 'level' ] ) => {
switch ( level ) {
case 'error':
return warning;
case 'warning':
return warning;
case 'info':
return info;
case 'success':
return check;
default:
return warning;
}
};

/**
* NoticeBanner component
*
* @param {Object} props - The component properties.
* @param {string} props.level - The notice level: error, warning, info, success.
* @param {boolean} props.hideCloseButton - Whether to hide the close button.
* @param {Function} props.onClose - The function to call when the close button is clicked.
* @param {string} props.title - The title of the notice.
* @param {React.ReactNode[]} props.actions - Actions to show across the bottom of the bar.
* @param {React.Component} props.children - The notice content.
* @returns {React.ReactElement} The `NoticeBanner` component.
*/
const NoticeBanner: React.FC< NoticeBannerProps > = ( {
level,
title,
children,
actions,
hideCloseButton,
onClose,
} ) => {
const classes = classNames( 'notice-banner', `is-${ level }` );

return (
<div className={ classes }>
<div className="notice-banner__icon-wrapper">
<Icon icon={ getIconByLevel( level ) } className="notice-banner__icon" />
</div>

<div className="notice-banner__main-content">
<div className="notice-banner__title">{ title }</div>
{ children }

{ actions && actions.length > 0 && (
<div className="notice-banner__action-bar">
{ actions.map( ( action, index ) => (
<div key={ index }>{ action }</div>
) ) }
</div>
) }
</div>

{ ! hideCloseButton && (
<button aria-label="close" className="notice-banner__close-button" onClick={ onClose }>
<Icon icon={ close } />
</button>
) }
</div>
);
};

NoticeBanner.defaultProps = {
level: 'info',
hideCloseButton: false,
};

export default NoticeBanner;
Loading

0 comments on commit 366863a

Please sign in to comment.