Create listings tab for re-engagement emails

[MAILPOET-3763]
This commit is contained in:
Pavel Dohnal
2021-09-13 15:06:37 +02:00
committed by Veljko V
parent 7749d17fb1
commit ac3625e434
5 changed files with 322 additions and 2 deletions

View 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);

View File

@ -19,6 +19,7 @@ import EventsConditions from 'newsletters/automatic_emails/events_conditions.jsx
import NewsletterListStandard from 'newsletters/listings/standard.jsx';
import NewsletterListWelcome from 'newsletters/listings/welcome.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 NewsletterSendingStatus from 'newsletters/sending_status.jsx';
import Listings from 'newsletters/automatic_emails/listings.jsx';
@ -84,6 +85,14 @@ const Tabs = withNpsPoll(() => {
: <NewsletterListNotification />
}
</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) => (
<Tab
key={email.slug}
@ -162,7 +171,7 @@ const routes = [
component: Tabs,
},
{
path: '/(standard|welcome|notification)/(.*)?',
path: '/(standard|welcome|notification|re_engagement)/(.*)?',
component: Tabs,
},
/* New newsletter: types */

View File

@ -146,7 +146,7 @@ class NewslettersResponseBuilder {
if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
$data['segments'] = $this->buildSegments($newsletter);
$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['options'] = $this->buildOptions($newsletter);
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;

View File

@ -20,6 +20,7 @@ class NewsletterListingRepository extends ListingRepository {
private static $supportedTypes = [
NewsletterEntity::TYPE_STANDARD,
NewsletterEntity::TYPE_RE_ENGAGEMENT,
NewsletterEntity::TYPE_WELCOME,
NewsletterEntity::TYPE_AUTOMATIC,
NewsletterEntity::TYPE_NOTIFICATION,
@ -151,6 +152,7 @@ class NewsletterListingRepository extends ListingRepository {
break;
case NewsletterEntity::TYPE_WELCOME:
case NewsletterEntity::TYPE_RE_ENGAGEMENT:
case NewsletterEntity::TYPE_NOTIFICATION:
case NewsletterEntity::TYPE_AUTOMATIC:
$groups = array_merge($groups, [

View File

@ -101,6 +101,7 @@
'tabWelcomeTitle': __('Welcome Emails'),
'tabNotificationTitle': __('Post Notifications'),
'tabWoocommerceTitle': __('WooCommerce Emails'),
'tabReEngagementTitle': __('Re-engagement Email'),
'tabBlankTitle': __('Simple text'),
'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'),
'welcomeEmailActivated': __('Your Welcome Email is now activated!'),
'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!'),
'postNotificationActivationFailed': __('Your Post Notification could not be activated, check the settings.'),
'welcomeEventSegment': __('Sent when someone subscribes to the list: "%$1s".'),