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

feat: segmentation criteria for subscriptions and memberships #2696

Merged
merged 8 commits into from
Oct 27, 2023
Merged
36 changes: 24 additions & 12 deletions assets/components/src/autocomplete-tokenfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ class AutocompleteTokenField extends Component {
return labels.reduce( ( acc, label ) => {
Object.keys( validValues ).forEach( key => {
if ( validValues[ key ] === label ) {
acc.push( { value: key, label } );
// Preserve numeric or string type of values. Object.keys will convert numbers to strings.
const value = isNaN( parseInt( key ) ) ? key.toString() : parseInt( key );
acc.push( { value, label } );
}
} );

Expand All @@ -128,7 +130,9 @@ class AutocompleteTokenField extends Component {
}

return labels.map( label =>
Object.keys( validValues ).find( key => validValues[ key ] === label )
Object.keys( validValues )
.map( key => ( isNaN( parseInt( key ) ) ? key.toString() : parseInt( key ) ) )
.find( key => validValues[ key ] === label )
);
}

Expand Down Expand Up @@ -206,18 +210,26 @@ class AutocompleteTokenField extends Component {
const { help, label = '', placeholder = '', maxLength } = this.props;
const { suggestions, loading } = this.state;

const classNames = [ 'newspack-autocomplete-tokenfield__input-container' ];

if ( label ) {
classNames.push( 'has-label' );
}

return (
<div className="newspack-autocomplete-tokenfield">
<FormTokenField
value={ this.getTokens() }
suggestions={ suggestions.map( suggestion => suggestion.label ) }
onChange={ tokens => this.handleOnChange( tokens ) }
onInputChange={ input => this.debouncedUpdateSuggestions( input ) }
label={ label }
maxLength={ maxLength }
placeholder={ placeholder }
/>
{ loading && <Spinner /> }
<div className={ classNames.join( ' ' ) }>
<FormTokenField
value={ this.getTokens() }
suggestions={ suggestions.map( suggestion => suggestion.label ) }
onChange={ tokens => this.handleOnChange( tokens ) }
onInputChange={ input => this.debouncedUpdateSuggestions( input ) }
label={ label }
maxLength={ maxLength }
placeholder={ placeholder }
/>
{ loading && <Spinner /> }
</div>
{ help && <p className="newspack-autocomplete-tokenfield__help">{ help }</p> }
</div>
);
Expand Down
16 changes: 11 additions & 5 deletions assets/components/src/autocomplete-tokenfield/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
@use '~@wordpress/base-styles/colors' as wp-colors;

.newspack-autocomplete-tokenfield {
position: relative;

.components-spinner {
margin: 8px;
margin: 0;
position: absolute;
right: 0;
top: calc( 1.4em + 8px );
right: 8px;
top: 8px;
}

&__input-container {
position: relative;

&.has-label .components-spinner {
top: 32px;
}
}

/* Workaround for hard-coded help text in FormTokenField. */
Expand Down
11 changes: 2 additions & 9 deletions assets/components/src/autocomplete-with-suggestions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { Button, CheckboxControl, SelectControl, Spinner } from '@wordpress/components';
import { Button, CheckboxControl, SelectControl } from '@wordpress/components';
import { decodeEntities } from '@wordpress/html-entities';
import { useEffect, useState } from '@wordpress/element';
import { __, _x, sprintf } from '@wordpress/i18n';
Expand Down Expand Up @@ -265,14 +265,6 @@ const AutocompleteWithSuggestions = ( {
* Render a list of suggestions that can be clicked to select instead of searching by title.
*/
const renderSuggestions = () => {
if ( isLoading ) {
return (
<div className="newspack-autocomplete-with-suggestions__suggestions-spinner">
<Spinner />
</div>
);
}

if ( 0 === suggestions.length ) {
return null;
}
Expand Down Expand Up @@ -319,6 +311,7 @@ const AutocompleteWithSuggestions = ( {
fetchSuggestions={ async search => handleFetchSuggestions( search, 0, postTypeToSearch ) }
fetchSavedInfo={ postIds => handleFetchSaved( postIds ) }
label={ label }
loading={ isLoading }
help={ ! hideHelp && help }
returnFullObjects
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
margin-top: 16px;
}
}

.components-spinner {
margin: 8px;
right: 0;
top: calc( 1.4em + 8px );
}

.components-select-control__input {
max-width: none;
}
Expand Down
35 changes: 0 additions & 35 deletions assets/components/src/subscription-lists-control/index.js

This file was deleted.

42 changes: 42 additions & 0 deletions assets/wizards/popups/components/lists-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

/**
* Internal dependencies
*/
import AutocompleteTokenField from '../../../../components/src/autocomplete-tokenfield';

export default function ListsControl( { label, help, placeholder, value, onChange, path } ) {
const getSuggestions = item => ( {
value: isNaN( parseInt( item.id ) ) ? item.id.toString() : parseInt( item.id ),
label: item.title || item.name,
} );

return (
<AutocompleteTokenField
label={ label }
help={ help }
placeholder={ placeholder }
tokens={ value || [] }
fetchSuggestions={ async () => {
const lists = await apiFetch( {
path,
} );
const values = Array.isArray( lists ) ? lists : Object.values( lists );
const suggestions = values.map( getSuggestions );

return suggestions;
} }
fetchSavedInfo={ async ids => {
const lists = await apiFetch( {
path,
} );
const values = Array.isArray( lists ) ? lists : Object.values( lists );
return values.filter( item => ids.includes( item.id ) ).map( getSuggestions );
} }
onChange={ onChange }
/>
);
}
79 changes: 66 additions & 13 deletions assets/wizards/popups/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,33 +257,37 @@ addFilter(
}
);

const getNewsletterSubscriptionLists = memoize( async () => {
const getItems = memoize( async path => {
try {
const lists = await apiFetch( {
path: '/newspack-newsletters/v1/lists_config',
const items = await apiFetch( {
path,
} );
return Object.values( lists ).map( item => ( { id: item.id, label: item.title } ) );
const values = Array.isArray( items ) ? items : Object.values( items );
return values.map( item => ( {
id: isNaN( parseInt( item.id ) ) ? item.id.toString() : parseInt( item.id ),
label: item.title || item.name,
} ) );
} catch ( e ) {
console.warn( e );
return [];
}
} );

const NewsletterSubscriptionListsNames = ( { label, ids } ) => {
const [ lists, setLists ] = useState( [] );
const ItemNames = ( { label, ids, path } ) => {
const [ items, setItems ] = useState( [] );
useEffect( () => {
getNewsletterSubscriptionLists().then( setLists );
getItems( path ).then( setItems );
}, [ ids ] );
if ( ! lists.length ) {
if ( ! items.length ) {
return null;
}
return (
<span>
{ label }{ ' ' }
{ lists.length
? lists
.filter( list => ids.includes( list.id ) )
.map( list => list.label )
{ items.length
? items
.filter( item => ids.includes( item.id ) )
.map( item => item.label )
.join( ', ' )
: '' }
</span>
Expand All @@ -299,11 +303,60 @@ addFilter(
return null;
}
return (
<NewsletterSubscriptionListsNames
<ItemNames
label={
config.id === 'subscribed_lists' ? __( 'Subscribed to:' ) : __( 'Not subscribed to:' )
}
ids={ item.value }
path="/newspack-newsletters/v1/lists_config"
/>
);
}
return message;
}
);

addFilter(
'newspack.wizards.campaigns.segmentDescription.criteriaMessage',
'newspack.activeSubscriptions',
( message, value, config, item ) => {
if ( [ 'active_subscriptions', 'not_active_subscriptions' ].includes( config.id ) ) {
if ( ! item.value?.length ) {
return null;
}
return (
<ItemNames
label={
config.id === 'active_subscriptions'
? __( 'Has active subscription(s):' )
: __( 'Does not have active subscription(s):' )
}
ids={ item.value }
path="/newspack/v1/wizard/newspack-popups-wizard/subscription-products"
/>
);
}
return message;
}
);

addFilter(
'newspack.wizards.campaigns.segmentDescription.criteriaMessage',
'newspack.activeMemberships',
( message, value, config, item ) => {
if ( [ 'active_memberships', 'not_active_memberships' ].includes( config.id ) ) {
if ( ! item.value?.length ) {
return null;
}
return (
<ItemNames
label={
config.id === 'active_memberships'
? __( 'Has active membership(s):' )
: __( 'Does not have active membership(s):' )
}
ids={ item.value }
path="/wc/v3/memberships/plans?per_page=100"
/>
);
}
Expand Down
4 changes: 2 additions & 2 deletions assets/wizards/popups/views/segments/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* Internal dependencies.
*/
import { withWizardScreen } from '../../../../components/src';
import SegmentsList from './SegmentsList';
import SingleSegment from './SingleSegment';
import SegmentsList from './segments-list';
import SingleSegment from './single-segment';
import './style.scss';

/**
Expand Down
Loading