Add Inbox note action indication (https://github.com/woocommerce/woocommerce-admin/pull/3039)
* Move inbox note actions to a bespoke component. * Set busy state on action buttons on click. * Allow for Note actions to be deleted. * Update FB extension note after installation is complete. * Link actions don't get busy treatment. * Re-fetch note actions after updating. Get new action IDs from the database. * Add tracking to inbox note views. (https://github.com/woocommerce/woocommerce-admin/pull/3096) * Move inbox note content to its own component. * Send a tracks event when inbox notes are in the viewport. Uses react-visibility-sensor. * Match event data to `inbox_action_click`.
This commit is contained in:
parent
1ac8577fc2
commit
26f23def50
|
@ -0,0 +1,79 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button } from '@wordpress/components';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { ADMIN_URL as adminUrl } from '@woocommerce/wc-admin-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
class InboxNoteAction extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = {
|
||||
inAction: false,
|
||||
};
|
||||
|
||||
this.handleActionClick = this.handleActionClick.bind( this );
|
||||
}
|
||||
|
||||
handleActionClick( event ) {
|
||||
const { action, noteId, triggerNoteAction } = this.props;
|
||||
const href = event.target.href || '';
|
||||
let inAction = true;
|
||||
|
||||
if ( href.length && ! href.startsWith( adminUrl ) ) {
|
||||
event.preventDefault();
|
||||
inAction = false; // link buttons shouldn't be "busy".
|
||||
window.open( href, '_blank' );
|
||||
}
|
||||
|
||||
this.setState( { inAction }, () => triggerNoteAction( noteId, action.id ) );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { action } = this.props;
|
||||
return (
|
||||
<Button
|
||||
isDefault
|
||||
isPrimary={ action.primary }
|
||||
isBusy={ this.state.inAction }
|
||||
disabled={ this.state.inAction }
|
||||
href={ action.url || undefined }
|
||||
onClick={ this.handleActionClick }
|
||||
>
|
||||
{ action.label }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InboxNoteAction.propTypes = {
|
||||
noteId: PropTypes.number,
|
||||
action: PropTypes.shape( {
|
||||
id: PropTypes.number.isRequired,
|
||||
url: PropTypes.string,
|
||||
label: PropTypes.string.isRequired,
|
||||
primary: PropTypes.bool.isRequired,
|
||||
} ),
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withDispatch( dispatch => {
|
||||
const { triggerNoteAction } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
triggerNoteAction,
|
||||
};
|
||||
} )
|
||||
)( InboxNoteAction );
|
|
@ -0,0 +1,104 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import Gridicon from 'gridicons';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ActivityCard } from '../../activity-card';
|
||||
import NoteAction from './action';
|
||||
import sanitizeHTML from 'lib/sanitize-html';
|
||||
import classnames from 'classnames';
|
||||
import { recordEvent } from 'lib/tracks';
|
||||
|
||||
class InboxNoteCard extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.onVisible = this.onVisible.bind( this );
|
||||
this.hasBeenSeen = false;
|
||||
}
|
||||
|
||||
// Trigger a view Tracks event when the note is seen.
|
||||
onVisible( isVisible ) {
|
||||
if ( isVisible && ! this.hasBeenSeen ) {
|
||||
const { note } = this.props;
|
||||
const {
|
||||
content: note_content,
|
||||
name: note_name,
|
||||
title: note_title,
|
||||
type: note_type,
|
||||
icon: note_icon,
|
||||
} = note;
|
||||
|
||||
recordEvent( 'inbox_note_view', {
|
||||
note_content,
|
||||
note_name,
|
||||
note_title,
|
||||
note_type,
|
||||
note_icon,
|
||||
} );
|
||||
|
||||
this.hasBeenSeen = true;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { lastRead, note } = this.props;
|
||||
|
||||
const getButtonsFromActions = () => {
|
||||
if ( ! note.actions ) {
|
||||
return [];
|
||||
}
|
||||
return note.actions.map( action => <NoteAction noteId={ note.id } action={ action } /> );
|
||||
};
|
||||
|
||||
return (
|
||||
<VisibilitySensor onChange={ this.onVisible }>
|
||||
<ActivityCard
|
||||
className={ classnames( 'woocommerce-inbox-activity-card', {
|
||||
actioned: 'unactioned' !== note.status,
|
||||
} ) }
|
||||
title={ note.title }
|
||||
date={ note.date_created }
|
||||
icon={ <Gridicon icon={ note.icon } size={ 48 } /> }
|
||||
unread={
|
||||
! lastRead ||
|
||||
! note.date_created_gmt ||
|
||||
new Date( note.date_created_gmt + 'Z' ).getTime() > lastRead
|
||||
}
|
||||
actions={ getButtonsFromActions( note ) }
|
||||
>
|
||||
<span dangerouslySetInnerHTML={ sanitizeHTML( note.content ) } />
|
||||
</ActivityCard>
|
||||
</VisibilitySensor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InboxNoteCard.propTypes = {
|
||||
note: PropTypes.shape( {
|
||||
id: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
content: PropTypes.string,
|
||||
date_created: PropTypes.string,
|
||||
date_created_gmt: PropTypes.string,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
id: PropTypes.number.isRequired,
|
||||
url: PropTypes.string,
|
||||
label: PropTypes.string.isRequired,
|
||||
primary: PropTypes.bool.isRequired,
|
||||
} )
|
||||
),
|
||||
} ),
|
||||
lastRead: PropTypes.number,
|
||||
};
|
||||
|
||||
export default InboxNoteCard;
|
|
@ -3,27 +3,20 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import Gridicon from 'gridicons';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* WooCommerce dependencies
|
||||
*/
|
||||
import { ADMIN_URL as adminUrl } from '@woocommerce/wc-admin-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ActivityCard, ActivityCardPlaceholder } from '../activity-card';
|
||||
import ActivityHeader from '../activity-header';
|
||||
import { ActivityCard, ActivityCardPlaceholder } from '../../activity-card';
|
||||
import ActivityHeader from '../../activity-header';
|
||||
import InboxNoteCard from './card';
|
||||
import { EmptyContent, Section } from '@woocommerce/components';
|
||||
import sanitizeHTML from 'lib/sanitize-html';
|
||||
import { QUERY_DEFAULTS } from 'wc-api/constants';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class InboxPanel extends Component {
|
||||
constructor( props ) {
|
||||
|
@ -38,18 +31,6 @@ class InboxPanel extends Component {
|
|||
this.props.updateCurrentUserData( userDataFields );
|
||||
}
|
||||
|
||||
handleActionClick( event, note_id, action_id ) {
|
||||
const { triggerNoteAction } = this.props;
|
||||
const href = event.target.href || '';
|
||||
|
||||
if ( href.length && ! href.startsWith( adminUrl ) ) {
|
||||
event.preventDefault();
|
||||
window.open( href, '_blank' );
|
||||
}
|
||||
|
||||
triggerNoteAction( note_id, action_id );
|
||||
}
|
||||
|
||||
renderEmptyCard() {
|
||||
return (
|
||||
<ActivityCard
|
||||
|
@ -73,42 +54,10 @@ class InboxPanel extends Component {
|
|||
return this.renderEmptyCard();
|
||||
}
|
||||
|
||||
const getButtonsFromActions = note => {
|
||||
if ( ! note.actions ) {
|
||||
return [];
|
||||
}
|
||||
return note.actions.map( action => (
|
||||
<Button
|
||||
isDefault
|
||||
isPrimary={ action.primary }
|
||||
href={ action.url || undefined }
|
||||
onClick={ e => this.handleActionClick( e, note.id, action.id ) }
|
||||
>
|
||||
{ action.label }
|
||||
</Button>
|
||||
) );
|
||||
};
|
||||
|
||||
const notesArray = Object.keys( notes ).map( key => notes[ key ] );
|
||||
|
||||
return notesArray.map( note => (
|
||||
<ActivityCard
|
||||
key={ note.id }
|
||||
className={ classnames( 'woocommerce-inbox-activity-card', {
|
||||
actioned: 'unactioned' !== note.status,
|
||||
} ) }
|
||||
title={ note.title }
|
||||
date={ note.date_created }
|
||||
icon={ <Gridicon icon={ note.icon } size={ 48 } /> }
|
||||
unread={
|
||||
! lastRead ||
|
||||
! note.date_created_gmt ||
|
||||
new Date( note.date_created_gmt + 'Z' ).getTime() > lastRead
|
||||
}
|
||||
actions={ getButtonsFromActions( note ) }
|
||||
>
|
||||
<span dangerouslySetInnerHTML={ sanitizeHTML( note.content ) } />
|
||||
</ActivityCard>
|
||||
<InboxNoteCard key={ note.id } note={ note } lastRead={ lastRead } />
|
||||
) );
|
||||
}
|
||||
|
||||
|
@ -180,11 +129,10 @@ export default compose(
|
|||
return { notes, isError, isRequesting, lastRead: userData.activity_panel_inbox_last_read };
|
||||
} ),
|
||||
withDispatch( dispatch => {
|
||||
const { updateCurrentUserData, triggerNoteAction } = dispatch( 'wc-api' );
|
||||
const { updateCurrentUserData } = dispatch( 'wc-api' );
|
||||
|
||||
return {
|
||||
updateCurrentUserData,
|
||||
triggerNoteAction,
|
||||
};
|
||||
} )
|
||||
)( InboxPanel );
|
|
@ -16775,6 +16775,14 @@
|
|||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"react-visibility-sensor": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz",
|
||||
"integrity": "sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==",
|
||||
"requires": {
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-with-direction": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.3.1.tgz",
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
"react-dates": "17.2.0",
|
||||
"react-router-dom": "5.1.2",
|
||||
"react-transition-group": "2.9.0",
|
||||
"react-visibility-sensor": "5.1.1",
|
||||
"redux": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -244,7 +244,30 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
|
|||
return false;
|
||||
}
|
||||
|
||||
foreach ( $note->get_actions( 'edit' ) as $action ) {
|
||||
// Process action removal. Actions are removed from
|
||||
// the note if they aren't part of the changeset.
|
||||
// See WC_Admin_Note::add_action().
|
||||
$changed_actions = $note->get_actions( 'edit' );
|
||||
$actions_to_keep = array();
|
||||
|
||||
foreach ( $changed_actions as $action ) {
|
||||
if ( ! empty( $action->id ) ) {
|
||||
$actions_to_keep[] = (int) $action->id;
|
||||
}
|
||||
}
|
||||
|
||||
$clear_actions_query = $wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->prefix}wc_admin_note_actions WHERE note_id = %d", $note->get_id()
|
||||
);
|
||||
|
||||
if ( $actions_to_keep ) {
|
||||
$clear_actions_query .= sprintf( ' AND action_id NOT IN (%s)', implode( ',', $actions_to_keep ) );
|
||||
}
|
||||
|
||||
$wpdb->query( $clear_actions_query );
|
||||
|
||||
// Update/insert the actions in this changeset.
|
||||
foreach ( $changed_actions as $action ) {
|
||||
$action_data = array(
|
||||
'note_id' => $note->get_id(),
|
||||
'name' => $action->name,
|
||||
|
@ -274,6 +297,9 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
|
|||
$data_format
|
||||
);
|
||||
}
|
||||
|
||||
// Update actions from DB (to grab new IDs).
|
||||
$this->read_actions( $note );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -84,6 +84,23 @@ class WC_Admin_Note extends \WC_Data {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge changes with data and clear.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function apply_changes() {
|
||||
$this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
|
||||
|
||||
// Note actions need to be replaced wholesale.
|
||||
// Merging arrays doesn't allow for deleting note actions.
|
||||
if ( isset( $this->changes['actions'] ) ) {
|
||||
$this->data['actions'] = $this->changes['actions'];
|
||||
}
|
||||
|
||||
$this->changes = array();
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Helpers
|
||||
|
|
|
@ -69,7 +69,7 @@ class WC_Admin_Notes_Facebook_Extension {
|
|||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce-admin' ), 'https://woocommerce.com/products/facebook/', WC_Admin_Note::E_WC_ADMIN_NOTE_UNACTIONED );
|
||||
$note->add_action( 'install-now', __( 'Install now', 'woocommerce-admin' ), false, WC_Admin_Note::E_WC_ADMIN_NOTE_ACTIONED, true );
|
||||
$note->add_action( 'install-now', __( 'Install now', 'woocommerce-admin' ), false, WC_Admin_Note::E_WC_ADMIN_NOTE_UNACTIONED, true );
|
||||
|
||||
// Create the note as "actioned" if the Facebook extension is already installed.
|
||||
if ( 0 === validate_plugin( 'facebook-for-woocommerce/facebook-for-woocommerce.php' ) ) {
|
||||
|
@ -97,6 +97,25 @@ class WC_Admin_Notes_Facebook_Extension {
|
|||
|
||||
$activate_request = array( 'plugins' => 'facebook-for-woocommerce' );
|
||||
$installer->activate_plugins( $activate_request );
|
||||
|
||||
$content = __( 'You\'re almost ready to start driving sales with Facebook. Complete the setup steps to control how WooCommerce integrates with your Facebook store.', 'woocommerce-admin' );
|
||||
$note->set_title( __( 'Market on Facebook — Installed', 'woocommerce-admin' ) );
|
||||
$note->set_content( $content );
|
||||
$note->set_icon( 'checkmark-circle' );
|
||||
$note->clear_actions();
|
||||
$note->add_action(
|
||||
'configure-facebook',
|
||||
__( 'Setup', 'woocommerce-admin' ),
|
||||
add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-settings',
|
||||
'tab' => 'integration',
|
||||
'section' => 'facebookcommerce',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
),
|
||||
WC_Admin_Note::E_WC_ADMIN_NOTE_UNACTIONED
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue