Create listings tab for re-engagement emails
[MAILPOET-3763]
This commit is contained in:
306
assets/js/src/newsletters/listings/re_engagement.jsx
Normal file
306
assets/js/src/newsletters/listings/re_engagement.jsx
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Toggle from 'common/form/toggle/toggle';
|
||||||
|
import Listing from 'listing/listing.jsx';
|
||||||
|
import Statistics from 'newsletters/listings/statistics.jsx';
|
||||||
|
import {
|
||||||
|
addStatsCTAAction,
|
||||||
|
checkCronStatus,
|
||||||
|
checkMailerStatus,
|
||||||
|
} from 'newsletters/listings/utils.jsx';
|
||||||
|
import NewsletterTypes from 'newsletters/types';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
|
||||||
|
const mailpoetTrackingEnabled = (!!(window.mailpoet_tracking_enabled));
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
onNoItemsFound: (group, search) => MailPoet.I18n.t(search ? 'noItemsFound' : 'emptyListing'),
|
||||||
|
onTrash: (response) => {
|
||||||
|
const count = Number(response.meta.count);
|
||||||
|
let message = null;
|
||||||
|
|
||||||
|
if (count === 1) {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||||
|
).replace('%$1d', count.toLocaleString());
|
||||||
|
}
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
},
|
||||||
|
onDelete: (response) => {
|
||||||
|
const count = Number(response.meta.count);
|
||||||
|
let message = null;
|
||||||
|
|
||||||
|
if (count === 1) {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||||
|
).replace('%$1d', count.toLocaleString());
|
||||||
|
}
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
},
|
||||||
|
onRestore: (response) => {
|
||||||
|
const count = Number(response.meta.count);
|
||||||
|
let message = null;
|
||||||
|
|
||||||
|
if (count === 1) {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('oneNewsletterRestored')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = (
|
||||||
|
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||||
|
).replace('%$1d', count.toLocaleString());
|
||||||
|
}
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'subject',
|
||||||
|
label: MailPoet.I18n.t('subject'),
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'statistics',
|
||||||
|
label: MailPoet.I18n.t('statistics'),
|
||||||
|
display: mailpoetTrackingEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: MailPoet.I18n.t('status'),
|
||||||
|
width: 145,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updated_at',
|
||||||
|
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const bulkActions = [
|
||||||
|
{
|
||||||
|
name: 'trash',
|
||||||
|
label: MailPoet.I18n.t('moveToTrash'),
|
||||||
|
onSuccess: messages.onTrash,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let newsletterActions = [
|
||||||
|
{
|
||||||
|
name: 'view',
|
||||||
|
link: function link(newsletter) {
|
||||||
|
return (
|
||||||
|
<a href={newsletter.preview_url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{MailPoet.I18n.t('preview')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'duplicate',
|
||||||
|
className: 'mailpoet-hide-on-mobile',
|
||||||
|
label: MailPoet.I18n.t('duplicate'),
|
||||||
|
onClick: (newsletter, refresh) => MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'newsletters',
|
||||||
|
action: 'duplicate',
|
||||||
|
data: {
|
||||||
|
id: newsletter.id,
|
||||||
|
},
|
||||||
|
}).done((response) => {
|
||||||
|
MailPoet.Notice.success((MailPoet.I18n.t('newsletterDuplicated')).replace('%$1s', response.data.subject));
|
||||||
|
refresh();
|
||||||
|
}).fail((response) => {
|
||||||
|
if (response.errors.length > 0) {
|
||||||
|
MailPoet.Notice.error(
|
||||||
|
response.errors.map((error) => error.message),
|
||||||
|
{ scroll: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
className: 'mailpoet-hide-on-mobile',
|
||||||
|
link: function link(newsletter) {
|
||||||
|
return (
|
||||||
|
<a href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}>
|
||||||
|
{MailPoet.I18n.t('edit')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'trash',
|
||||||
|
className: 'mailpoet-hide-on-mobile',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
newsletterActions = addStatsCTAAction(newsletterActions);
|
||||||
|
|
||||||
|
class NewsletterListReEngagement extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
newslettersCount: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus = (checked, e) => {
|
||||||
|
// make the event persist so that we can still override the selected value
|
||||||
|
// in the ajax callback
|
||||||
|
e.persist();
|
||||||
|
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'newsletters',
|
||||||
|
action: 'setStatus',
|
||||||
|
data: {
|
||||||
|
id: Number(e.target.getAttribute('data-id')),
|
||||||
|
status: checked ? 'active' : 'draft',
|
||||||
|
},
|
||||||
|
}).done((response) => {
|
||||||
|
if (response.data.status === 'active') {
|
||||||
|
MailPoet.Notice.success(MailPoet.I18n.t('reEngagementEmailActivated'));
|
||||||
|
}
|
||||||
|
// force refresh of listing so that groups are updated
|
||||||
|
this.forceUpdate();
|
||||||
|
}).fail((response) => {
|
||||||
|
MailPoet.Notice.showApiErrorNotice(response);
|
||||||
|
|
||||||
|
// reset value to previous newsletter's status
|
||||||
|
e.target.checked = !checked;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderStatus = (newsletter) => {
|
||||||
|
const totalSentMessage = MailPoet.I18n.t('sentToXSubscribers')
|
||||||
|
.replace('%$1d', newsletter.total_sent.toLocaleString());
|
||||||
|
const totalScheduledMessage = MailPoet.I18n.t('scheduledToXSubscribers')
|
||||||
|
.replace('%$1d', newsletter.total_scheduled.toLocaleString());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
className="mailpoet-listing-status-toggle"
|
||||||
|
onCheck={this.updateStatus}
|
||||||
|
data-id={newsletter.id}
|
||||||
|
dimension="small"
|
||||||
|
defaultChecked={newsletter.status === 'active'}
|
||||||
|
/>
|
||||||
|
<p className="mailpoet-listing-stats-description">
|
||||||
|
<Link
|
||||||
|
to={`/sending-status/${newsletter.id}`}
|
||||||
|
data-automation-id={`sending_status_${newsletter.id}`}
|
||||||
|
>
|
||||||
|
{ totalSentMessage }
|
||||||
|
</Link>
|
||||||
|
{' '}
|
||||||
|
<br />
|
||||||
|
{ totalScheduledMessage }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderItem = (newsletter, actions) => {
|
||||||
|
const rowClasses = classNames(
|
||||||
|
'manage-column',
|
||||||
|
'column-primary',
|
||||||
|
'has-row-actions'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<td className={rowClasses}>
|
||||||
|
<a
|
||||||
|
className="mailpoet-listing-title"
|
||||||
|
href={`?page=mailpoet-newsletter-editor&id=${newsletter.id}`}
|
||||||
|
>
|
||||||
|
{ newsletter.subject }
|
||||||
|
</a>
|
||||||
|
{ actions }
|
||||||
|
</td>
|
||||||
|
{ (mailpoetTrackingEnabled === true) ? (
|
||||||
|
<td className="column mailpoet-listing-stats-column" data-colname={MailPoet.I18n.t('statistics')}>
|
||||||
|
<Statistics
|
||||||
|
newsletter={newsletter}
|
||||||
|
isSent={newsletter.total_sent > 0 && !!newsletter.statistics}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
) : null }
|
||||||
|
<td className="column" data-colname={MailPoet.I18n.t('status')}>
|
||||||
|
{ this.renderStatus(newsletter) }
|
||||||
|
</td>
|
||||||
|
<td className="column-date mailpoet-hide-on-mobile" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
|
||||||
|
{ MailPoet.Date.short(newsletter.updated_at) }
|
||||||
|
<br />
|
||||||
|
{ MailPoet.Date.time(newsletter.updated_at) }
|
||||||
|
</td>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isItemInactive = (newsletter) => newsletter.status === 'draft';
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.state.newslettersCount === 0 && (
|
||||||
|
<NewsletterTypes
|
||||||
|
filter={(type) => type.slug === 're_engagement'}
|
||||||
|
hideScreenOptions={false}
|
||||||
|
hideClosingButton
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.newslettersCount !== 0 && (
|
||||||
|
<Listing
|
||||||
|
limit={window.mailpoet_listing_per_page}
|
||||||
|
location={this.props.location}
|
||||||
|
params={this.props.match.params}
|
||||||
|
endpoint="newsletters"
|
||||||
|
type="re_engagement"
|
||||||
|
base_url="re_engagement"
|
||||||
|
onRenderItem={this.renderItem}
|
||||||
|
isItemInactive={this.isItemInactive}
|
||||||
|
columns={columns}
|
||||||
|
bulk_actions={bulkActions}
|
||||||
|
item_actions={newsletterActions}
|
||||||
|
messages={messages}
|
||||||
|
auto_refresh
|
||||||
|
sort_by="updated_at"
|
||||||
|
sort_order="desc"
|
||||||
|
afterGetItems={(state) => {
|
||||||
|
if (!state.loading) {
|
||||||
|
const total = state.groups.reduce((count, group) => (count + group.count), 0);
|
||||||
|
this.setState({ newslettersCount: total });
|
||||||
|
}
|
||||||
|
checkMailerStatus(state);
|
||||||
|
checkCronStatus(state);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NewsletterListReEngagement.propTypes = {
|
||||||
|
location: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
match: PropTypes.shape({
|
||||||
|
params: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(NewsletterListReEngagement);
|
@ -19,6 +19,7 @@ import EventsConditions from 'newsletters/automatic_emails/events_conditions.jsx
|
|||||||
import NewsletterListStandard from 'newsletters/listings/standard.jsx';
|
import NewsletterListStandard from 'newsletters/listings/standard.jsx';
|
||||||
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx';
|
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx';
|
||||||
import NewsletterListNotification from 'newsletters/listings/notification.jsx';
|
import NewsletterListNotification from 'newsletters/listings/notification.jsx';
|
||||||
|
import NewsletterListReEngagement from 'newsletters/listings/re_engagement.jsx';
|
||||||
import NewsletterListNotificationHistory from 'newsletters/listings/notification_history.jsx';
|
import NewsletterListNotificationHistory from 'newsletters/listings/notification_history.jsx';
|
||||||
import NewsletterSendingStatus from 'newsletters/sending_status.jsx';
|
import NewsletterSendingStatus from 'newsletters/sending_status.jsx';
|
||||||
import Listings from 'newsletters/automatic_emails/listings.jsx';
|
import Listings from 'newsletters/automatic_emails/listings.jsx';
|
||||||
@ -84,6 +85,14 @@ const Tabs = withNpsPoll(() => {
|
|||||||
: <NewsletterListNotification />
|
: <NewsletterListNotification />
|
||||||
}
|
}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
key="re_engagement"
|
||||||
|
route="re_engagement/(.*)?"
|
||||||
|
title={MailPoet.I18n.t('tabReEngagementTitle')}
|
||||||
|
automationId={`tab-${MailPoet.I18n.t('tabReEngagementTitle')}`}
|
||||||
|
>
|
||||||
|
<NewsletterListReEngagement />
|
||||||
|
</Tab>
|
||||||
{window.mailpoet_woocommerce_active && _.map(automaticEmails, (email) => (
|
{window.mailpoet_woocommerce_active && _.map(automaticEmails, (email) => (
|
||||||
<Tab
|
<Tab
|
||||||
key={email.slug}
|
key={email.slug}
|
||||||
@ -162,7 +171,7 @@ const routes = [
|
|||||||
component: Tabs,
|
component: Tabs,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/(standard|welcome|notification)/(.*)?',
|
path: '/(standard|welcome|notification|re_engagement)/(.*)?',
|
||||||
component: Tabs,
|
component: Tabs,
|
||||||
},
|
},
|
||||||
/* New newsletter: types */
|
/* New newsletter: types */
|
||||||
|
@ -146,7 +146,7 @@ class NewslettersResponseBuilder {
|
|||||||
if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
|
if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
|
||||||
$data['segments'] = $this->buildSegments($newsletter);
|
$data['segments'] = $this->buildSegments($newsletter);
|
||||||
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
|
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
|
||||||
} elseif (in_array($newsletter->getType(), [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC], true)) {
|
} elseif (in_array($newsletter->getType(), [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC, NewsletterEntity::TYPE_RE_ENGAGEMENT], true)) {
|
||||||
$data['segments'] = [];
|
$data['segments'] = [];
|
||||||
$data['options'] = $this->buildOptions($newsletter);
|
$data['options'] = $this->buildOptions($newsletter);
|
||||||
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
|
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
|
||||||
|
@ -20,6 +20,7 @@ class NewsletterListingRepository extends ListingRepository {
|
|||||||
|
|
||||||
private static $supportedTypes = [
|
private static $supportedTypes = [
|
||||||
NewsletterEntity::TYPE_STANDARD,
|
NewsletterEntity::TYPE_STANDARD,
|
||||||
|
NewsletterEntity::TYPE_RE_ENGAGEMENT,
|
||||||
NewsletterEntity::TYPE_WELCOME,
|
NewsletterEntity::TYPE_WELCOME,
|
||||||
NewsletterEntity::TYPE_AUTOMATIC,
|
NewsletterEntity::TYPE_AUTOMATIC,
|
||||||
NewsletterEntity::TYPE_NOTIFICATION,
|
NewsletterEntity::TYPE_NOTIFICATION,
|
||||||
@ -151,6 +152,7 @@ class NewsletterListingRepository extends ListingRepository {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NewsletterEntity::TYPE_WELCOME:
|
case NewsletterEntity::TYPE_WELCOME:
|
||||||
|
case NewsletterEntity::TYPE_RE_ENGAGEMENT:
|
||||||
case NewsletterEntity::TYPE_NOTIFICATION:
|
case NewsletterEntity::TYPE_NOTIFICATION:
|
||||||
case NewsletterEntity::TYPE_AUTOMATIC:
|
case NewsletterEntity::TYPE_AUTOMATIC:
|
||||||
$groups = array_merge($groups, [
|
$groups = array_merge($groups, [
|
||||||
|
@ -101,6 +101,7 @@
|
|||||||
'tabWelcomeTitle': __('Welcome Emails'),
|
'tabWelcomeTitle': __('Welcome Emails'),
|
||||||
'tabNotificationTitle': __('Post Notifications'),
|
'tabNotificationTitle': __('Post Notifications'),
|
||||||
'tabWoocommerceTitle': __('WooCommerce Emails'),
|
'tabWoocommerceTitle': __('WooCommerce Emails'),
|
||||||
|
'tabReEngagementTitle': __('Re-engagement Email'),
|
||||||
'tabBlankTitle': __('Simple text'),
|
'tabBlankTitle': __('Simple text'),
|
||||||
|
|
||||||
'reEngagementTextPre': __('After no activity for'),
|
'reEngagementTextPre': __('After no activity for'),
|
||||||
@ -308,6 +309,8 @@
|
|||||||
'newsletterInvalidFromAddress': _x('You need to authorize the email address <i>%$1s</i> to be able to send with it. [link]Authorize my email address[/link]', 'Users need to confirm that they own the email address they want to use to send their newsletter'),
|
'newsletterInvalidFromAddress': _x('You need to authorize the email address <i>%$1s</i> to be able to send with it. [link]Authorize my email address[/link]', 'Users need to confirm that they own the email address they want to use to send their newsletter'),
|
||||||
'welcomeEmailActivated': __('Your Welcome Email is now activated!'),
|
'welcomeEmailActivated': __('Your Welcome Email is now activated!'),
|
||||||
'welcomeEmailActivationFailed': __('Your Welcome Email could not be activated, please check the settings.'),
|
'welcomeEmailActivationFailed': __('Your Welcome Email could not be activated, please check the settings.'),
|
||||||
|
'reEngagementEmailActivated': __('Your ReEngagement Email is now activated!'),
|
||||||
|
'reEngagementEmailActivationFailed': __('Your ReEngagement Email could not be activated, please check the settings.'),
|
||||||
'postNotificationActivated': __('Your post notification is now active!'),
|
'postNotificationActivated': __('Your post notification is now active!'),
|
||||||
'postNotificationActivationFailed': __('Your Post Notification could not be activated, check the settings.'),
|
'postNotificationActivationFailed': __('Your Post Notification could not be activated, check the settings.'),
|
||||||
'welcomeEventSegment': __('Sent when someone subscribes to the list: "%$1s".'),
|
'welcomeEventSegment': __('Sent when someone subscribes to the list: "%$1s".'),
|
||||||
|
Reference in New Issue
Block a user