Compare commits
184 Commits
Author | SHA1 | Date | |
---|---|---|---|
1fa19974cf | |||
0e2e2d50de | |||
4f16f246b8 | |||
9ca1cabb67 | |||
a990250072 | |||
5fbe658920 | |||
2151118183 | |||
4c65374a0c | |||
32cf2084b8 | |||
9dc834e14a | |||
3db11c443d | |||
421549d6ee | |||
a7277f3437 | |||
4d56911230 | |||
0f23dea7fc | |||
742e3e85e9 | |||
6eeb5bb1bf | |||
01eb59a295 | |||
8ddd42ea4c | |||
eb80adc2ed | |||
3103ef11d5 | |||
a1ead743fa | |||
c8d0291e41 | |||
bd44d77973 | |||
f872ab1819 | |||
55371ce7ee | |||
ba45d23307 | |||
f660bd9bd7 | |||
1332fb89e4 | |||
dc5254721e | |||
918a4d7c74 | |||
ec29c8fb49 | |||
43922a7c27 | |||
76b1166269 | |||
12c0605f92 | |||
0bbf1b96d5 | |||
b716b427a7 | |||
c1ac9f7922 | |||
3dd872e211 | |||
a09a9cdcbf | |||
2d835cdec1 | |||
28b91ed994 | |||
1e147c9dd5 | |||
6f89b47b30 | |||
8d5c07a317 | |||
9ff72b4d4c | |||
4fe5219b89 | |||
2dab7fdb0c | |||
5075982cd3 | |||
1a58166a0b | |||
ca0681b1a7 | |||
540b34c63f | |||
c8565b1197 | |||
75906820e5 | |||
2cacb5809d | |||
900faa2a71 | |||
c140eea734 | |||
5201c359d3 | |||
4e1b1b3c27 | |||
8af01c3bcb | |||
d94dabb8d6 | |||
63b1f1670b | |||
2d050b0ea5 | |||
686f702ec9 | |||
5754302154 | |||
e2ede3e568 | |||
f95824dea0 | |||
650a8704cd | |||
cd37f92592 | |||
6bf201b45e | |||
e178d6519c | |||
97d4a8e4f8 | |||
e5ce252587 | |||
b7cae6dac4 | |||
07d3b753b8 | |||
72ef0909f5 | |||
a5473bf882 | |||
c4e7ea4c8e | |||
ce462f1643 | |||
91936aff92 | |||
57858bbe9a | |||
42d9b1241e | |||
fc4a5315df | |||
e771bd53da | |||
291f46d732 | |||
e236d3312c | |||
794090291a | |||
380eec1ed6 | |||
7c283b7fda | |||
916080fa5a | |||
c10f80457d | |||
9773408c8b | |||
5ba5426281 | |||
098aebfaf9 | |||
75d79f19cc | |||
1b70e6d494 | |||
2c57251bae | |||
9bdb32c073 | |||
b74890137a | |||
57548b579f | |||
7aa1a5f4ba | |||
d60e399a3b | |||
da34614b5c | |||
a0d330fded | |||
f766661f8c | |||
82ddce42df | |||
bfe95676c3 | |||
789bb0b396 | |||
2620ef0b57 | |||
627962570e | |||
bbdbf6a52d | |||
56ed9f4ece | |||
e6188f5cef | |||
a37e5bbe74 | |||
9df99b1a00 | |||
5ff543e62d | |||
8f14c5ac53 | |||
4e0aa3c56d | |||
545c6bfa5e | |||
cd3652eaa6 | |||
655641737b | |||
152794720a | |||
cb294fb303 | |||
98744a53c1 | |||
430fcc20a8 | |||
1df78f22d3 | |||
2f823f5606 | |||
086d6dce7e | |||
879cca9fb3 | |||
3434fbe3b5 | |||
d4f9ccca5e | |||
4a74c3e6fd | |||
60d9a3f1ac | |||
b64fbbdb7f | |||
12e8d44a43 | |||
667658ae2f | |||
9d2624163c | |||
8f6688eba7 | |||
b360d9a2cf | |||
11384bbf6a | |||
6235944442 | |||
83573b0d43 | |||
bd87c09e1a | |||
1010b64c05 | |||
dd1fcd5100 | |||
75706c9e8b | |||
61e1dd6a83 | |||
8d5af952f6 | |||
2751a4bf3a | |||
ed297dd68d | |||
17c7d42ede | |||
1741b39375 | |||
800432ab54 | |||
f169c8f8ac | |||
f92a12db30 | |||
3ea730a0b6 | |||
e3c19fa306 | |||
f2c4890def | |||
5bc2f62d98 | |||
c954603bfc | |||
bf552801ec | |||
6bb1af0a18 | |||
48dca5e298 | |||
c08813be1d | |||
1cc6d93717 | |||
18a071fd7b | |||
32d310d999 | |||
c76d18e647 | |||
865706c112 | |||
3238049828 | |||
a41de82030 | |||
ccec1faeb1 | |||
b19887add0 | |||
ba2cb75877 | |||
b89905aa80 | |||
a25e879cac | |||
0106c5123d | |||
2cb20a8f63 | |||
fe1a994442 | |||
70889ab06d | |||
aab6865f50 | |||
efd32043e3 | |||
3eaa13a421 | |||
80d2ab44a3 |
@ -410,7 +410,9 @@ jobs:
|
|||||||
name: Group acceptance tests
|
name: Group acceptance tests
|
||||||
command: |
|
command: |
|
||||||
# Convert test result filename values to be relative paths because the circleci CLI's split command requires exact matches
|
# Convert test result filename values to be relative paths because the circleci CLI's split command requires exact matches
|
||||||
sed -i.bak 's#/wp-core/wp-content/plugins/mailpoet/##g' $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json
|
if [ -e $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json ]; then
|
||||||
|
sed -i.bak 's#/wp-core/wp-content/plugins/mailpoet/##g' $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json
|
||||||
|
fi
|
||||||
# `circleci tests split` returns different values based on the container it's run on
|
# `circleci tests split` returns different values based on the container it's run on
|
||||||
# in case group is defined find only tests containing the group
|
# in case group is defined find only tests containing the group
|
||||||
if [[ -n '<< parameters.group >>' ]]; then
|
if [[ -n '<< parameters.group >>' ]]; then
|
||||||
|
@ -18,8 +18,8 @@ This method returns a list of subscribers. To see the subscriber data structure,
|
|||||||
|
|
||||||
Filter argument supports following array keys.
|
Filter argument supports following array keys.
|
||||||
|
|
||||||
| Key | Type | Description |
|
| Key | Type | Description |
|
||||||
| -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
|
| ------------ | ------------ | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
| status | string | Specific status of subscribers. One of values: `unconfirmed`, `subscribed`, `unsubscribed`, `bounced`, `inactive` |
|
| status | string | Specific status of subscribers. One of values: `unconfirmed`, `subscribed`, `unsubscribed`, `bounced`, `inactive` |
|
||||||
| list_id | int | List id or dynamic segment id |
|
| listId | int | List id or dynamic segment id |
|
||||||
| min_updated_at | DateTime\int | DateTime object or timestamp of the minimal last update of subscribers |
|
| minUpdatedAt | DateTime\int | DateTime object or timestamp of the minimal last update of subscribers |
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
"@babel/preset-env"
|
"@babel/preset-env"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"babel-plugin-typescript-to-proptypes",
|
|
||||||
[
|
[
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
{
|
{
|
||||||
|
@ -117,11 +117,45 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function translationsBuild() {
|
public function translationsBuild() {
|
||||||
|
$exclude = implode(',', [
|
||||||
|
'.mp_svn',
|
||||||
|
'assets/css',
|
||||||
|
'assets/img',
|
||||||
|
'assets/js',
|
||||||
|
'generated',
|
||||||
|
'lang',
|
||||||
|
'lib-3rd-party',
|
||||||
|
'mailpoet-premium',
|
||||||
|
'node_modules',
|
||||||
|
'plugin_repository',
|
||||||
|
'prefixer',
|
||||||
|
'tasks',
|
||||||
|
'temp',
|
||||||
|
'tests',
|
||||||
|
'tools',
|
||||||
|
'vendor',
|
||||||
|
'vendor-prefixed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$headers = escapeshellarg(
|
||||||
|
json_encode([
|
||||||
|
'Report-Msgid-Bugs-To' => 'http://support.mailpoet.com/',
|
||||||
|
'Last-Translator' => 'MailPoet i18n (https://www.transifex.com/organization/wysija)',
|
||||||
|
'Language-Team' => 'MailPoet i18n <https://www.transifex.com/organization/wysija>',
|
||||||
|
'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
$this->collectionBuilder()
|
$this->collectionBuilder()
|
||||||
->taskExec('mkdir -p ' . __DIR__ . '/lang')
|
->taskExec('mkdir -p ' . __DIR__ . '/lang')
|
||||||
->taskExec(
|
|
||||||
'php -d memory_limit=-1 tasks/makepot/grunt-makepot.php wp-plugin . lang/mailpoet.pot mailpoet .mp_svn,assets,lang,node_modules,plugin_repository,tasks,tests,vendor'
|
// HTML, HBS
|
||||||
)->run();
|
->taskExec("php -d memory_limit=-1 tasks/makepot/makepot-views.php . > lang/mailpoet.pot")
|
||||||
|
|
||||||
|
// PHP, JS/TS
|
||||||
|
->taskExec("vendor/bin/wp i18n make-pot --merge --slug=mailpoet --domain=mailpoet --exclude=$exclude --headers=$headers . lang/mailpoet.pot")
|
||||||
|
|
||||||
|
->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function translationsGetPotFileFromBuild() {
|
public function translationsGetPotFileFromBuild() {
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
.mailpoet-automatoin-deactivate-modal {
|
||||||
|
color: #1d2327;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 21px;
|
||||||
|
max-width: 480px;
|
||||||
|
|
||||||
|
.mailpoet-automation-options {
|
||||||
|
li {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-option {
|
||||||
|
border: 2px solid #dcdcde;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #646970;
|
||||||
|
display: grid;
|
||||||
|
font-size: 12px;
|
||||||
|
grid-gap: 8px;
|
||||||
|
grid-template-columns: 20px auto;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #2271b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #1d2327;
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-button {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
&.is-tertiary {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,3 +30,31 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-field__error {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
background: right top/26px no-repeat url('../../img/icons/alert.svg');
|
||||||
|
padding-right: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select,
|
||||||
|
input[type=number] {
|
||||||
|
background-position-x: calc(100% - 26px);
|
||||||
|
padding-right: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-base-control__help,
|
||||||
|
.mailpoet-automation-field-message {
|
||||||
|
color: #d63638;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-button.mailpoet-automation-button-sidebar-primary,
|
||||||
|
.components-button.mailpoet-automation-button-sidebar-primary.has-text,
|
||||||
|
.components-button.mailpoet-automation-button-sidebar-primary.has-icon {
|
||||||
|
background: #d63638;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
padding: 16px 48px 16px 16px;
|
padding: 16px 48px 16px 16px;
|
||||||
|
|
||||||
|
label & {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mailpoet-automation-panel-plain-body-title-action {
|
.mailpoet-automation-panel-plain-body-title-action {
|
||||||
@ -57,3 +61,78 @@
|
|||||||
color: #757575;
|
color: #757575;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel {
|
||||||
|
animation: mailpoet-automation-activate-panel-animation .1s forwards;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: auto;
|
||||||
|
overflow: auto;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
width: 281px;
|
||||||
|
z-index: 999999;
|
||||||
|
|
||||||
|
button {
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header {
|
||||||
|
align-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 61px;
|
||||||
|
|
||||||
|
.has-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header,
|
||||||
|
.mailpoet-automation-activate-panel__section {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header,
|
||||||
|
.mailpoet-automation-activate-panel__body {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
|
||||||
|
.components-spinner {
|
||||||
|
display: block;
|
||||||
|
margin: 100px auto 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__section {
|
||||||
|
margin-left: -16px;
|
||||||
|
margin-right: -16px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header-activate-button,
|
||||||
|
.mailpoet-automation-activate-panel__header-cancel-button {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header-activate-button {
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-activate-panel__header-cancel-button {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mailpoet-automation-activate-panel-animation {
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,3 +17,21 @@
|
|||||||
padding: 3px;
|
padding: 3px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-editor-stats {
|
||||||
|
margin: 0 auto 32px;
|
||||||
|
max-width: 480px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-item {
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-label {
|
||||||
|
color: #787c82;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,18 +4,43 @@
|
|||||||
.mailpoet-add-new-button {
|
.mailpoet-add-new-button {
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mailpoet-automation-listing {
|
.mailpoet-automation-listing-heading {
|
||||||
/* Prevent border radius beneath tabs */
|
margin-bottom: 16px;
|
||||||
border-radius: 0 0 1px 1px;
|
}
|
||||||
}
|
|
||||||
|
.mailpoet-automation-listing {
|
||||||
|
box-shadow: none;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mailpoet-filter-tab-panel {
|
.mailpoet-filter-tab-panel {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 1px;
|
border: 1px solid #dcdcde;
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
|
border-radius: 2px;
|
||||||
outline: none;
|
|
||||||
|
.components-tab-panel__tabs {
|
||||||
|
box-shadow: inset 0 -1px 0 0 #dcdcde;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-tab-panel__tabs-item:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-tab-panel__tabs-item.is-active {
|
||||||
|
box-shadow: inset 0 -4px 0 0 var(--wp-admin-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-tab-panel__tabs-item:focus-visible {
|
||||||
|
box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-tab-panel__tabs-item.is-active:focus-visible {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color),
|
||||||
|
inset 0 -4px 0 0 var(--wp-admin-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
background-color: #f0f0f1;
|
background-color: #f0f0f1;
|
||||||
@ -27,6 +52,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mailpoet-automation-listing-heading {
|
.mailpoet-automation-listing-more-button button.components-button {
|
||||||
margin-bottom: 16px;
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
width: 36px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
.mailpoet-automation-listing-cell-actions {
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
.mailpoet-automation-listing-cell-status {
|
.mailpoet-automation-listing-cell-status {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
> div.components-base-control > div.components-base-control__field {
|
> div.components-base-control > div.components-base-control__field {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
.mailpoet-automation-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-item {
|
||||||
|
color: $color-wordpress-heading;
|
||||||
|
display: grid;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-label {
|
||||||
|
color: #646970;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&.display-after {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-value {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mailpoet-automation-stats-item-separator {
|
||||||
|
color: #a7aaad;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
@ -232,3 +232,7 @@ progress::-moz-progress-bar {
|
|||||||
.mailpoet-form-field-tags label.components-form-token-field__label {
|
.mailpoet-form-field-tags label.components-form-token-field__label {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet-form-field-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
.mailpoet-automation-stats {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin: auto;
|
|
||||||
|
|
||||||
@at-root #mailpoet_automation_editor #{&} {
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mailpoet-automation-stats-item {
|
|
||||||
color: $color-wordpress-heading;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 16px;
|
|
||||||
padding: 0 16px;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
@at-root #mailpoet_automation_editor #{&} {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
@at-root #mailpoet_automation #{&} {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@at-root #mailpoet_automation_editor #{&} {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
align-items: center;
|
|
||||||
color: #a7aaad;
|
|
||||||
content: '›';
|
|
||||||
display: flex;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: normal;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type:after {
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.mailpoet-automation-stats-label {
|
|
||||||
color: #646970;
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,13 @@
|
|||||||
@import '../../../node_modules/@wordpress/edit-site/build-style/style';
|
@import '../../../node_modules/@wordpress/edit-site/build-style/style';
|
||||||
@import '../../../node_modules/@wordpress/block-editor/build-style/style'; // for inserter styles
|
@import '../../../node_modules/@wordpress/block-editor/build-style/style'; // for inserter styles
|
||||||
@import 'settings/colors';
|
@import 'settings/colors';
|
||||||
|
|
||||||
|
// automation components
|
||||||
|
|
||||||
|
@import './components-automation/statistics';
|
||||||
|
|
||||||
|
// automation editor
|
||||||
|
|
||||||
@import './components-automation-editor/add-step-button';
|
@import './components-automation-editor/add-step-button';
|
||||||
@import './components-automation-editor/add-trigger';
|
@import './components-automation-editor/add-trigger';
|
||||||
@import './components-automation-editor/block-icon';
|
@import './components-automation-editor/block-icon';
|
||||||
@ -16,8 +23,8 @@
|
|||||||
@import './components-automation-editor/step-card';
|
@import './components-automation-editor/step-card';
|
||||||
@import './components-automation-editor/workflow';
|
@import './components-automation-editor/workflow';
|
||||||
@import './components-automation-editor/notices';
|
@import './components-automation-editor/notices';
|
||||||
|
@import './components-automation-editor/deactivate-modal';
|
||||||
|
|
||||||
// integrations
|
// integrations
|
||||||
|
|
||||||
@import './components-automation-integrations/mailpoet';
|
@import './components-automation-integrations/mailpoet';
|
||||||
@import './components/automation_statistics';
|
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
@import '../../../node_modules/@woocommerce/components/build-style/style';
|
@import '../../../node_modules/@woocommerce/components/build-style/style';
|
||||||
@import 'settings/colors';
|
@import 'settings/colors';
|
||||||
|
|
||||||
|
// automation components
|
||||||
|
|
||||||
|
@import './components-automation/statistics';
|
||||||
|
|
||||||
|
// automation listing
|
||||||
|
|
||||||
@import './components-automation-listing/listing';
|
@import './components-automation-listing/listing';
|
||||||
@import './components-automation-listing/header';
|
@import './components-automation-listing/header';
|
||||||
@import './components-automation-listing/search';
|
@import './components-automation-listing/search';
|
||||||
|
@import './components-automation-listing/cells/actions';
|
||||||
@import './components-automation-listing/cells/status';
|
@import './components-automation-listing/cells/status';
|
||||||
@import './components/automation_statistics';
|
|
||||||
|
5
mailpoet/assets/img/icons/alert.svg
Normal file
5
mailpoet/assets/img/icons/alert.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4.75C7.99594 4.75 4.75 7.99594 4.75 12C4.75 16.0041 7.99594 19.25 12 19.25C16.0041 19.25 19.25 16.0041 19.25 12C19.25 7.99594 16.0041 4.75 12 4.75ZM3.25 12C3.25 7.16751 7.16751 3.25 12 3.25C16.8325 3.25 20.75 7.16751 20.75 12C20.75 16.8325 16.8325 20.75 12 20.75C7.16751 20.75 3.25 16.8325 3.25 12Z" fill="#d63638"/>
|
||||||
|
<path d="M13 7H11V13H13V7Z" fill="#d63638"/>
|
||||||
|
<path d="M13 15H11V17H13V15Z" fill="#d63638"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 565 B |
@ -2,35 +2,26 @@ import ReactDOM from 'react-dom';
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
||||||
import { plusIcon } from 'common/button/icon/plus';
|
import { plusIcon } from 'common/button/icon/plus';
|
||||||
import { Button, Flex } from '@wordpress/components';
|
import { Button, Flex, Popover, SlotFillProvider } from '@wordpress/components';
|
||||||
import { Workflow } from './listing/workflow';
|
import { useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { initializeApi, useMutation } from './api';
|
||||||
|
import { registerTranslations } from './i18n';
|
||||||
|
import { createStore, storeName } from './listing/store';
|
||||||
import { AutomationListing } from './listing';
|
import { AutomationListing } from './listing';
|
||||||
|
import { registerApiErrorHandler } from './listing/api-error-handler';
|
||||||
|
import { Notices } from './listing/components/notices';
|
||||||
|
import { WorkflowListingNotices } from './listing/workflow-listing-notices';
|
||||||
import { Onboarding } from './onboarding';
|
import { Onboarding } from './onboarding';
|
||||||
import {
|
import {
|
||||||
CreateEmptyWorkflowButton,
|
CreateEmptyWorkflowButton,
|
||||||
CreateWorkflowFromTemplateButton,
|
CreateWorkflowFromTemplateButton,
|
||||||
} from './testing';
|
} from './testing';
|
||||||
import { useMutation, useQuery } from './api';
|
|
||||||
import { WorkflowListingNotices } from './listing/workflow-listing-notices';
|
|
||||||
import { MailPoet } from '../mailpoet';
|
import { MailPoet } from '../mailpoet';
|
||||||
|
|
||||||
function Content(): JSX.Element {
|
function Content(): JSX.Element {
|
||||||
const { data, loading, error } = useQuery<{ data: Workflow[] }>('workflows');
|
const count = useSelect((select) => select(storeName).getWorkflowCount());
|
||||||
|
return count > 0 ? <AutomationListing /> : <Onboarding />;
|
||||||
if (error) {
|
|
||||||
return <div>Error: {error}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div>Loading workflows...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflows = data?.data ?? [];
|
|
||||||
return workflows.length > 0 ? (
|
|
||||||
<AutomationListing workflows={workflows} loading={loading} />
|
|
||||||
) : (
|
|
||||||
<Onboarding />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Workflows(): JSX.Element {
|
function Workflows(): JSX.Element {
|
||||||
@ -38,16 +29,17 @@ function Workflows(): JSX.Element {
|
|||||||
<>
|
<>
|
||||||
<TopBarWithBeamer />
|
<TopBarWithBeamer />
|
||||||
<Flex className="mailpoet-automation-listing-heading">
|
<Flex className="mailpoet-automation-listing-heading">
|
||||||
<h1 className="wp-heading-inline">Automations</h1>
|
<h1 className="wp-heading-inline">{__('Automations', 'mailpoet')}</h1>
|
||||||
<Button
|
<Button
|
||||||
href={MailPoet.urls.automationTemplates}
|
href={MailPoet.urls.automationTemplates}
|
||||||
icon={plusIcon}
|
icon={plusIcon}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="mailpoet-add-new-button"
|
className="mailpoet-add-new-button"
|
||||||
>
|
>
|
||||||
New automation
|
{__('New automation', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Notices />
|
||||||
<Content />
|
<Content />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -104,33 +96,41 @@ function DeleteSchemaButton(): JSX.Element {
|
|||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<SlotFillProvider>
|
||||||
<div>
|
<BrowserRouter>
|
||||||
<Workflows />
|
<div>
|
||||||
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
|
<Workflows />
|
||||||
<CreateEmptyWorkflowButton />
|
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
|
||||||
<CreateWorkflowFromTemplateButton slug="simple-welcome-email">
|
<CreateEmptyWorkflowButton />
|
||||||
Create testing workflow from template (welcome email)
|
<CreateWorkflowFromTemplateButton slug="simple-welcome-email">
|
||||||
</CreateWorkflowFromTemplateButton>
|
Create testing workflow from template (welcome email)
|
||||||
<CreateWorkflowFromTemplateButton slug="welcome-email-sequence">
|
</CreateWorkflowFromTemplateButton>
|
||||||
Create testing workflow from template (welcome sequence, only
|
<CreateWorkflowFromTemplateButton slug="welcome-email-sequence">
|
||||||
premium)
|
Create testing workflow from template (welcome sequence, only
|
||||||
</CreateWorkflowFromTemplateButton>
|
premium)
|
||||||
<CreateWorkflowFromTemplateButton slug="advanced-welcome-email-sequence">
|
</CreateWorkflowFromTemplateButton>
|
||||||
Create testing workflow from template (advanced welcome sequence,
|
<CreateWorkflowFromTemplateButton slug="advanced-welcome-email-sequence">
|
||||||
only premium)
|
Create testing workflow from template (advanced welcome sequence,
|
||||||
</CreateWorkflowFromTemplateButton>
|
only premium)
|
||||||
<RecreateSchemaButton />
|
</CreateWorkflowFromTemplateButton>
|
||||||
<DeleteSchemaButton />
|
<RecreateSchemaButton />
|
||||||
|
<DeleteSchemaButton />
|
||||||
|
</div>
|
||||||
|
<Popover.Slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</BrowserRouter>
|
||||||
</BrowserRouter>
|
</SlotFillProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
createStore();
|
||||||
|
|
||||||
const root = document.getElementById('mailpoet_automation');
|
const root = document.getElementById('mailpoet_automation');
|
||||||
if (root) {
|
if (root) {
|
||||||
|
registerTranslations();
|
||||||
|
registerApiErrorHandler();
|
||||||
|
initializeApi();
|
||||||
ReactDOM.render(<App />, root);
|
ReactDOM.render(<App />, root);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import { Fragment } from '@wordpress/element';
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: Item[];
|
||||||
|
labelPosition?: 'before' | 'after';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Statistics({
|
||||||
|
items,
|
||||||
|
labelPosition = 'before',
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const intl = new Intl.NumberFormat();
|
||||||
|
return (
|
||||||
|
<div className="mailpoet-automation-stats">
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<Fragment key={item.key}>
|
||||||
|
<div key={item.key} className="mailpoet-automation-stats-item">
|
||||||
|
<span
|
||||||
|
className={`mailpoet-automation-stats-label display-${labelPosition}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
<span className="mailpoet-automation-stats-value">
|
||||||
|
{intl.format(item.value)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{i < items.length - 1 && (
|
||||||
|
<div className="mailpoet-automation-stats-item-separator">›</div>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,9 @@ declare global {
|
|||||||
root: string;
|
root: string;
|
||||||
nonce: string;
|
nonce: string;
|
||||||
};
|
};
|
||||||
|
mailpoet_workflow_count: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = window.mailpoet_automation_api;
|
export const api = window.mailpoet_automation_api;
|
||||||
|
export const workflowCount = window.mailpoet_workflow_count;
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
|
|
||||||
export function TrashButton(): JSX.Element {
|
export function TrashButton(): JSX.Element {
|
||||||
@ -20,8 +21,8 @@ export function TrashButton(): JSX.Element {
|
|||||||
<>
|
<>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={showConfirmDialog}
|
isOpen={showConfirmDialog}
|
||||||
title="Delete workflow"
|
title={__('Delete workflow', 'mailpoet')}
|
||||||
confirmButtonText="Yes, delete"
|
confirmButtonText={__('Yes, delete', 'mailpoet')}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
trash(() => {
|
trash(() => {
|
||||||
setShowConfirmDialog(false);
|
setShowConfirmDialog(false);
|
||||||
@ -30,7 +31,12 @@ export function TrashButton(): JSX.Element {
|
|||||||
onCancel={() => setShowConfirmDialog(false)}
|
onCancel={() => setShowConfirmDialog(false)}
|
||||||
__experimentalHideHeader={false}
|
__experimentalHideHeader={false}
|
||||||
>
|
>
|
||||||
You are about to delete the “{workflow.name}” workflow.
|
{sprintf(
|
||||||
|
__('You are about to delete the "%s" workflow.', 'mailpoet'),
|
||||||
|
workflow.name,
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
{__(' This will stop it for all subscribers immediately.', 'mailpoet')}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -38,7 +44,7 @@ export function TrashButton(): JSX.Element {
|
|||||||
isDestructive
|
isDestructive
|
||||||
onClick={() => setShowConfirmDialog(true)}
|
onClick={() => setShowConfirmDialog(true)}
|
||||||
>
|
>
|
||||||
Move to Trash
|
{__('Move to Trash', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ComponentProps, ComponentType, Ref } from 'react';
|
import { ComponentProps, ComponentType, Ref } from 'react';
|
||||||
import {
|
import {
|
||||||
Dropdown as WpDropdown,
|
|
||||||
Button,
|
|
||||||
VisuallyHidden,
|
|
||||||
__experimentalText as Text,
|
__experimentalText as Text,
|
||||||
|
Button,
|
||||||
|
Dropdown as WpDropdown,
|
||||||
|
VisuallyHidden,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { useRef } from '@wordpress/element';
|
import { useRef } from '@wordpress/element';
|
||||||
@ -38,7 +38,11 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
||||||
if (workflowStatus === WorkflowStatus.ACTIVE) {
|
if (workflowStatus === WorkflowStatus.ACTIVE) {
|
||||||
chipClass = 'mailpoet-automation-editor-chip-success';
|
chipClass = 'mailpoet-automation-editor-chip-success';
|
||||||
} else if (workflowStatus === WorkflowStatus.INACTIVE) {
|
} else if (
|
||||||
|
[WorkflowStatus.INACTIVE, WorkflowStatus.DEACTIVATING].includes(
|
||||||
|
workflowStatus,
|
||||||
|
)
|
||||||
|
) {
|
||||||
chipClass = 'mailpoet-automation-editor-chip-danger';
|
chipClass = 'mailpoet-automation-editor-chip-danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
as="h1"
|
as="h1"
|
||||||
>
|
>
|
||||||
<VisuallyHidden as="span">
|
<VisuallyHidden as="span">
|
||||||
{__('Editing workflow: ')}
|
{__('Editing workflow: ', 'mailpoet')}
|
||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
{workflowName}
|
{workflowName}
|
||||||
</Text>
|
</Text>
|
||||||
@ -73,10 +77,14 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
size="body"
|
size="body"
|
||||||
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
||||||
>
|
>
|
||||||
{workflowStatus === WorkflowStatus.ACTIVE && __('Active')}
|
{workflowStatus === WorkflowStatus.ACTIVE &&
|
||||||
|
__('Active', 'mailpoet')}
|
||||||
{workflowStatus === WorkflowStatus.INACTIVE &&
|
{workflowStatus === WorkflowStatus.INACTIVE &&
|
||||||
__('Inactive')}
|
__('Inactive', 'mailpoet')}
|
||||||
{workflowStatus === WorkflowStatus.DRAFT && __('Draft')}
|
{workflowStatus === WorkflowStatus.DEACTIVATING &&
|
||||||
|
__('Deactivating', 'mailpoet')}
|
||||||
|
{workflowStatus === WorkflowStatus.DRAFT &&
|
||||||
|
__('Draft', 'mailpoet')}
|
||||||
</Text>
|
</Text>
|
||||||
</a>
|
</a>
|
||||||
<Button
|
<Button
|
||||||
@ -85,9 +93,9 @@ export function DocumentActions({ children }): JSX.Element {
|
|||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
label={__('Change workflow name')}
|
label={__('Change workflow name', 'mailpoet')}
|
||||||
>
|
>
|
||||||
{showIconLabels && __('Rename')}
|
{showIconLabels && __('Rename', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -12,7 +12,7 @@ import { __ } from '@wordpress/i18n';
|
|||||||
import { Chip } from '../chip';
|
import { Chip } from '../chip';
|
||||||
import { ColoredIcon } from '../icons';
|
import { ColoredIcon } from '../icons';
|
||||||
import {
|
import {
|
||||||
StepError as StepErrorType,
|
StepErrors as StepErrorType,
|
||||||
stepSidebarKey,
|
stepSidebarKey,
|
||||||
storeName,
|
storeName,
|
||||||
} from '../../store';
|
} from '../../store';
|
||||||
@ -155,7 +155,10 @@ export function Errors(): JSX.Element | null {
|
|||||||
className="mailpoet-automation-errors"
|
className="mailpoet-automation-errors"
|
||||||
>
|
>
|
||||||
<div className="mailpoet-automation-errors-header">
|
<div className="mailpoet-automation-errors-header">
|
||||||
{__('The following steps are not fully set:', 'mailpoet')}
|
{
|
||||||
|
// translators: Label for a list of automation workflow steps that are incomplete or have errors
|
||||||
|
__('The following steps are not fully set:', 'mailpoet')
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{stepErrors.map((error) => (
|
{stepErrors.map((error) => (
|
||||||
<StepError key={error.step_id} stepId={error.step_id} />
|
<StepError key={error.step_id} stepId={error.step_id} />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { Button, NavigableMenu, TextControl } from '@wordpress/components';
|
import { Button, NavigableMenu, TextControl } from '@wordpress/components';
|
||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useDispatch, useSelect } from '@wordpress/data';
|
||||||
import { PinnedItems } from '@wordpress/interface';
|
import { PinnedItems } from '@wordpress/interface';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { DocumentActions } from './document_actions';
|
import { DocumentActions } from './document_actions';
|
||||||
@ -8,12 +9,16 @@ import { InserterToggle } from './inserter_toggle';
|
|||||||
import { MoreMenu } from './more_menu';
|
import { MoreMenu } from './more_menu';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
import { WorkflowStatus } from '../../../listing/workflow';
|
import { WorkflowStatus } from '../../../listing/workflow';
|
||||||
|
import {
|
||||||
|
DeactivateImmediatelyModal,
|
||||||
|
DeactivateModal,
|
||||||
|
} from '../modals/deactivate-modal';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/header/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/header/index.js
|
||||||
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/header/index.js
|
// https://github.com/WordPress/gutenberg/blob/0ee78b1bbe9c6f3e6df99f3b967132fa12bef77d/packages/edit-site/src/components/header/index.js
|
||||||
|
|
||||||
function ActivateButton(): JSX.Element {
|
function ActivateButton({ onClick, label }): JSX.Element {
|
||||||
const { errors } = useSelect(
|
const { errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
errors: select(storeName).getErrors(),
|
errors: select(storeName).getErrors(),
|
||||||
@ -21,30 +26,28 @@ function ActivateButton(): JSX.Element {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { activate } = useDispatch(storeName);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="editor-post-publish-button"
|
className="editor-post-publish-button"
|
||||||
onClick={activate}
|
onClick={onClick}
|
||||||
disabled={!!errors}
|
disabled={!!errors}
|
||||||
>
|
>
|
||||||
Activate
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UpdateButton(): JSX.Element {
|
function UpdateButton(): JSX.Element {
|
||||||
const { activate } = useDispatch(storeName);
|
const { save } = useDispatch(storeName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="editor-post-publish-button"
|
className="editor-post-publish-button"
|
||||||
onClick={activate}
|
onClick={save}
|
||||||
>
|
>
|
||||||
Update
|
{__('Update', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -54,16 +57,100 @@ function SaveDraftButton(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="tertiary" onClick={save}>
|
<Button variant="tertiary" onClick={save}>
|
||||||
{__('Save draft')}
|
{__('Save draft', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DeactivateButton(): JSX.Element {
|
||||||
|
const [showDeactivateModal, setShowDeactivateModal] = useState(false);
|
||||||
|
const [isBusy, setIsBusy] = useState(false);
|
||||||
|
const { hasUsersInProgress } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
hasUsersInProgress:
|
||||||
|
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deactivateOrShowModal = () => {
|
||||||
|
if (hasUsersInProgress) {
|
||||||
|
setShowDeactivateModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsBusy(true);
|
||||||
|
void dispatch(storeName).deactivate();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showDeactivateModal && (
|
||||||
|
<DeactivateModal
|
||||||
|
onClose={() => {
|
||||||
|
setShowDeactivateModal(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
isBusy={isBusy}
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={deactivateOrShowModal}
|
||||||
|
>
|
||||||
|
{__('Deactivate', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeactivateNowButton(): JSX.Element {
|
||||||
|
const [showDeactivateModal, setShowDeactivateModal] = useState(false);
|
||||||
|
const [isBusy, setIsBusy] = useState(false);
|
||||||
|
const { hasUsersInProgress } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
hasUsersInProgress:
|
||||||
|
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deactivateOrShowModal = () => {
|
||||||
|
if (hasUsersInProgress) {
|
||||||
|
setShowDeactivateModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsBusy(true);
|
||||||
|
void dispatch(storeName).deactivate();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showDeactivateModal && (
|
||||||
|
<DeactivateImmediatelyModal
|
||||||
|
onClose={() => {
|
||||||
|
setShowDeactivateModal(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
isBusy={isBusy}
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={deactivateOrShowModal}
|
||||||
|
>
|
||||||
|
{__('Deactivate now', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
showInserterToggle: boolean;
|
showInserterToggle: boolean;
|
||||||
|
toggleActivatePanel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Header({ showInserterToggle }: Props): JSX.Element {
|
export function Header({
|
||||||
|
showInserterToggle,
|
||||||
|
toggleActivatePanel,
|
||||||
|
}: Props): JSX.Element {
|
||||||
const { setWorkflowName } = useDispatch(storeName);
|
const { setWorkflowName } = useDispatch(storeName);
|
||||||
const { workflowName, workflowStatus } = useSelect(
|
const { workflowName, workflowStatus } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
@ -90,13 +177,14 @@ export function Header({ showInserterToggle }: Props): JSX.Element {
|
|||||||
{() => (
|
{() => (
|
||||||
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
<div className="mailpoet-automation-editor-dropdown-name-edit">
|
||||||
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
<div className="mailpoet-automation-editor-dropdown-name-edit-title">
|
||||||
{__('Automation name')}
|
{__('Automation name', 'mailpoet')}
|
||||||
</div>
|
</div>
|
||||||
<TextControl
|
<TextControl
|
||||||
value={workflowName}
|
value={workflowName}
|
||||||
onChange={(newName) => setWorkflowName(newName)}
|
onChange={(newName) => setWorkflowName(newName)}
|
||||||
help={__(
|
help={__(
|
||||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||||
|
'mailpoet',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -107,9 +195,36 @@ export function Header({ showInserterToggle }: Props): JSX.Element {
|
|||||||
<div className="edit-site-header_end">
|
<div className="edit-site-header_end">
|
||||||
<div className="edit-site-header__actions">
|
<div className="edit-site-header__actions">
|
||||||
<Errors />
|
<Errors />
|
||||||
<SaveDraftButton />
|
{workflowStatus === WorkflowStatus.DRAFT && (
|
||||||
{workflowStatus !== WorkflowStatus.ACTIVE && <ActivateButton />}
|
<>
|
||||||
{workflowStatus === WorkflowStatus.ACTIVE && <UpdateButton />}
|
<SaveDraftButton />
|
||||||
|
<ActivateButton
|
||||||
|
onClick={toggleActivatePanel}
|
||||||
|
label={__('Activate', 'mailpoet')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{workflowStatus === WorkflowStatus.ACTIVE && (
|
||||||
|
<>
|
||||||
|
<DeactivateButton />
|
||||||
|
<UpdateButton />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{workflowStatus === WorkflowStatus.DEACTIVATING && (
|
||||||
|
<>
|
||||||
|
<DeactivateNowButton />
|
||||||
|
<ActivateButton
|
||||||
|
onClick={toggleActivatePanel}
|
||||||
|
label={__('Update & Activate', 'mailpoet')}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{workflowStatus === WorkflowStatus.INACTIVE && (
|
||||||
|
<ActivateButton
|
||||||
|
onClick={toggleActivatePanel}
|
||||||
|
label={__('Update & Activate', 'mailpoet')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PinnedItems.Slot scope={storeName} />
|
<PinnedItems.Slot scope={storeName} />
|
||||||
<MoreMenu />
|
<MoreMenu />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Button, ToolbarItem } from '@wordpress/components';
|
import { Button, ToolbarItem } from '@wordpress/components';
|
||||||
import { useSelect, useDispatch } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
import { __, _x } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { plus } from '@wordpress/icons';
|
import { plus } from '@wordpress/icons';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
|
|
||||||
@ -28,13 +28,11 @@ export function InserterToggle(): JSX.Element {
|
|||||||
onMouseDown={(event) => event.preventDefault()}
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
onClick={toggleInserterSidebar}
|
onClick={toggleInserterSidebar}
|
||||||
icon={plus}
|
icon={plus}
|
||||||
label={_x(
|
label={__('Toggle step inserter', 'mailpoet')}
|
||||||
'Toggle step inserter',
|
|
||||||
'Generic label for step inserter button',
|
|
||||||
)}
|
|
||||||
showTooltip={!showIconLabels}
|
showTooltip={!showIconLabels}
|
||||||
>
|
>
|
||||||
{showIconLabels && (!isInserterOpened ? __('Add') : __('Close'))}
|
{showIconLabels &&
|
||||||
|
(!isInserterOpened ? __('Add', 'mailpoet') : __('Close', 'mailpoet'))}
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,14 @@ export function MoreMenu(): JSX.Element {
|
|||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<MenuGroup label={_x('View', 'noun')}>
|
<MenuGroup label={_x('View', 'noun', 'mailpoet')}>
|
||||||
<PreferenceToggleMenuItem
|
<PreferenceToggleMenuItem
|
||||||
scope={storeName}
|
scope={storeName}
|
||||||
name="fullscreenMode"
|
name="fullscreenMode"
|
||||||
label={__('Fullscreen mode')}
|
label={__('Fullscreen mode', 'mailpoet')}
|
||||||
info={__('Work without distraction')}
|
info={__('Work without distraction', 'mailpoet')}
|
||||||
messageActivated={__('Fullscreen mode activated')}
|
messageActivated={__('Fullscreen mode activated', 'mailpoet')}
|
||||||
messageDeactivated={__('Fullscreen mode deactivated')}
|
messageDeactivated={__('Fullscreen mode deactivated', 'mailpoet')}
|
||||||
shortcut={displayShortcut.secondary('f')}
|
shortcut={displayShortcut.secondary('f')}
|
||||||
/>
|
/>
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
|
@ -13,7 +13,10 @@ export const InserterListboxGroup = forwardRef<HTMLDivElement, Props>(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldSpeak) {
|
if (shouldSpeak) {
|
||||||
speak(__('Use left and right arrow keys to move through blocks'));
|
speak(
|
||||||
|
// translators: Moving through automation step list using keyboard
|
||||||
|
__('Use left and right arrow keys to move through steps', 'mailpoet'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [shouldSpeak]);
|
}, [shouldSpeak]);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { forwardRef, Fragment, useCallback, useMemo } from 'react';
|
|||||||
import { SearchControl } from '@wordpress/components';
|
import { SearchControl } from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { useRef, useImperativeHandle, useState } from '@wordpress/element';
|
import { useRef, useImperativeHandle, useState } from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { blockDefault, Icon } from '@wordpress/icons';
|
import { blockDefault, Icon } from '@wordpress/icons';
|
||||||
import { Group } from './group';
|
import { Group } from './group';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
@ -41,21 +41,26 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
|||||||
{
|
{
|
||||||
type: 'triggers',
|
type: 'triggers',
|
||||||
title: undefined,
|
title: undefined,
|
||||||
label: __('Triggers', 'mailpoet'),
|
// translators: Label for a list of automation steps of type trigger
|
||||||
|
label: _x('Triggers', 'automation steps', 'mailpoet'),
|
||||||
items: steps.filter(({ group }) => group === 'triggers'),
|
items: steps.filter(({ group }) => group === 'triggers'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
type: 'actions',
|
type: 'actions',
|
||||||
title: __('Actions', 'mailpoet'),
|
// translators: Label for a list of automation steps of type action
|
||||||
label: __('Actions', 'mailpoet'),
|
title: _x('Actions', 'automation steps', 'mailpoet'),
|
||||||
|
// translators: Label for a list of automation steps of type action
|
||||||
|
label: _x('Actions', 'automation steps', 'mailpoet'),
|
||||||
items: steps.filter(({ group }) => group === 'actions'),
|
items: steps.filter(({ group }) => group === 'actions'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'logical',
|
type: 'logical',
|
||||||
title: __('Logical', 'mailpoet'),
|
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||||
label: __('Logical', 'mailpoet'),
|
title: _x('Logical', 'automation steps', 'mailpoet'),
|
||||||
|
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||||
|
label: _x('Logical', 'automation steps', 'mailpoet'),
|
||||||
items: steps.filter(({ group }) => group === 'logical'),
|
items: steps.filter(({ group }) => group === 'logical'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -96,8 +101,8 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
|||||||
setFilterValue(value);
|
setFilterValue(value);
|
||||||
}}
|
}}
|
||||||
value={filterValue}
|
value={filterValue}
|
||||||
label={__('Search for blocks and patterns')}
|
label={__('Search for automation steps', 'mailpoet')}
|
||||||
placeholder={__('Search')}
|
placeholder={__('Search', 'mailpoet')}
|
||||||
ref={searchRef}
|
ref={searchRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -135,7 +140,7 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
|||||||
className="block-editor-inserter__no-results-icon"
|
className="block-editor-inserter__no-results-icon"
|
||||||
icon={blockDefault}
|
icon={blockDefault}
|
||||||
/>
|
/>
|
||||||
<p>{__('No results found.')}</p>
|
<p>{__('No results found.', 'mailpoet')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</InserterListbox>
|
</InserterListbox>
|
||||||
|
@ -25,7 +25,7 @@ export function KeyboardShortcuts(): null {
|
|||||||
void registerShortcut({
|
void registerShortcut({
|
||||||
name: 'mailpoet/automation-editor/toggle-fullscreen',
|
name: 'mailpoet/automation-editor/toggle-fullscreen',
|
||||||
category: 'global',
|
category: 'global',
|
||||||
description: __('Toggle fullscreen mode.'),
|
description: __('Toggle fullscreen mode.', 'mailpoet'),
|
||||||
keyCombination: {
|
keyCombination: {
|
||||||
modifier: 'secondary',
|
modifier: 'secondary',
|
||||||
character: 'f',
|
character: 'f',
|
||||||
@ -35,7 +35,7 @@ export function KeyboardShortcuts(): null {
|
|||||||
void registerShortcut({
|
void registerShortcut({
|
||||||
name: 'mailpoet/automation-editor/toggle-sidebar',
|
name: 'mailpoet/automation-editor/toggle-sidebar',
|
||||||
category: 'global',
|
category: 'global',
|
||||||
description: __('Show or hide the settings sidebar.'),
|
description: __('Show or hide the settings sidebar.', 'mailpoet'),
|
||||||
keyCombination: {
|
keyCombination: {
|
||||||
modifier: 'primaryShift',
|
modifier: 'primaryShift',
|
||||||
character: ',',
|
character: ',',
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, Modal } from '@wordpress/components';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { WorkflowStatus } from '../../../listing/workflow';
|
||||||
|
|
||||||
|
type DeactivateImmediatelyModalProps = {
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
export function DeactivateImmediatelyModal({
|
||||||
|
onClose,
|
||||||
|
}: DeactivateImmediatelyModalProps): JSX.Element {
|
||||||
|
const [isBusy, setIsBusy] = useState<boolean>(false);
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="mailpoet-automatoin-deactivate-modal"
|
||||||
|
title={__('Stop automatoin for all subscribers?', 'mailpoet')}
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
'Are you sure you want to deactivate now? This would stop this automation for all subscribers immediately.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isBusy={isBusy}
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setIsBusy(true);
|
||||||
|
dispatch(storeName).deactivate(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Deactivate now', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button disabled={isBusy} variant="tertiary" onClick={onClose}>
|
||||||
|
{__('Cancel', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeactivateModalProps = {
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
export function DeactivateModal({
|
||||||
|
onClose,
|
||||||
|
}: DeactivateModalProps): JSX.Element {
|
||||||
|
const { workflowName } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
workflowName: select(storeName).getWorkflowData().name,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [selected, setSelected] = useState<
|
||||||
|
WorkflowStatus.INACTIVE | WorkflowStatus.DEACTIVATING
|
||||||
|
>(WorkflowStatus.DEACTIVATING);
|
||||||
|
const [isBusy, setIsBusy] = useState<boolean>(false);
|
||||||
|
// translators: %s is the name of the automation.
|
||||||
|
const title = sprintf(
|
||||||
|
__('Deactivate the "%s" automation?', 'mailpoet'),
|
||||||
|
workflowName,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="mailpoet-automatoin-deactivate-modal"
|
||||||
|
title={title}
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Some subscribers entered but have not finished the flow. Let's decide what to do in this case.",
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
<ul className="mailpoet-automation-options">
|
||||||
|
<li>
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
selected === WorkflowStatus.DEACTIVATING
|
||||||
|
? 'mailpoet-automation-option active'
|
||||||
|
: 'mailpoet-automation-option'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
disabled={isBusy}
|
||||||
|
name="deactivation-method"
|
||||||
|
checked={selected === WorkflowStatus.DEACTIVATING}
|
||||||
|
onChange={() => setSelected(WorkflowStatus.DEACTIVATING)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<strong>
|
||||||
|
{__('Let entered subscribers finish the flow', 'mailpoet')}
|
||||||
|
</strong>
|
||||||
|
{__(
|
||||||
|
"New subscribers won't enter, but recently entered could proceed.",
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
selected === WorkflowStatus.INACTIVE
|
||||||
|
? 'mailpoet-automation-option active'
|
||||||
|
: 'mailpoet-automation-option'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
disabled={isBusy}
|
||||||
|
name="deactivation-method"
|
||||||
|
checked={selected === WorkflowStatus.INACTIVE}
|
||||||
|
onChange={() => setSelected(WorkflowStatus.INACTIVE)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<strong>
|
||||||
|
{__('Stop automation for all subscribers', 'mailpoet')}
|
||||||
|
</strong>
|
||||||
|
{__(
|
||||||
|
'Automation will be deactivated for all the subscribers immediately.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isBusy={isBusy}
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setIsBusy(true);
|
||||||
|
dispatch(storeName).deactivate(
|
||||||
|
selected !== WorkflowStatus.DEACTIVATING,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Deactivate automation', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button disabled={isBusy} variant="tertiary" onClick={onClose}>
|
||||||
|
{__('Cancel', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { Button, Spinner } from '@wordpress/components';
|
||||||
|
import { closeSmall } from '@wordpress/icons';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { WorkflowStatus } from '../../../listing/workflow';
|
||||||
|
import { MailPoet } from '../../../../mailpoet';
|
||||||
|
|
||||||
|
function PreStep({ onClose }): JSX.Element {
|
||||||
|
const [isActivating, setIsActivating] = useState(false);
|
||||||
|
const { activate } = useDispatch(storeName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mailpoet-automation-activate-panel__header">
|
||||||
|
<div className="mailpoet-automation-activate-panel__header-activate-button">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
disabled={isActivating}
|
||||||
|
isBusy={isActivating}
|
||||||
|
autoFocus={!isActivating}
|
||||||
|
onClick={() => {
|
||||||
|
setIsActivating(true);
|
||||||
|
activate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isActivating && __('Activating…', 'mailpoet')}
|
||||||
|
{!isActivating && __('Activate', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mailpoet-automation-activate-panel__header-cancel-button">
|
||||||
|
<Button variant="secondary" onClick={onClose} disabled={isActivating}>
|
||||||
|
{__('Cancel', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isActivating && (
|
||||||
|
<div className="mailpoet-automation-activate-panel__body">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isActivating && (
|
||||||
|
<div className="mailpoet-automation-activate-panel__body">
|
||||||
|
<p>
|
||||||
|
<strong>{__('Are you ready to activate?', 'mailpoet')}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__('Double-check your settings before activating.', 'mailpoet')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PostStep({ onClose }): JSX.Element {
|
||||||
|
const { workflow } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
workflow: select(storeName).getWorkflowData(),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const goToListings = () => {
|
||||||
|
window.location.href = MailPoet.urls.automationListing;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mailpoet-automation-activate-panel__header">
|
||||||
|
<Button
|
||||||
|
icon={closeSmall}
|
||||||
|
onClick={onClose}
|
||||||
|
label={__('Close', 'mailpoet')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mailpoet-automation-activate-panel__body">
|
||||||
|
<div className="mailpoet-automation-activate-panel__section">
|
||||||
|
{sprintf(__('"%s" is now live.', 'mailpoet'), workflow.name)}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<strong>{__('What’s next?', 'mailpoet')}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
'View all your automations to track statistics and create new ones.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<Button variant="secondary" onClick={goToListings}>
|
||||||
|
{__('View all automations', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActivatePanel({ onClose }): JSX.Element {
|
||||||
|
const { workflow, errors } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
errors: select(storeName).getErrors(),
|
||||||
|
workflow: select(storeName).getWorkflowData(),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errors) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [errors, onClose]);
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const isActive = workflow.status === WorkflowStatus.ACTIVE;
|
||||||
|
return (
|
||||||
|
<div className="mailpoet-automation-activate-panel">
|
||||||
|
{isActive && <PostStep onClose={onClose} />}
|
||||||
|
{!isActive && <PreStep onClose={onClose} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Dropdown, TextControl } from '@wordpress/components';
|
import { Dropdown, TextControl } from '@wordpress/components';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { edit, Icon } from '@wordpress/icons';
|
import { edit, Icon } from '@wordpress/icons';
|
||||||
import { PlainBodyTitle } from './plain-body-title';
|
import { PlainBodyTitle } from './plain-body-title';
|
||||||
import { TitleActionButton } from './title-action-button';
|
import { TitleActionButton } from './title-action-button';
|
||||||
@ -25,7 +26,7 @@ export function StepName({
|
|||||||
<TitleActionButton
|
<TitleActionButton
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
aria-label="Edit step name"
|
aria-label={__('Edit step name', 'mailpoet')}
|
||||||
>
|
>
|
||||||
<Icon icon={edit} size={16} />
|
<Icon icon={edit} size={16} />
|
||||||
</TitleActionButton>
|
</TitleActionButton>
|
||||||
@ -33,13 +34,15 @@ export function StepName({
|
|||||||
)}
|
)}
|
||||||
renderContent={() => (
|
renderContent={() => (
|
||||||
<TextControl
|
<TextControl
|
||||||
label="Step name"
|
label={__('Step name', 'mailpoet')}
|
||||||
className="mailpoet-step-name-input"
|
className="mailpoet-step-name-input"
|
||||||
placeholder={defaultName}
|
placeholder={defaultName}
|
||||||
value={currentName}
|
value={currentName}
|
||||||
onChange={update}
|
onChange={update}
|
||||||
help="Give the automation step a name that indicates its purpose. E.g
|
help={__(
|
||||||
“Abandoned cart recovery”. This name will be displayed only to you and not to the clients."
|
'Give the automation step a name that indicates its purpose. E.g "Abandoned cart recovery". This name will be displayed only to you and not to the clients.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
@ -17,12 +18,12 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
|||||||
|
|
||||||
const [workflowAriaLabel, workflowActiveClass] =
|
const [workflowAriaLabel, workflowActiveClass] =
|
||||||
sidebarKey === workflowSidebarKey
|
sidebarKey === workflowSidebarKey
|
||||||
? ['Workflow (selected)', 'is-active']
|
? [__('Workflow (selected)', 'mailpoet'), 'is-active']
|
||||||
: ['Workflow', ''];
|
: ['Workflow', ''];
|
||||||
|
|
||||||
const [stepAriaLabel, stepActiveClass] =
|
const [stepAriaLabel, stepActiveClass] =
|
||||||
sidebarKey === stepSidebarKey
|
sidebarKey === stepSidebarKey
|
||||||
? ['Step (selected)', 'is-active']
|
? [__('Step (selected)', 'mailpoet'), 'is-active']
|
||||||
: ['Step', ''];
|
: ['Step', ''];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -32,9 +33,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
|||||||
onClick={openWorkflowSettings}
|
onClick={openWorkflowSettings}
|
||||||
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`}
|
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`}
|
||||||
aria-label={workflowAriaLabel}
|
aria-label={workflowAriaLabel}
|
||||||
data-label="Workflow"
|
data-label={__('Workflow', 'mailpoet')}
|
||||||
>
|
>
|
||||||
Workflow
|
{__('Workflow', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@ -42,9 +43,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
|||||||
onClick={openStepSettings}
|
onClick={openStepSettings}
|
||||||
className={`edit-site-sidebar__panel-tab ${stepActiveClass}`}
|
className={`edit-site-sidebar__panel-tab ${stepActiveClass}`}
|
||||||
aria-label={stepAriaLabel}
|
aria-label={stepAriaLabel}
|
||||||
data-label="Workflow"
|
data-label={__('Step', 'mailpoet')}
|
||||||
>
|
>
|
||||||
Step
|
{__('Step', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -47,13 +47,13 @@ export function Sidebar(props: Props): JSX.Element {
|
|||||||
<ComplementaryArea
|
<ComplementaryArea
|
||||||
identifier={sidebarKey}
|
identifier={sidebarKey}
|
||||||
header={<Header sidebarKey={sidebarKey} />}
|
header={<Header sidebarKey={sidebarKey} />}
|
||||||
closeLabel={__('Close settings')}
|
closeLabel={__('Close settings', 'mailpoet')}
|
||||||
headerClassName="edit-site-sidebar__panel-tabs"
|
headerClassName="edit-site-sidebar__panel-tabs"
|
||||||
title={__('Settings')}
|
title={__('Settings', 'mailpoet')}
|
||||||
icon={cog}
|
icon={cog}
|
||||||
className="edit-site-sidebar mailpoet-automation-sidebar"
|
className="edit-site-sidebar mailpoet-automation-sidebar"
|
||||||
panelClassName="edit-site-sidebar"
|
panelClassName="edit-site-sidebar"
|
||||||
smallScreenTitle={workflowName || __('(no title)')}
|
smallScreenTitle={workflowName || __('(no title)', 'mailpoet')}
|
||||||
scope={storeName}
|
scope={storeName}
|
||||||
toggleShortcut={keyboardShortcut}
|
toggleShortcut={keyboardShortcut}
|
||||||
isActiveByDefault={sidebarActiveByDefault}
|
isActiveByDefault={sidebarActiveByDefault}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { PanelBody } from '@wordpress/components';
|
import { PanelBody } from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../../store';
|
import { storeName } from '../../../store';
|
||||||
import { StepCard } from '../../step-card';
|
import { StepCard } from '../../step-card';
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export function StepSidebar(): JSX.Element {
|
|||||||
|
|
||||||
<Edit />
|
<Edit />
|
||||||
|
|
||||||
<PanelBody title="Debug info" initialOpen={false}>
|
<PanelBody title={__('Debug info', 'mailpoet')} initialOpen={false}>
|
||||||
<div>
|
<div>
|
||||||
<strong>ID:</strong> {selectedStep.id}
|
<strong>ID:</strong> {selectedStep.id}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { PanelBody, PanelRow } from '@wordpress/components';
|
import { PanelBody, PanelRow } from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../../store';
|
import { storeName } from '../../../store';
|
||||||
import { TrashButton } from '../../actions/trash-button';
|
import { TrashButton } from '../../actions/trash-button';
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ export function WorkflowSidebar(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelBody title="Automation details" initialOpen>
|
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
|
||||||
<PanelRow>
|
<PanelRow>
|
||||||
<strong>Date added</strong>{' '}
|
<strong>Date added</strong>{' '}
|
||||||
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
|
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
export function EmptyWorkflow(): JSX.Element {
|
export function EmptyWorkflow(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-editor-empty-workflow">
|
<div className="mailpoet-automation-editor-empty-workflow">
|
||||||
No workflow data.
|
{__('No workflow data.', 'mailpoet')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { _x } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../store';
|
import { storeName } from '../../store';
|
||||||
|
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
||||||
|
|
||||||
export function Statistics(): JSX.Element {
|
export function Statistics(): JSX.Element {
|
||||||
const { workflow } = useSelect(
|
const { workflow } = useSelect(
|
||||||
@ -11,27 +12,29 @@ export function Statistics(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="mailpoet-automation-editor-stats">
|
||||||
<ul className="mailpoet-automation-stats">
|
<BaseStatistics
|
||||||
<li className="mailpoet-automation-stats-item">
|
items={[
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Total Entered', 'mailpoet')}
|
key: 'entered',
|
||||||
</span>
|
// translators: Total number of subscribers who entered an automation workflow
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.entered)}
|
label: _x('Total Entered', 'automation stats', 'mailpoet'),
|
||||||
</li>
|
value: workflow.stats.totals.entered,
|
||||||
<li className="mailpoet-automation-stats-item">
|
},
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Total Processing', 'mailpoet')}
|
key: 'processing',
|
||||||
</span>
|
// translators: Total number of subscribers who are being processed in an automation workflow
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.in_progress)}
|
label: _x('Total Processing', 'automation stats', 'mailpoet'),
|
||||||
</li>
|
value: workflow.stats.totals.in_progress,
|
||||||
<li className="mailpoet-automation-stats-item">
|
},
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Total Exited', 'mailpoet')}
|
key: 'exited',
|
||||||
</span>
|
// translators: Total number of subscribers who exited an automation workflow, no matter the result
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.exited)}
|
label: _x('Total Exited', 'automation stats', 'mailpoet'),
|
||||||
</li>
|
value: workflow.stats.totals.exited,
|
||||||
</ul>
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export function StepMoreMenu({ step }: Props): JSX.Element {
|
|||||||
utm_campaign: 'remove_automation_step',
|
utm_campaign: 'remove_automation_step',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{__('You cannot remove a new step from the automation.', 'mailpoet')}
|
{__('You cannot remove a step from the automation.', 'mailpoet')}
|
||||||
</PremiumModal>
|
</PremiumModal>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -3,7 +3,7 @@ import { useContext } from 'react';
|
|||||||
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
||||||
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
||||||
import { blockMeta } from '@wordpress/icons';
|
import { blockMeta } from '@wordpress/icons';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { WorkflowCompositeContext } from './context';
|
import { WorkflowCompositeContext } from './context';
|
||||||
import { StepMoreMenu } from './step-more-menu';
|
import { StepMoreMenu } from './step-more-menu';
|
||||||
import { Step as StepData } from './types';
|
import { Step as StepData } from './types';
|
||||||
@ -51,6 +51,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
|||||||
const compositeState = useContext(WorkflowCompositeContext);
|
const compositeState = useContext(WorkflowCompositeContext);
|
||||||
const { batch } = useRegistry();
|
const { batch } = useRegistry();
|
||||||
|
|
||||||
|
const compositeItemId = `step-${step.id}`;
|
||||||
const stepTypeData = stepType ?? getUnknownStepType(step);
|
const stepTypeData = stepType ?? getUnknownStepType(step);
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-editor-step-wrapper">
|
<div className="mailpoet-automation-editor-step-wrapper">
|
||||||
@ -63,6 +64,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
|||||||
'is-selected-step': isSelected,
|
'is-selected-step': isSelected,
|
||||||
'is-unknown-step': !stepType,
|
'is-unknown-step': !stepType,
|
||||||
})}
|
})}
|
||||||
|
id={compositeItemId}
|
||||||
key={step.id}
|
key={step.id}
|
||||||
focusable
|
focusable
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -82,11 +84,14 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="mailpoet-automation-editor-step-title">
|
<label
|
||||||
|
htmlFor={compositeItemId}
|
||||||
|
className="mailpoet-automation-editor-step-title"
|
||||||
|
>
|
||||||
{step.type !== 'trigger'
|
{step.type !== 'trigger'
|
||||||
? stepTypeData.title
|
? stepTypeData.title
|
||||||
: __('Trigger', 'mailpoet')}
|
: _x('Trigger', 'noun', 'mailpoet')}
|
||||||
</div>
|
</label>
|
||||||
<div className="mailpoet-automation-editor-step-subtitle">
|
<div className="mailpoet-automation-editor-step-subtitle">
|
||||||
{step.type !== 'trigger'
|
{step.type !== 'trigger'
|
||||||
? stepTypeData.subtitle(step)
|
? stepTypeData.subtitle(step)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { WorkflowStatus } from '../../../listing/workflow';
|
||||||
|
|
||||||
export type NextStep = {
|
export type NextStep = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
@ -13,7 +15,7 @@ export type Step = {
|
|||||||
export type Workflow = {
|
export type Workflow = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
status: 'active' | 'inactive' | 'draft' | 'trash';
|
status: WorkflowStatus;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
activated_at: string;
|
activated_at: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { useState } from 'react';
|
||||||
import { Button, Icon, Popover, SlotFillProvider } from '@wordpress/components';
|
import { Button, Icon, Popover, SlotFillProvider } from '@wordpress/components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { wordpress } from '@wordpress/icons';
|
import { wordpress } from '@wordpress/icons';
|
||||||
@ -23,6 +24,8 @@ import { initialize as initializeMailPoetIntegration } from '../integrations/mai
|
|||||||
import { MailPoet } from '../../mailpoet';
|
import { MailPoet } from '../../mailpoet';
|
||||||
import { LISTING_NOTICE_PARAMETERS } from '../listing/workflow-listing-notices';
|
import { LISTING_NOTICE_PARAMETERS } from '../listing/workflow-listing-notices';
|
||||||
import { registerApiErrorHandler } from './api-error-handler';
|
import { registerApiErrorHandler } from './api-error-handler';
|
||||||
|
import { ActivatePanel } from './components/panel/activate-panel';
|
||||||
|
import { registerTranslations } from '../i18n';
|
||||||
|
|
||||||
// See:
|
// See:
|
||||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
|
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
|
||||||
@ -48,6 +51,7 @@ function Editor(): JSX.Element {
|
|||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
const [showActivatePanel, setShowActivatePanel] = useState(false);
|
||||||
|
|
||||||
const className = classnames('interface-interface-skeleton', {
|
const className = classnames('interface-interface-skeleton', {
|
||||||
'is-sidebar-opened': isSidebarOpened,
|
'is-sidebar-opened': isSidebarOpened,
|
||||||
@ -60,6 +64,11 @@ function Editor(): JSX.Element {
|
|||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleActivatePanel = () => {
|
||||||
|
setShowActivatePanel(!showActivatePanel);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShortcutProvider>
|
<ShortcutProvider>
|
||||||
<SlotFillProvider>
|
<SlotFillProvider>
|
||||||
@ -80,7 +89,12 @@ function Editor(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
header={<Header showInserterToggle={showInserterSidebar} />}
|
header={
|
||||||
|
<Header
|
||||||
|
showInserterToggle={showInserterSidebar}
|
||||||
|
toggleActivatePanel={toggleActivatePanel}
|
||||||
|
/>
|
||||||
|
}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<EditorNotices />
|
<EditorNotices />
|
||||||
@ -92,6 +106,7 @@ function Editor(): JSX.Element {
|
|||||||
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{showActivatePanel && <ActivatePanel onClose={toggleActivatePanel} />}
|
||||||
<Popover.Slot />
|
<Popover.Slot />
|
||||||
</SlotFillProvider>
|
</SlotFillProvider>
|
||||||
</ShortcutProvider>
|
</ShortcutProvider>
|
||||||
@ -103,6 +118,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
const root = document.getElementById('mailpoet_automation_editor');
|
const root = document.getElementById('mailpoet_automation_editor');
|
||||||
if (root) {
|
if (root) {
|
||||||
|
registerTranslations();
|
||||||
registerApiErrorHandler();
|
registerApiErrorHandler();
|
||||||
initializeApi();
|
initializeApi();
|
||||||
initializeCoreIntegration();
|
initializeCoreIntegration();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { select } from '@wordpress/data';
|
import { dispatch, select, StoreDescriptor } from '@wordpress/data';
|
||||||
import { apiFetch } from '@wordpress/data-controls';
|
import { apiFetch } from '@wordpress/data-controls';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { store as interfaceStore } from '@wordpress/interface';
|
import { store as interfaceStore } from '@wordpress/interface';
|
||||||
import { store as preferencesStore } from '@wordpress/preferences';
|
import { store as preferencesStore } from '@wordpress/preferences';
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
@ -7,6 +9,7 @@ import { storeName } from './constants';
|
|||||||
import { Feature, State } from './types';
|
import { Feature, State } from './types';
|
||||||
import { LISTING_NOTICE_PARAMETERS } from '../../listing/workflow-listing-notices';
|
import { LISTING_NOTICE_PARAMETERS } from '../../listing/workflow-listing-notices';
|
||||||
import { MailPoet } from '../../../mailpoet';
|
import { MailPoet } from '../../../mailpoet';
|
||||||
|
import { WorkflowStatus } from '../../listing/workflow';
|
||||||
|
|
||||||
export const openSidebar =
|
export const openSidebar =
|
||||||
(key) =>
|
(key) =>
|
||||||
@ -62,6 +65,17 @@ export function* save() {
|
|||||||
data: { ...workflow },
|
data: { ...workflow },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
|
if (data?.data) {
|
||||||
|
void createNotice(
|
||||||
|
'success',
|
||||||
|
__('The automation has been saved.', 'mailpoet'),
|
||||||
|
{
|
||||||
|
type: 'snackbar',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'SAVE',
|
type: 'SAVE',
|
||||||
workflow: data?.data ?? workflow,
|
workflow: data?.data ?? workflow,
|
||||||
@ -75,16 +89,72 @@ export function* activate() {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
...workflow,
|
...workflow,
|
||||||
status: 'active',
|
status: WorkflowStatus.ACTIVE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
|
if (data?.data.status === WorkflowStatus.ACTIVE) {
|
||||||
|
void createNotice(
|
||||||
|
'success',
|
||||||
|
__('Well done! Automation is now activated!', 'mailpoet'),
|
||||||
|
{
|
||||||
|
type: 'snackbar',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ACTIVATE',
|
type: 'ACTIVATE',
|
||||||
workflow: data?.data ?? workflow,
|
workflow: data?.data ?? workflow,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* deactivate(deactivateWorkflowRuns = true) {
|
||||||
|
const workflow = select(storeName).getWorkflowData();
|
||||||
|
const data = yield apiFetch({
|
||||||
|
path: `/workflows/${workflow.id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data: {
|
||||||
|
...workflow,
|
||||||
|
status: deactivateWorkflowRuns
|
||||||
|
? WorkflowStatus.INACTIVE
|
||||||
|
: WorkflowStatus.DEACTIVATING,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||||
|
if (deactivateWorkflowRuns && data?.data.status === WorkflowStatus.INACTIVE) {
|
||||||
|
void createNotice(
|
||||||
|
'success',
|
||||||
|
__('Automation is now deactivated!', 'mailpoet'),
|
||||||
|
{
|
||||||
|
type: 'snackbar',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!deactivateWorkflowRuns &&
|
||||||
|
data?.data.status === WorkflowStatus.DEACTIVATING
|
||||||
|
) {
|
||||||
|
void createNotice(
|
||||||
|
'success',
|
||||||
|
__(
|
||||||
|
'Automation is deactivated. But recent users are still going through the flow.',
|
||||||
|
'mailpoet',
|
||||||
|
),
|
||||||
|
{
|
||||||
|
type: 'snackbar',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'DEACTIVATE',
|
||||||
|
workflow: data?.data ?? workflow,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
export function* trash(onTrashed: () => void = undefined) {
|
export function* trash(onTrashed: () => void = undefined) {
|
||||||
const workflow = select(storeName).getWorkflowData();
|
const workflow = select(storeName).getWorkflowData();
|
||||||
const data = yield apiFetch({
|
const data = yield apiFetch({
|
||||||
@ -92,13 +162,13 @@ export function* trash(onTrashed: () => void = undefined) {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
...workflow,
|
...workflow,
|
||||||
status: 'trash',
|
status: WorkflowStatus.TRASH,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onTrashed?.();
|
onTrashed?.();
|
||||||
|
|
||||||
if (data?.status === 'trash') {
|
if (data?.status === WorkflowStatus.TRASH) {
|
||||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||||
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
|
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
|
||||||
});
|
});
|
||||||
|
@ -39,6 +39,12 @@ export function reducer(state: State, action: Action): State {
|
|||||||
workflowData: action.workflow,
|
workflowData: action.workflow,
|
||||||
workflowSaved: true,
|
workflowSaved: true,
|
||||||
};
|
};
|
||||||
|
case 'DEACTIVATE':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workflowData: action.workflow,
|
||||||
|
workflowSaved: true,
|
||||||
|
};
|
||||||
case 'TRASH':
|
case 'TRASH':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -2,7 +2,7 @@ import { createRegistrySelector } from '@wordpress/data';
|
|||||||
import { store as interfaceStore } from '@wordpress/interface';
|
import { store as interfaceStore } from '@wordpress/interface';
|
||||||
import { store as preferencesStore } from '@wordpress/preferences';
|
import { store as preferencesStore } from '@wordpress/preferences';
|
||||||
import { storeName } from './constants';
|
import { storeName } from './constants';
|
||||||
import { Context, Errors, Feature, State, StepError, StepType } from './types';
|
import { Context, Errors, Feature, State, StepErrors, StepType } from './types';
|
||||||
import { Item } from '../components/inserter/item';
|
import { Item } from '../components/inserter/item';
|
||||||
import { Step, Workflow } from '../components/workflow/types';
|
import { Step, Workflow } from '../components/workflow/types';
|
||||||
|
|
||||||
@ -78,6 +78,6 @@ export function getErrors(state: State): Errors | undefined {
|
|||||||
return state.errors;
|
return state.errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStepError(state: State, id: string): StepError | undefined {
|
export function getStepError(state: State, id: string): StepErrors | undefined {
|
||||||
return state.errors?.steps[id] ?? undefined;
|
return state.errors?.steps[id] ?? undefined;
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,14 @@ export type StepType = {
|
|||||||
background: string;
|
background: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StepError = {
|
export type StepErrors = {
|
||||||
step_id: string;
|
step_id: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
fields: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Errors = {
|
export type Errors = {
|
||||||
steps: Record<string, StepError>;
|
steps: Record<string, StepErrors>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
|
14
mailpoet/assets/js/src/automation/i18n.tsx
Normal file
14
mailpoet/assets/js/src/automation/i18n.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { getLocaleData, setLocaleData } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
wp: {
|
||||||
|
i18n: { getLocaleData: typeof getLocaleData };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are using "@wordpress/i18n" from our bundle while WordPress initializes
|
||||||
|
// translation data on the core one — we need to pass the data to our code.
|
||||||
|
export const registerTranslations = () =>
|
||||||
|
setLocaleData(window.wp.i18n.getLocaleData('mailpoet'), 'mailpoet');
|
@ -6,27 +6,48 @@ import {
|
|||||||
FlexItem,
|
FlexItem,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
import { dispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { PlainBodyTitle } from '../../../../editor/components/panel';
|
import { PlainBodyTitle } from '../../../../editor/components/panel';
|
||||||
import { storeName } from '../../../../editor/store';
|
import { storeName } from '../../../../editor/store';
|
||||||
import { DelayTypeOptions } from './types/delayTypes';
|
import { DelayTypeOptions } from './types/delayTypes';
|
||||||
|
|
||||||
export function Edit(): JSX.Element {
|
export function Edit(): JSX.Element {
|
||||||
const { selectedStep } = useSelect(
|
const { selectedStep, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
|
errors: select(storeName).getStepError(
|
||||||
|
select(storeName).getSelectedStep().id,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const errorFields = errors?.fields ?? {};
|
||||||
|
const delayErrorMessage = errorFields?.delay ?? '';
|
||||||
|
const delayTypeErrorMessage = errorFields?.delay_type ?? '';
|
||||||
|
const delayValueInputId = `delay-number-${selectedStep.id}`;
|
||||||
return (
|
return (
|
||||||
<PanelBody opened>
|
<PanelBody opened>
|
||||||
<PlainBodyTitle title="Wait for" />
|
<label htmlFor={delayValueInputId}>
|
||||||
|
<PlainBodyTitle
|
||||||
|
title={
|
||||||
|
// translators: A label for a wait delay time selection form field - time unit follows
|
||||||
|
__('Wait for', 'mailpoet')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<Flex align="top">
|
<Flex align="top">
|
||||||
<FlexItem style={{ flex: '1 1 0' }}>
|
<FlexItem
|
||||||
|
style={{ flex: '1 1 0' }}
|
||||||
|
className={
|
||||||
|
delayErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
>
|
||||||
<TextControl
|
<TextControl
|
||||||
label=""
|
id={delayValueInputId}
|
||||||
|
help={delayErrorMessage}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Number"
|
placeholder={__('Number', 'mailpoet')}
|
||||||
value={(selectedStep.args.delay as string) ?? ''}
|
value={(selectedStep.args.delay as string) ?? ''}
|
||||||
onChange={(rawValue) => {
|
onChange={(rawValue) => {
|
||||||
const value: number =
|
const value: number =
|
||||||
@ -41,9 +62,15 @@ export function Edit(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FlexItem>
|
</FlexItem>
|
||||||
<FlexItem style={{ flex: '1 1 0' }}>
|
<FlexItem
|
||||||
|
style={{ flex: '1 1 0' }}
|
||||||
|
className={
|
||||||
|
delayTypeErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
>
|
||||||
<SelectControl
|
<SelectControl
|
||||||
label=""
|
label=""
|
||||||
|
help={delayTypeErrorMessage}
|
||||||
value={(selectedStep.args.delay_type as string) ?? 'HOURS'}
|
value={(selectedStep.args.delay_type as string) ?? 'HOURS'}
|
||||||
options={DelayTypeOptions}
|
options={DelayTypeOptions}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
import { StepType } from '../../../../editor/store/types';
|
import { StepType } from '../../../../editor/store/types';
|
||||||
@ -14,13 +15,16 @@ const getDelayInformation = (delayTypeValue: string, value: number): string =>
|
|||||||
export const step: StepType = {
|
export const step: StepType = {
|
||||||
key: 'core:delay',
|
key: 'core:delay',
|
||||||
group: 'actions',
|
group: 'actions',
|
||||||
title: 'Delay',
|
title: _x('Delay', 'noun', 'mailpoet'),
|
||||||
foreground: '#7F54B3',
|
foreground: '#7F54B3',
|
||||||
background: '#f7edf7',
|
background: '#f7edf7',
|
||||||
description: 'Wait some time before proceeding with the steps below',
|
description: __(
|
||||||
|
'Wait some time before proceeding with the steps below',
|
||||||
|
'mailpoet',
|
||||||
|
),
|
||||||
subtitle: (data): string => {
|
subtitle: (data): string => {
|
||||||
if (!data.args.delay || !data.args.delay_type) {
|
if (!data.args.delay || !data.args.delay_type) {
|
||||||
return 'Not set up yet.';
|
return __('Not set up yet.', 'mailpoet');
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDelayInformation(
|
return getDelayInformation(
|
||||||
|
@ -1,25 +1,35 @@
|
|||||||
import { SelectControl } from '@wordpress/components';
|
import { SelectControl } from '@wordpress/components';
|
||||||
|
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||||
|
|
||||||
export type DelayTypes = SelectControl.Option & {
|
export type DelayTypes = SelectControl.Option & {
|
||||||
subtitle: (value: number) => string;
|
subtitle: (value: number) => string;
|
||||||
};
|
};
|
||||||
export const DelayTypeOptions: DelayTypes[] = [
|
export const DelayTypeOptions: DelayTypes[] = [
|
||||||
{
|
{
|
||||||
label: 'Hours',
|
label: __('Hours', 'mailpoet'),
|
||||||
subtitle: (value: number) =>
|
subtitle: (value: number) =>
|
||||||
`Wait for ${value} ${value === 1 ? 'hour' : 'hours'}`,
|
sprintf(
|
||||||
|
_n('Wait for %d hour', 'Wait for %d hours', value, 'mailpoet'),
|
||||||
|
value,
|
||||||
|
),
|
||||||
value: 'HOURS',
|
value: 'HOURS',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Days',
|
label: __('Days', 'mailpoet'),
|
||||||
subtitle: (value: number) =>
|
subtitle: (value: number) =>
|
||||||
`Wait for ${value} ${value === 1 ? 'day' : 'days'}`,
|
sprintf(
|
||||||
|
_n('Wait for %d day', 'Wait for %d days', value, 'mailpoet'),
|
||||||
|
value,
|
||||||
|
),
|
||||||
value: 'DAYS',
|
value: 'DAYS',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Weeks',
|
label: __('Weeks', 'mailpoet'),
|
||||||
subtitle: (value: number) =>
|
subtitle: (value: number) =>
|
||||||
`Wait for ${value} ${value === 1 ? 'week' : 'weeks'}`,
|
sprintf(
|
||||||
|
_n('Wait for %d week', 'Wait for %d weeks', value, 'mailpoet'),
|
||||||
|
value,
|
||||||
|
),
|
||||||
value: 'WEEKS',
|
value: 'WEEKS',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,25 +1,52 @@
|
|||||||
import { dispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { plus } from '@wordpress/icons';
|
import { plus } from '@wordpress/icons';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Button } from '../../../components/button';
|
import { Button } from '../../../components/button';
|
||||||
import { storeName } from '../../../../../editor/store';
|
import { storeName } from '../../../../../editor/store';
|
||||||
import { MailPoet } from '../../../../../../mailpoet';
|
import { MailPoet } from '../../../../../../mailpoet';
|
||||||
|
|
||||||
|
const emailPreviewLinkCache = {};
|
||||||
|
const retrievePreviewLink = async (emailId) => {
|
||||||
|
if (
|
||||||
|
emailPreviewLinkCache[emailId] &&
|
||||||
|
emailPreviewLinkCache[emailId].length > 0
|
||||||
|
) {
|
||||||
|
return emailPreviewLinkCache[emailId];
|
||||||
|
}
|
||||||
|
const response = await MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'newsletters',
|
||||||
|
action: 'get',
|
||||||
|
data: {
|
||||||
|
id: emailId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
emailPreviewLinkCache[emailId] = response?.meta?.preview_url ?? '';
|
||||||
|
return emailPreviewLinkCache[emailId];
|
||||||
|
};
|
||||||
|
|
||||||
export function EditNewsletter(): JSX.Element {
|
export function EditNewsletter(): JSX.Element {
|
||||||
const [redirectToTemplateSelection, setRedirectToTemplateSelection] =
|
const [redirectToTemplateSelection, setRedirectToTemplateSelection] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false);
|
||||||
|
|
||||||
const { selectedStep, workflowId, workflowSaved } = useSelect(
|
const { selectedStep, workflowId, workflowSaved, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
workflowId: select(storeName).getWorkflowData().id,
|
workflowId: select(storeName).getWorkflowData().id,
|
||||||
workflowSaved: select(storeName).getWorkflowSaved(),
|
workflowSaved: select(storeName).getWorkflowSaved(),
|
||||||
|
errors: select(storeName).getStepError(
|
||||||
|
select(storeName).getSelectedStep().id,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailId = selectedStep?.args?.email_id as number | undefined;
|
const emailId = selectedStep?.args?.email_id as number | undefined;
|
||||||
const workflowStepId = selectedStep.id;
|
const workflowStepId = selectedStep.id;
|
||||||
|
const errorFields = errors?.fields ?? {};
|
||||||
|
const emailIdError = errorFields?.email_id ?? '';
|
||||||
|
|
||||||
const createEmail = useCallback(async () => {
|
const createEmail = useCallback(async () => {
|
||||||
setRedirectToTemplateSelection(true);
|
setRedirectToTemplateSelection(true);
|
||||||
@ -56,16 +83,26 @@ export function EditNewsletter(): JSX.Element {
|
|||||||
|
|
||||||
if (!emailId || redirectToTemplateSelection) {
|
if (!emailId || redirectToTemplateSelection) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<div className={emailIdError ? 'mailpoet-automation-field__error' : ''}>
|
||||||
variant="sidebar-primary"
|
<Button
|
||||||
centered
|
variant="sidebar-primary"
|
||||||
icon={plus}
|
centered
|
||||||
onClick={createEmail}
|
icon={plus}
|
||||||
isBusy={redirectToTemplateSelection}
|
onClick={createEmail}
|
||||||
disabled={redirectToTemplateSelection}
|
isBusy={redirectToTemplateSelection}
|
||||||
>
|
disabled={redirectToTemplateSelection}
|
||||||
Design email
|
>
|
||||||
</Button>
|
{__('Design email', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
{emailIdError && (
|
||||||
|
<span className="mailpoet-automation-field-message">
|
||||||
|
{__(
|
||||||
|
'You need to design an email before you can activate the workflow',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +115,21 @@ export function EditNewsletter(): JSX.Element {
|
|||||||
selectedStep.args.email_id as string
|
selectedStep.args.email_id as string
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Edit content
|
{__('Edit content', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" centered>
|
<Button
|
||||||
Preview
|
variant="secondary"
|
||||||
|
centered
|
||||||
|
isBusy={fetchingPreviewLink}
|
||||||
|
disabled={fetchingPreviewLink}
|
||||||
|
onClick={async () => {
|
||||||
|
setFetchingPreviewLink(true);
|
||||||
|
const link = await retrievePreviewLink(emailId);
|
||||||
|
window.open(link as string, '_blank');
|
||||||
|
setFetchingPreviewLink(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Preview', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ComponentProps } from 'react';
|
import { ComponentProps } from 'react';
|
||||||
import { PanelBody, TextareaControl, TextControl } from '@wordpress/components';
|
import { PanelBody, TextareaControl, TextControl } from '@wordpress/components';
|
||||||
import { dispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { ShortcodeHelpText } from './shortcode_help_text';
|
import { ShortcodeHelpText } from './shortcode_help_text';
|
||||||
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
||||||
import { storeName } from '../../../../../editor/store';
|
import { storeName } from '../../../../../editor/store';
|
||||||
@ -31,14 +32,21 @@ function SingleLineTextareaControl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function EmailPanel(): JSX.Element {
|
export function EmailPanel(): JSX.Element {
|
||||||
const { selectedStep, selectedStepType } = useSelect(
|
const { selectedStep, selectedStepType, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
selectedStepType: select(storeName).getSelectedStepType(),
|
selectedStepType: select(storeName).getSelectedStepType(),
|
||||||
|
errors: select(storeName).getStepError(
|
||||||
|
select(storeName).getSelectedStep().id,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const errorFields = errors?.fields ?? {};
|
||||||
|
const senderNameErrorMessage = errorFields?.sender_name ?? '';
|
||||||
|
const senderAddressErrorMessage = errorFields?.sender_address ?? '';
|
||||||
|
const subjectErrorMessage = errorFields?.subject ?? '';
|
||||||
return (
|
return (
|
||||||
<PanelBody opened>
|
<PanelBody opened>
|
||||||
<StepName
|
<StepName
|
||||||
@ -49,8 +57,15 @@ export function EmailPanel(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextControl
|
<TextControl
|
||||||
label="“From” name"
|
className={
|
||||||
placeholder="John Doe"
|
senderNameErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
help={senderNameErrorMessage}
|
||||||
|
label={__('"From" name', 'mailpoet')}
|
||||||
|
placeholder={
|
||||||
|
// translators: A placeholder for a person's name
|
||||||
|
__('John Doe', 'mailpoet')
|
||||||
|
}
|
||||||
value={(selectedStep.args.sender_name as string) ?? ''}
|
value={(selectedStep.args.sender_name as string) ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
@ -61,9 +76,16 @@ export function EmailPanel(): JSX.Element {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TextControl
|
<TextControl
|
||||||
|
className={
|
||||||
|
senderAddressErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
help={senderAddressErrorMessage}
|
||||||
type="email"
|
type="email"
|
||||||
label="“From” email address"
|
label={__('"From" email address', 'mailpoet')}
|
||||||
placeholder="you@domain.com"
|
placeholder={
|
||||||
|
// translators: A placeholder for an email
|
||||||
|
__('you@domain.com', 'mailpoet')
|
||||||
|
}
|
||||||
value={(selectedStep.args.sender_address as string) ?? ''}
|
value={(selectedStep.args.sender_address as string) ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
@ -74,17 +96,25 @@ export function EmailPanel(): JSX.Element {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SingleLineTextareaControl
|
<SingleLineTextareaControl
|
||||||
label="Subject"
|
className={
|
||||||
placeholder="Type in subject…"
|
subjectErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
label={__('Subject', 'mailpoet')}
|
||||||
|
placeholder={__('Type in subject…', 'mailpoet')}
|
||||||
value={(selectedStep.args.subject as string) ?? ''}
|
value={(selectedStep.args.subject as string) ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(selectedStep.id, 'subject', value)
|
dispatch(storeName).updateStepArgs(selectedStep.id, 'subject', value)
|
||||||
}
|
}
|
||||||
help={<ShortcodeHelpText />}
|
help={
|
||||||
|
<>
|
||||||
|
{`${subjectErrorMessage} `}
|
||||||
|
<ShortcodeHelpText />
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<SingleLineTextareaControl
|
<SingleLineTextareaControl
|
||||||
label="Preheader"
|
label={__('Preheader', 'mailpoet')}
|
||||||
placeholder="Type in preheader…"
|
placeholder={__('Type in preheader…', 'mailpoet')}
|
||||||
value={(selectedStep.args.preheader as string) ?? ''}
|
value={(selectedStep.args.preheader as string) ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
@ -97,7 +127,7 @@ export function EmailPanel(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mailpoet-automation-email-content-separator" />
|
<div className="mailpoet-automation-email-content-separator" />
|
||||||
<PlainBodyTitle title="Email" />
|
<PlainBodyTitle title={__('Email', 'mailpoet')} />
|
||||||
<EditNewsletter />
|
<EditNewsletter />
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
);
|
);
|
||||||
|
@ -31,9 +31,9 @@ export function GoogleAnalyticsPanel(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelBody title="Google analytics" initialOpen={false}>
|
<PanelBody title={__('Google analytics', 'mailpoet')} initialOpen={false}>
|
||||||
<ToggleControl
|
<ToggleControl
|
||||||
label="Enable custom GA tracking"
|
label={__('Enable custom GA tracking', 'mailpoet')}
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
|
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
|
||||||
import { dispatch, useSelect } from '@wordpress/data';
|
import { dispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { storeName } from '../../../../../editor/store';
|
import { storeName } from '../../../../../editor/store';
|
||||||
|
|
||||||
export function ReplyToPanel(): JSX.Element {
|
export function ReplyToPanel(): JSX.Element {
|
||||||
const { selectedStep } = useSelect(
|
const { selectedStep, errors } = useSelect(
|
||||||
(select) => ({
|
(select) => ({
|
||||||
selectedStep: select(storeName).getSelectedStep(),
|
selectedStep: select(storeName).getSelectedStep(),
|
||||||
|
errors: select(storeName).getStepError(
|
||||||
|
select(storeName).getSelectedStep().id,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@ -18,10 +22,16 @@ export function ReplyToPanel(): JSX.Element {
|
|||||||
const enabled =
|
const enabled =
|
||||||
typeof replyToName !== 'undefined' || typeof replyToAddress !== 'undefined';
|
typeof replyToName !== 'undefined' || typeof replyToAddress !== 'undefined';
|
||||||
|
|
||||||
|
const errorFields = errors?.fields ?? {};
|
||||||
|
const replyToNameError = errorFields?.reply_to_name ?? '';
|
||||||
|
const replyToAddressError = errorFields?.reply_to_address ?? '';
|
||||||
return (
|
return (
|
||||||
<PanelBody title="Reply to" initialOpen={false}>
|
<PanelBody title={__('Reply to', 'mailpoet')} initialOpen={false}>
|
||||||
<ToggleControl
|
<ToggleControl
|
||||||
label="Use different email address for getting replies to the email"
|
label={__(
|
||||||
|
'Use different email address for getting replies to the email',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
@ -40,8 +50,15 @@ export function ReplyToPanel(): JSX.Element {
|
|||||||
{enabled && (
|
{enabled && (
|
||||||
<>
|
<>
|
||||||
<TextControl
|
<TextControl
|
||||||
label="“Reply to” name"
|
className={
|
||||||
placeholder="John Doe"
|
replyToNameError ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
help={replyToNameError}
|
||||||
|
label={__('"Reply to" name', 'mailpoet')}
|
||||||
|
placeholder={
|
||||||
|
// translators: A placeholder for a person's name
|
||||||
|
__('John Doe', 'mailpoet')
|
||||||
|
}
|
||||||
value={replyToName ?? ''}
|
value={replyToName ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
@ -53,9 +70,16 @@ export function ReplyToPanel(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<TextControl
|
<TextControl
|
||||||
|
className={
|
||||||
|
replyToAddressError ? 'mailpoet-automation-field__error' : ''
|
||||||
|
}
|
||||||
|
help={replyToAddressError}
|
||||||
type="email"
|
type="email"
|
||||||
label="“Reply to” email address"
|
label={__('"Reply to" email address', 'mailpoet')}
|
||||||
placeholder="you@domain.com"
|
placeholder={
|
||||||
|
// translators: A placeholder for an email
|
||||||
|
__('you@domain.com', 'mailpoet')
|
||||||
|
}
|
||||||
value={replyToAddress ?? ''}
|
value={replyToAddress ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(storeName).updateStepArgs(
|
dispatch(storeName).updateStepArgs(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
export function ShortcodeHelpText(): JSX.Element {
|
export function ShortcodeHelpText(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<span className="mailpoet-shortcode-selector">
|
<span className="mailpoet-shortcode-selector">
|
||||||
@ -8,7 +10,7 @@ export function ShortcodeHelpText(): JSX.Element {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
data-beacon-article="59d662ef042863379ddc6faa"
|
data-beacon-article="59d662ef042863379ddc6faa"
|
||||||
>
|
>
|
||||||
MailPoet shortcodes
|
{__('MailPoet shortcodes', 'mailpoet')}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ComponentProps, ComponentType, useEffect, useState } from 'react';
|
import { ComponentProps, ComponentType, useEffect, useState } from 'react';
|
||||||
import { Spinner as WpSpinner } from '@wordpress/components';
|
import { Spinner as WpSpinner } from '@wordpress/components';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { MailPoetAjax } from '../../../../../../ajax';
|
import { MailPoetAjax } from '../../../../../../ajax';
|
||||||
|
|
||||||
// @types/wordpress__components don't define "className", which is supported
|
// @types/wordpress__components don't define "className", which is supported
|
||||||
@ -39,7 +40,7 @@ export function Thumbnail({ emailId }: Props): JSX.Element {
|
|||||||
<img
|
<img
|
||||||
className="mailpoet-automation-thumbnail-image"
|
className="mailpoet-automation-thumbnail-image"
|
||||||
src={thumbnailUrl}
|
src={thumbnailUrl}
|
||||||
alt="Email thumbnail"
|
alt={__('Email thumbnail', 'mailpoet')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
import { StepType } from '../../../../editor/store/types';
|
import { StepType } from '../../../../editor/store/types';
|
||||||
@ -5,9 +6,10 @@ import { StepType } from '../../../../editor/store/types';
|
|||||||
export const step: StepType = {
|
export const step: StepType = {
|
||||||
key: 'mailpoet:send-email',
|
key: 'mailpoet:send-email',
|
||||||
group: 'actions',
|
group: 'actions',
|
||||||
title: 'Send email',
|
title: __('Send email', 'mailpoet'),
|
||||||
description: 'An email will be sent to subscriber',
|
description: __('An email will be sent to subscriber', 'mailpoet'),
|
||||||
subtitle: (data) => (data.args.name as string) ?? 'Send email',
|
subtitle: (data) =>
|
||||||
|
(data.args.name as string) ?? __('Send email', 'mailpoet'),
|
||||||
foreground: '#996800',
|
foreground: '#996800',
|
||||||
background: '#FCF9E8',
|
background: '#FCF9E8',
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { commentAuthorAvatar } from '@wordpress/icons';
|
import { commentAuthorAvatar } from '@wordpress/icons';
|
||||||
import { StepType } from '../../../../editor/store';
|
import { StepType } from '../../../../editor/store';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
@ -13,7 +13,7 @@ export const step: StepType = {
|
|||||||
'Starts the automation when a new subscriber is added to MailPoet.',
|
'Starts the automation when a new subscriber is added to MailPoet.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
subtitle: () => __('Trigger', 'mailpoet'),
|
subtitle: () => _x('Trigger', 'noun', 'mailpoet'),
|
||||||
icon: () => (
|
icon: () => (
|
||||||
<div style={{ width: '100%', height: '100%', scale: '1.4' }}>
|
<div style={{ width: '100%', height: '100%', scale: '1.4' }}>
|
||||||
{commentAuthorAvatar}
|
{commentAuthorAvatar}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
import { wordpress } from '@wordpress/icons';
|
import { wordpress } from '@wordpress/icons';
|
||||||
import { StepType } from '../../../../editor/store';
|
import { StepType } from '../../../../editor/store';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
@ -13,7 +13,7 @@ export const step: StepType = {
|
|||||||
'Starts the automation when a new user registered in WordPress.',
|
'Starts the automation when a new user registered in WordPress.',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
),
|
),
|
||||||
subtitle: () => __('Trigger', 'mailpoet'),
|
subtitle: () => _x('Trigger', 'noun', 'mailpoet'),
|
||||||
icon: () => (
|
icon: () => (
|
||||||
<div style={{ width: '100%', height: '100%', scale: '1.12' }}>
|
<div style={{ width: '100%', height: '100%', scale: '1.12' }}>
|
||||||
{wordpress}
|
{wordpress}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import apiFetch, { APIFetchOptions } from '@wordpress/api-fetch';
|
||||||
|
import { dispatch, StoreDescriptor } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import { ApiError } from '../api';
|
||||||
|
|
||||||
|
export const registerApiErrorHandler = (): void =>
|
||||||
|
apiFetch.use(
|
||||||
|
async (
|
||||||
|
options: APIFetchOptions,
|
||||||
|
next: (nextOptions: APIFetchOptions) => Promise<unknown>,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const result = await next(options);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
const errorObject = error as ApiError;
|
||||||
|
const status = errorObject.data?.status;
|
||||||
|
|
||||||
|
if (status && status >= 400 && status < 500) {
|
||||||
|
const message = errorObject.message;
|
||||||
|
void dispatch(noticesStore as StoreDescriptor).createErrorNotice(
|
||||||
|
message ?? __('An unknown error occurred.', 'mailpoet'),
|
||||||
|
{ explicitDismiss: true },
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch(noticesStore as StoreDescriptor).createErrorNotice(
|
||||||
|
__('An unknown error occurred.', 'mailpoet'),
|
||||||
|
{ explicitDismiss: true },
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Button } from '@wordpress/components';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
|
import { Workflow } from '../../workflow';
|
||||||
|
import { MailPoet } from '../../../../mailpoet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflow: Workflow;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EditWorkflow({ workflow, label }: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
href={addQueryArgs(MailPoet.urls.automationEditor, { id: workflow.id })}
|
||||||
|
>
|
||||||
|
{label ?? __('Edit', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './edit-workflow';
|
||||||
|
export * from './undo-trash';
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Button } from '@wordpress/components';
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { storeName } from '../../store/constants';
|
||||||
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflow: Workflow;
|
||||||
|
previousStatus: WorkflowStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UndoTrashButton({
|
||||||
|
workflow,
|
||||||
|
previousStatus,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const { restoreWorkflow } = useDispatch(storeName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={() => restoreWorkflow(workflow, previousStatus)}
|
||||||
|
>
|
||||||
|
{__('Undo', 'mailpoet')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Fragment } from 'react';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { DropdownMenu } from '@wordpress/components';
|
||||||
|
import { moreVertical } from '@wordpress/icons';
|
||||||
|
import {
|
||||||
|
useDeleteButton,
|
||||||
|
useDuplicateButton,
|
||||||
|
useRestoreButton,
|
||||||
|
useTrashButton,
|
||||||
|
} from '../menu';
|
||||||
|
import { Workflow } from '../../workflow';
|
||||||
|
import { EditWorkflow } from '../actions';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflow: Workflow;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Actions({ workflow }: Props): JSX.Element {
|
||||||
|
// Menu items are using custom hooks because the "DropdownMenu" component uses the "controls"
|
||||||
|
// attribute rather than child components, but we need to render modal confirmation dialogs.
|
||||||
|
const duplicate = useDuplicateButton(workflow);
|
||||||
|
const trash = useTrashButton(workflow);
|
||||||
|
const restore = useRestoreButton(workflow);
|
||||||
|
const del = useDeleteButton(workflow);
|
||||||
|
|
||||||
|
const menuItems = [duplicate, trash, restore, del].filter((item) => item);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mailpoet-automation-listing-cell-actions">
|
||||||
|
<EditWorkflow workflow={workflow} />
|
||||||
|
{menuItems.map(({ control, slot }) => (
|
||||||
|
<Fragment key={control.title}>{slot}</Fragment>
|
||||||
|
))}
|
||||||
|
<DropdownMenu
|
||||||
|
className="mailpoet-automation-listing-more-button"
|
||||||
|
label={__('More', 'mailpoet')}
|
||||||
|
icon={moreVertical}
|
||||||
|
controls={menuItems.map(({ control }) => control)}
|
||||||
|
popoverProps={{ position: 'bottom left' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import { Workflow } from '../../workflow';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
workflow: Workflow;
|
|
||||||
label?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Edit({ workflow, label }: Props): JSX.Element {
|
|
||||||
return (
|
|
||||||
<a href={`admin.php?page=mailpoet-automation-editor&id=${workflow.id}`}>
|
|
||||||
{label ?? __('Edit', 'mailpoet')}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
export * from './edit';
|
export * from './actions';
|
||||||
export * from './more';
|
|
||||||
export * from './name';
|
export * from './name';
|
||||||
export * from './status';
|
export * from './status';
|
||||||
export * from './subscribers';
|
export * from './subscribers';
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { EllipsisMenu, MenuItem } from '@woocommerce/components/build';
|
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import { Workflow } from '../../workflow';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
workflow: Workflow;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function More({ workflow }: Props): JSX.Element {
|
|
||||||
return (
|
|
||||||
<EllipsisMenu
|
|
||||||
label={`Actions for ${workflow.name}`}
|
|
||||||
renderContent={() => (
|
|
||||||
<div>
|
|
||||||
<MenuItem onInvoke={() => {}}>
|
|
||||||
<p>{__('Duplicate', 'mailpoet')}</p>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onInvoke={() => {}}>
|
|
||||||
<p>{__('Move to trash', 'mailpoet')}</p>
|
|
||||||
</MenuItem>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Edit } from './edit';
|
import { EditWorkflow } from '../actions';
|
||||||
import { Workflow } from '../../workflow';
|
import { Workflow } from '../../workflow';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -6,5 +6,5 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Name({ workflow }: Props): JSX.Element {
|
export function Name({ workflow }: Props): JSX.Element {
|
||||||
return <Edit workflow={workflow} label={workflow.name} />;
|
return <EditWorkflow workflow={workflow} label={workflow.name} />;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { ToggleControl } from '@wordpress/components';
|
|
||||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -8,14 +6,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Status({ workflow }: Props): JSX.Element {
|
export function Status({ workflow }: Props): JSX.Element {
|
||||||
const [isActive, setIsActive] = useState(workflow.status === 'active');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-automation-listing-cell-status">
|
<div className="mailpoet-automation-listing-cell-status">
|
||||||
<ToggleControl
|
|
||||||
checked={isActive}
|
|
||||||
onChange={(active) => setIsActive(active)}
|
|
||||||
/>
|
|
||||||
{workflow.status === WorkflowStatus.ACTIVE
|
{workflow.status === WorkflowStatus.ACTIVE
|
||||||
? __('Active', 'mailpoet')
|
? __('Active', 'mailpoet')
|
||||||
: __('Not active', 'mailpoet')}
|
: __('Not active', 'mailpoet')}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { __ } from '@wordpress/i18n';
|
import { _x } from '@wordpress/i18n';
|
||||||
import { Workflow } from '../../workflow';
|
import { Workflow } from '../../workflow';
|
||||||
|
import { Statistics } from '../../../components/statistics';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
@ -7,25 +8,28 @@ type Props = {
|
|||||||
|
|
||||||
export function Subscribers({ workflow }: Props): JSX.Element {
|
export function Subscribers({ workflow }: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<ul className="mailpoet-automation-stats">
|
<Statistics
|
||||||
<li className="mailpoet-automation-stats-item">
|
labelPosition="after"
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.entered)}
|
items={[
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Entered', 'mailpoet')}
|
key: 'entered',
|
||||||
</span>
|
// translators: Total number of subscribers who entered an automation workflow
|
||||||
</li>
|
label: _x('Entered', 'automation stats', 'mailpoet'),
|
||||||
<li className="mailpoet-automation-stats-item">
|
value: workflow.stats.totals.entered,
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.in_progress)}
|
},
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Processing', 'mailpoet')}
|
key: 'processing',
|
||||||
</span>
|
// translators: Total number of subscribers who are being processed in an automation workflow
|
||||||
</li>
|
label: _x('Processing', 'automation stats', 'mailpoet'),
|
||||||
<li className="mailpoet-automation-stats-item">
|
value: workflow.stats.totals.in_progress,
|
||||||
{new Intl.NumberFormat().format(workflow.stats.totals.exited)}
|
},
|
||||||
<span className="mailpoet-automation-stats-label">
|
{
|
||||||
{__('Exited', 'mailpoet')}
|
key: 'exited',
|
||||||
</span>
|
// translators: Total number of subscribers who exited an automation workflow, no matter the result
|
||||||
</li>
|
label: _x('Exited', 'automation stats', 'mailpoet'),
|
||||||
</ul>
|
value: workflow.stats.totals.exited,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { Item } from './item';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
|
export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
||||||
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
const { deleteWorkflow } = useDispatch(storeName);
|
||||||
|
|
||||||
|
if (workflow.status !== WorkflowStatus.TRASH) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'delete',
|
||||||
|
control: {
|
||||||
|
title: __('Delete permanently', 'mailpoet'),
|
||||||
|
icon: null,
|
||||||
|
onClick: () => setShowDialog(true),
|
||||||
|
},
|
||||||
|
slot: (
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={showDialog}
|
||||||
|
title={__('Permanently delete automation', 'mailpoet')}
|
||||||
|
confirmButtonText={__('Yes, permanently delete', 'mailpoet')}
|
||||||
|
__experimentalHideHeader={false}
|
||||||
|
onConfirm={() => deleteWorkflow(workflow)}
|
||||||
|
onCancel={() => setShowDialog(false)}
|
||||||
|
>
|
||||||
|
{sprintf(
|
||||||
|
// translators: %s is the workflow name
|
||||||
|
__(
|
||||||
|
'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!',
|
||||||
|
'mailpoet',
|
||||||
|
),
|
||||||
|
workflow.name,
|
||||||
|
)}
|
||||||
|
</ConfirmDialog>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Item } from './item';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
|
export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
|
||||||
|
const { duplicateWorkflow } = useDispatch(storeName);
|
||||||
|
|
||||||
|
if (workflow.status === WorkflowStatus.TRASH) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'duplicate',
|
||||||
|
control: {
|
||||||
|
title: __('Duplicate', 'mailpoet'),
|
||||||
|
icon: null,
|
||||||
|
onClick: () => duplicateWorkflow(workflow),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
export * from './delete';
|
||||||
|
export * from './duplicate';
|
||||||
|
export * from './restore';
|
||||||
|
export * from './trash';
|
@ -0,0 +1,9 @@
|
|||||||
|
import { DropdownMenu } from '@wordpress/components';
|
||||||
|
|
||||||
|
import Control = DropdownMenu.Control;
|
||||||
|
|
||||||
|
export type Item = {
|
||||||
|
key: string;
|
||||||
|
control: Control;
|
||||||
|
slot?: JSX.Element;
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Item } from './item';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
|
export const useRestoreButton = (workflow: Workflow): Item | undefined => {
|
||||||
|
const { restoreWorkflow } = useDispatch(storeName);
|
||||||
|
|
||||||
|
if (workflow.status !== WorkflowStatus.TRASH) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'restore',
|
||||||
|
control: {
|
||||||
|
title: __('Restore', 'mailpoet'),
|
||||||
|
icon: null,
|
||||||
|
onClick: () => restoreWorkflow(workflow, WorkflowStatus.DRAFT),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { __, _x, sprintf } from '@wordpress/i18n';
|
||||||
|
import { Item } from './item';
|
||||||
|
import { storeName } from '../../store';
|
||||||
|
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||||
|
|
||||||
|
export const useTrashButton = (workflow: Workflow): Item | undefined => {
|
||||||
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
const { trashWorkflow } = useDispatch(storeName);
|
||||||
|
|
||||||
|
if (workflow.status === WorkflowStatus.TRASH) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'trash',
|
||||||
|
control: {
|
||||||
|
title: _x('Trash', 'verb', 'mailpoet'),
|
||||||
|
icon: null,
|
||||||
|
onClick: () => setShowDialog(true),
|
||||||
|
},
|
||||||
|
slot: (
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={showDialog}
|
||||||
|
title={__('Trash workflow', 'mailpoet')}
|
||||||
|
confirmButtonText={__('Yes, move to trash', 'mailpoet')}
|
||||||
|
__experimentalHideHeader={false}
|
||||||
|
onConfirm={() => trashWorkflow(workflow)}
|
||||||
|
onCancel={() => setShowDialog(false)}
|
||||||
|
>
|
||||||
|
{sprintf(
|
||||||
|
// translators: %s is the workflow name
|
||||||
|
__(
|
||||||
|
'Are you sure you want to move the workflow "%s" to the Trash?',
|
||||||
|
'mailpoet',
|
||||||
|
),
|
||||||
|
workflow.name,
|
||||||
|
)}
|
||||||
|
</ConfirmDialog>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
import { StoreDescriptor, useSelect, useDispatch } from '@wordpress/data';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import { Notice } from '../../../../notices/notice';
|
||||||
|
|
||||||
|
export function Notices(): JSX.Element {
|
||||||
|
const { notices } = useSelect(
|
||||||
|
(select) => ({
|
||||||
|
notices: select(noticesStore as StoreDescriptor).getNotices(),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { removeNotice } = useDispatch(noticesStore as StoreDescriptor);
|
||||||
|
|
||||||
|
const dismissibleNotices = notices.filter(
|
||||||
|
({ isDismissible, type }) => isDismissible && type === 'default',
|
||||||
|
);
|
||||||
|
|
||||||
|
const nonDismissibleNotices = notices.filter(
|
||||||
|
({ isDismissible, type }) => !isDismissible && type === 'default',
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{nonDismissibleNotices
|
||||||
|
.reverse()
|
||||||
|
.map(({ id, status, content, __unstableHTML }) => (
|
||||||
|
<Notice key={id} renderInPlace type={status} timeout={false}>
|
||||||
|
{__unstableHTML ?? <p>{content}</p>}
|
||||||
|
</Notice>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{dismissibleNotices
|
||||||
|
.reverse()
|
||||||
|
.map(({ id, status, content, __unstableHTML }) => (
|
||||||
|
<Notice
|
||||||
|
key={id}
|
||||||
|
type={status}
|
||||||
|
renderInPlace
|
||||||
|
timeout={false}
|
||||||
|
closable
|
||||||
|
onClose={removeNotice}
|
||||||
|
>
|
||||||
|
{__unstableHTML ?? <p>{content}</p>}
|
||||||
|
</Notice>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Workflow } from './workflow';
|
import { Workflow } from './workflow';
|
||||||
import { Edit, More, Name, Status, Subscribers } from './components/cells';
|
import { Actions, Name, Status, Subscribers } from './components/cells';
|
||||||
|
|
||||||
export function getRow(workflow: Workflow): object[] {
|
export function getRow(workflow: Workflow): object[] {
|
||||||
return [
|
return [
|
||||||
@ -21,12 +21,7 @@ export function getRow(workflow: Workflow): object[] {
|
|||||||
{
|
{
|
||||||
id: workflow.id,
|
id: workflow.id,
|
||||||
value: null,
|
value: null,
|
||||||
display: <Edit workflow={workflow} />,
|
display: <Actions workflow={workflow} />,
|
||||||
},
|
|
||||||
{
|
|
||||||
id: workflow.id,
|
|
||||||
value: null,
|
|
||||||
display: <More workflow={workflow} />,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,37 @@
|
|||||||
import { Search, TableCard } from '@woocommerce/components/build';
|
import { Search, TableCard } from '@woocommerce/components/build';
|
||||||
import { TabPanel } from '@wordpress/components';
|
import { TabPanel } from '@wordpress/components';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { __, _x } from '@wordpress/i18n';
|
||||||
|
import { useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { getRow } from './get-row';
|
import { getRow } from './get-row';
|
||||||
|
import { storeName } from './store/constants';
|
||||||
import { Workflow, WorkflowStatus } from './workflow';
|
import { Workflow, WorkflowStatus } from './workflow';
|
||||||
|
|
||||||
type Props = {
|
const tabConfig = [
|
||||||
workflows: Workflow[];
|
|
||||||
loading: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterTabs = [
|
|
||||||
{
|
{
|
||||||
name: 'all',
|
name: 'all',
|
||||||
title: 'All',
|
title: __('All', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-all',
|
className: 'mailpoet-tab-all',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.ACTIVE,
|
name: WorkflowStatus.ACTIVE,
|
||||||
title: 'Active',
|
title: __('Active', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-active',
|
className: 'mailpoet-tab-active',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.INACTIVE,
|
name: WorkflowStatus.INACTIVE,
|
||||||
title: 'Inactive',
|
title: __('Inactive', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-inactive',
|
className: 'mailpoet-tab-inactive',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.DRAFT,
|
name: WorkflowStatus.DRAFT,
|
||||||
title: 'Draft',
|
title: _x('Draft', 'noun', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-draft',
|
className: 'mailpoet-tab-draft',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: WorkflowStatus.TRASH,
|
name: WorkflowStatus.TRASH,
|
||||||
title: 'Trash',
|
title: _x('Trash', 'noun', 'mailpoet'),
|
||||||
className: 'mailpoet-tab-trash',
|
className: 'mailpoet-tab-trash',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
@ -43,11 +40,10 @@ const tableHeaders = [
|
|||||||
{ key: 'name', label: __('Name', 'mailpoet') },
|
{ key: 'name', label: __('Name', 'mailpoet') },
|
||||||
{ key: 'subscribers', label: __('Subscribers', 'mailpoet') },
|
{ key: 'subscribers', label: __('Subscribers', 'mailpoet') },
|
||||||
{ key: 'status', label: __('Status', 'mailpoet') },
|
{ key: 'status', label: __('Status', 'mailpoet') },
|
||||||
{ key: 'edit' },
|
{ key: 'actions' },
|
||||||
{ key: 'more' },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
export function AutomationListing(): JSX.Element {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const pageSearch = useMemo(
|
const pageSearch = useMemo(
|
||||||
@ -55,6 +51,22 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
|||||||
[location],
|
[location],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const workflows = useSelect((select) => select(storeName).getWorkflows());
|
||||||
|
const { loadWorkflows } = useDispatch(storeName);
|
||||||
|
|
||||||
|
const status = pageSearch.get('status');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadWorkflows();
|
||||||
|
}, [loadWorkflows]);
|
||||||
|
|
||||||
|
// focus tab button on status change (needed due to the force re-mount below)
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (status) {
|
||||||
|
document.querySelector<HTMLElement>(`.mailpoet-tab-${status}`)?.focus();
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
const updateUrlSearchString = useCallback(
|
const updateUrlSearchString = useCallback(
|
||||||
(search: Record<string, string>) => {
|
(search: Record<string, string>) => {
|
||||||
const newSearch = new URLSearchParams({
|
const newSearch = new URLSearchParams({
|
||||||
@ -75,10 +87,8 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const groupedWorkflows = useMemo<Record<string, Workflow[]>>(() => {
|
const groupedWorkflows = useMemo<Record<string, Workflow[]>>(() => {
|
||||||
const grouped = {
|
const grouped = { all: [] };
|
||||||
all: [],
|
(workflows ?? []).forEach((workflow) => {
|
||||||
};
|
|
||||||
workflows.forEach((workflow) => {
|
|
||||||
if (!grouped[workflow.status]) {
|
if (!grouped[workflow.status]) {
|
||||||
grouped[workflow.status] = [];
|
grouped[workflow.status] = [];
|
||||||
}
|
}
|
||||||
@ -92,30 +102,27 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
|||||||
|
|
||||||
const tabs = useMemo(
|
const tabs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
filterTabs.map((filterTab) => {
|
tabConfig.map((tab) => {
|
||||||
const count = (groupedWorkflows[filterTab.name] || []).length;
|
const count = (groupedWorkflows[tab.name] ?? []).length;
|
||||||
return {
|
return {
|
||||||
name: filterTab.name,
|
name: tab.name,
|
||||||
title:
|
title: (
|
||||||
count > 0 ? (
|
<>
|
||||||
<>
|
<span>{tab.title}</span>
|
||||||
<span>{filterTab.title}</span>
|
{count > 0 && <span className="count">{count}</span>}
|
||||||
<span className="count">{count}</span>
|
</>
|
||||||
</>
|
) as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- typed as string but supports JSX
|
||||||
) : (
|
className: tab.className,
|
||||||
<span>{filterTab.title}</span>
|
|
||||||
),
|
|
||||||
className: filterTab.className,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[groupedWorkflows],
|
[groupedWorkflows],
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabRenderer = useCallback(
|
const renderTabs = useCallback(
|
||||||
(tab) => {
|
(tab) => {
|
||||||
const filteredWorkflows: Workflow[] = groupedWorkflows[tab.name] ?? [];
|
const filteredWorkflows: Workflow[] = groupedWorkflows[tab.name] ?? [];
|
||||||
const rowsPerPage = parseInt(pageSearch.get('per_page') || '25', 10);
|
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
|
||||||
const currentPage = parseInt(pageSearch.get('paged') || '1', 10);
|
const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10);
|
||||||
const start = (currentPage - 1) * rowsPerPage;
|
const start = (currentPage - 1) * rowsPerPage;
|
||||||
const rows = filteredWorkflows
|
const rows = filteredWorkflows
|
||||||
.map((workflow) => getRow(workflow))
|
.map((workflow) => getRow(workflow))
|
||||||
@ -125,7 +132,7 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
|||||||
<TableCard
|
<TableCard
|
||||||
className="mailpoet-automation-listing"
|
className="mailpoet-automation-listing"
|
||||||
title=""
|
title=""
|
||||||
isLoading={loading}
|
isLoading={!workflows}
|
||||||
headers={tableHeaders}
|
headers={tableHeaders}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
rowKey={(_, i) => filteredWorkflows[i].id}
|
rowKey={(_, i) => filteredWorkflows[i].id}
|
||||||
@ -143,40 +150,30 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
|
|||||||
allowFreeTextSearch
|
allowFreeTextSearch
|
||||||
inlineTags
|
inlineTags
|
||||||
key="search"
|
key="search"
|
||||||
// onChange={ onSearchChange }
|
|
||||||
// placeholder={
|
|
||||||
// labels.placeholder ||
|
|
||||||
// __( 'Search by item name', 'woocommerce' )
|
|
||||||
// }
|
|
||||||
// selected={ searchedLabels }
|
|
||||||
type="custom"
|
type="custom"
|
||||||
disabled={loading || workflows.length === 0}
|
disabled={!workflows}
|
||||||
autocompleter={{}}
|
autocompleter={{}}
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[workflows, groupedWorkflows, pageSearch, loading, updateUrlSearchString],
|
[workflows, groupedWorkflows, pageSearch, updateUrlSearchString],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabPanel
|
<TabPanel
|
||||||
className="mailpoet-filter-tab-panel"
|
className="mailpoet-filter-tab-panel"
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore - the Tab type actually expects a string for titles but won't render HTML,
|
|
||||||
// making it very difficult to style the count badges. It seems to be compatible with JSX
|
|
||||||
// elements, however.
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
onSelect={(tabName) => {
|
onSelect={(tabName) => {
|
||||||
if (pageSearch.get('status') !== tabName) {
|
if (status !== tabName) {
|
||||||
updateUrlSearchString({ status: tabName });
|
updateUrlSearchString({ status: tabName });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
initialTabName={pageSearch.get('status') || 'all'}
|
initialTabName={status ?? 'all'}
|
||||||
key={pageSearch.get('status')} // Force re-render on browser forward/back
|
key={status} // force re-mount on history change to switch tab (via "initialTabName")
|
||||||
>
|
>
|
||||||
{tabRenderer}
|
{renderTabs}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
115
mailpoet/assets/js/src/automation/listing/store/actions.tsx
Normal file
115
mailpoet/assets/js/src/automation/listing/store/actions.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { dispatch, StoreDescriptor } from '@wordpress/data';
|
||||||
|
import { apiFetch } from '@wordpress/data-controls';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
import { Workflow, WorkflowStatus } from '../workflow';
|
||||||
|
import { EditWorkflow, UndoTrashButton } from '../components/actions';
|
||||||
|
|
||||||
|
const createSuccessNotice = (content: string, options?: unknown) =>
|
||||||
|
dispatch(noticesStore as StoreDescriptor).createSuccessNotice(
|
||||||
|
content,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeNotice = (id: string) =>
|
||||||
|
dispatch(noticesStore as StoreDescriptor).removeNotice(id);
|
||||||
|
|
||||||
|
export function* loadWorkflows() {
|
||||||
|
const data = yield apiFetch({
|
||||||
|
path: `/workflows`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'SET_WORKFLOWS',
|
||||||
|
workflows: data.data,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* duplicateWorkflow(workflow: Workflow) {
|
||||||
|
const data = yield apiFetch({
|
||||||
|
path: `/workflows/${workflow.id}/duplicate`,
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
void createSuccessNotice(
|
||||||
|
// translators: %s is the automation workflow name
|
||||||
|
sprintf(__('Automation "%s" was duplicated.', 'mailpoet'), workflow.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'ADD_WORKFLOW',
|
||||||
|
workflow: data.data,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* trashWorkflow(workflow: Workflow) {
|
||||||
|
const data = yield apiFetch({
|
||||||
|
path: `/workflows/${workflow.id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data: {
|
||||||
|
status: WorkflowStatus.TRASH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = __('1 automation moved to the Trash.', 'mailpoet');
|
||||||
|
void createSuccessNotice(message, {
|
||||||
|
id: `workflow-trashed-${workflow.id}`,
|
||||||
|
__unstableHTML: (
|
||||||
|
<p>
|
||||||
|
{message}{' '}
|
||||||
|
<UndoTrashButton workflow={workflow} previousStatus={workflow.status} />
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'UPDATE_WORKFLOW',
|
||||||
|
workflow: data.data,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* restoreWorkflow(workflow: Workflow, status: WorkflowStatus) {
|
||||||
|
const data = yield apiFetch({
|
||||||
|
path: `/workflows/${workflow.id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data: {
|
||||||
|
status,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
void removeNotice(`workflow-trashed-${workflow.id}`);
|
||||||
|
|
||||||
|
const message = __('1 automation restored from the Trash.', 'mailpoet');
|
||||||
|
void createSuccessNotice(message, {
|
||||||
|
__unstableHTML: (
|
||||||
|
<p>
|
||||||
|
{message}{' '}
|
||||||
|
<EditWorkflow
|
||||||
|
workflow={workflow}
|
||||||
|
label={__('Edit workflow', 'mailpoet')}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'UPDATE_WORKFLOW',
|
||||||
|
workflow: data.data,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* deleteWorkflow(workflow: Workflow) {
|
||||||
|
yield apiFetch({
|
||||||
|
path: `/workflows/${workflow.id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
void createSuccessNotice(
|
||||||
|
__('1 automation and all associated data permanently deleted.', 'mailpoet'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'DELETE_WORKFLOW',
|
||||||
|
workflow,
|
||||||
|
} as const;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export const storeName = 'mailpoet/automation-listing';
|
3
mailpoet/assets/js/src/automation/listing/store/index.ts
Normal file
3
mailpoet/assets/js/src/automation/listing/store/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './constants';
|
||||||
|
export * from './store';
|
||||||
|
export * from './types';
|
@ -0,0 +1,5 @@
|
|||||||
|
import { State } from './types';
|
||||||
|
|
||||||
|
export const getInitialState = (): State => ({
|
||||||
|
workflows: undefined,
|
||||||
|
});
|
34
mailpoet/assets/js/src/automation/listing/store/reducer.ts
Normal file
34
mailpoet/assets/js/src/automation/listing/store/reducer.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Action } from '@wordpress/data';
|
||||||
|
import { State } from './types';
|
||||||
|
import { Workflow } from '../workflow';
|
||||||
|
|
||||||
|
export function reducer(state: State, action: Action): State {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_WORKFLOWS':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workflows: action.workflows,
|
||||||
|
};
|
||||||
|
case 'ADD_WORKFLOW':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workflows: [action.workflow, ...state.workflows],
|
||||||
|
};
|
||||||
|
case 'UPDATE_WORKFLOW':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workflows: state.workflows.map((workflow: Workflow) =>
|
||||||
|
workflow.id === action.workflow.id ? action.workflow : workflow,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
case 'DELETE_WORKFLOW':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
workflows: state.workflows.filter(
|
||||||
|
(workflow: Workflow) => workflow.id !== action.workflow.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
11
mailpoet/assets/js/src/automation/listing/store/selectors.ts
Normal file
11
mailpoet/assets/js/src/automation/listing/store/selectors.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { State } from './types';
|
||||||
|
import { Workflow } from '../workflow';
|
||||||
|
import { workflowCount } from '../../config';
|
||||||
|
|
||||||
|
export function getWorkflows(state: State): Workflow[] {
|
||||||
|
return state.workflows;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkflowCount(state: State): number {
|
||||||
|
return state.workflows ? state.workflows.length : workflowCount;
|
||||||
|
}
|
41
mailpoet/assets/js/src/automation/listing/store/store.ts
Normal file
41
mailpoet/assets/js/src/automation/listing/store/store.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
createReduxStore,
|
||||||
|
register,
|
||||||
|
StoreConfig,
|
||||||
|
StoreDescriptor,
|
||||||
|
} from '@wordpress/data';
|
||||||
|
import { controls } from '@wordpress/data-controls';
|
||||||
|
import { storeName } from './constants';
|
||||||
|
import { getInitialState } from './initial_state';
|
||||||
|
import { reducer } from './reducer';
|
||||||
|
import * as actions from './actions';
|
||||||
|
import * as selectors from './selectors';
|
||||||
|
import { State } from './types';
|
||||||
|
import { OmitFirstArgs } from '../../../types';
|
||||||
|
|
||||||
|
type StoreType = Omit<StoreDescriptor, 'name'> & {
|
||||||
|
name: typeof storeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createStore = (): StoreType => {
|
||||||
|
const storeConfig = {
|
||||||
|
actions,
|
||||||
|
controls,
|
||||||
|
selectors,
|
||||||
|
reducer,
|
||||||
|
initialState: getInitialState(),
|
||||||
|
} as StoreConfig<State>;
|
||||||
|
|
||||||
|
const store = createReduxStore<State>(storeName, storeConfig) as StoreType;
|
||||||
|
register(store);
|
||||||
|
return store;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StoreKey = typeof storeName | StoreType;
|
||||||
|
|
||||||
|
declare module '@wordpress/data' {
|
||||||
|
function select(key: StoreKey): OmitFirstArgs<typeof selectors>;
|
||||||
|
function dispatch(key: StoreKey): typeof actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { actions, selectors };
|
5
mailpoet/assets/js/src/automation/listing/store/types.ts
Normal file
5
mailpoet/assets/js/src/automation/listing/store/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Workflow } from '../workflow';
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
workflows?: Workflow[];
|
||||||
|
};
|
@ -3,6 +3,7 @@ export enum WorkflowStatus {
|
|||||||
INACTIVE = 'inactive',
|
INACTIVE = 'inactive',
|
||||||
DRAFT = 'draft',
|
DRAFT = 'draft',
|
||||||
TRASH = 'trash',
|
TRASH = 'trash',
|
||||||
|
DEACTIVATING = 'deactivating',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Workflow = {
|
export type Workflow = {
|
||||||
|
@ -4,6 +4,7 @@ import { Flex } from '@wordpress/components';
|
|||||||
import { workflowTemplates } from './config';
|
import { workflowTemplates } from './config';
|
||||||
import { TemplateListItem } from './components/template-list-item';
|
import { TemplateListItem } from './components/template-list-item';
|
||||||
import { initializeApi } from '../api';
|
import { initializeApi } from '../api';
|
||||||
|
import { registerTranslations } from '../i18n';
|
||||||
import { TopBarWithBeamer } from '../../common/top_bar/top_bar';
|
import { TopBarWithBeamer } from '../../common/top_bar/top_bar';
|
||||||
import {
|
import {
|
||||||
FromScratchButton,
|
FromScratchButton,
|
||||||
@ -37,6 +38,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerTranslations();
|
||||||
initializeApi();
|
initializeApi();
|
||||||
ReactDOM.render(<Templates />, root);
|
ReactDOM.render(<Templates />, root);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { noop } from 'lodash';
|
|
||||||
import { MailPoet } from 'mailpoet';
|
import { MailPoet } from 'mailpoet';
|
||||||
import { Modal } from 'common/modal/modal';
|
import { Modal } from 'common/modal/modal';
|
||||||
import {
|
import {
|
||||||
@ -99,7 +97,7 @@ function AuthorizeSenderDomainModal({
|
|||||||
if (res.data.ok) {
|
if (res.data.ok) {
|
||||||
// record verified, close the modal
|
// record verified, close the modal
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
setVerifiedSenderDomain(senderDomain);
|
setVerifiedSenderDomain?.(senderDomain);
|
||||||
onRequestClose();
|
onRequestClose();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -185,14 +183,4 @@ function AuthorizeSenderDomainModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthorizeSenderDomainModal.propTypes = {
|
|
||||||
senderDomain: PropTypes.string.isRequired,
|
|
||||||
useModal: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
AuthorizeSenderDomainModal.defaultProps = {
|
|
||||||
setVerifiedSenderDomain: noop,
|
|
||||||
useModal: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { AuthorizeSenderDomainModal };
|
export { AuthorizeSenderDomainModal };
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import ReactStringReplace from 'react-string-replace';
|
import ReactStringReplace from 'react-string-replace';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { noop } from 'lodash';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { MailPoet } from 'mailpoet';
|
import { MailPoet } from 'mailpoet';
|
||||||
import { Modal } from 'common/modal/modal';
|
import { Modal } from 'common/modal/modal';
|
||||||
@ -117,7 +115,7 @@ function AuthorizeSenderEmailModal({
|
|||||||
setCreateEmailApiResponse(null);
|
setCreateEmailApiResponse(null);
|
||||||
setShowLoader(false);
|
setShowLoader(false);
|
||||||
setConfirmEmailApiResponse(true);
|
setConfirmEmailApiResponse(true);
|
||||||
setAuthorizedAddress(senderEmailAddress);
|
setAuthorizedAddress?.(senderEmailAddress);
|
||||||
removeUnauthorizedEmailNotices();
|
removeUnauthorizedEmailNotices();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -238,14 +236,4 @@ function AuthorizeSenderEmailModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthorizeSenderEmailModal.propTypes = {
|
|
||||||
senderEmail: PropTypes.string.isRequired,
|
|
||||||
useModal: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
AuthorizeSenderEmailModal.defaultProps = {
|
|
||||||
setAuthorizedAddress: noop,
|
|
||||||
useModal: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { AuthorizeSenderEmailModal };
|
export { AuthorizeSenderEmailModal };
|
||||||
|
@ -6,13 +6,13 @@ type Event = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
export type TokenFieldProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange: (event: Event) => void;
|
onChange: (event: Event) => void;
|
||||||
selectedValues?: FormTokenField.Value[];
|
selectedValues?: FormTokenField.Value[] | [];
|
||||||
suggestedValues?: readonly string[];
|
suggestedValues?: readonly string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export function TokenField({
|
|||||||
selectedValues,
|
selectedValues,
|
||||||
suggestedValues,
|
suggestedValues,
|
||||||
onChange,
|
onChange,
|
||||||
}: Props) {
|
}: TokenFieldProps) {
|
||||||
const args = {
|
const args = {
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
|
@ -99,6 +99,7 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
|||||||
>
|
>
|
||||||
{showAuthorizedEmailModal && (
|
{showAuthorizedEmailModal && (
|
||||||
<AuthorizeSenderEmailModal
|
<AuthorizeSenderEmailModal
|
||||||
|
useModal
|
||||||
senderEmail={address}
|
senderEmail={address}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
setShowAuthorizedEmailModal(false);
|
setShowAuthorizedEmailModal(false);
|
||||||
|
@ -2,6 +2,7 @@ import { Component } from 'react';
|
|||||||
import { FormFieldText } from 'form/fields/text.jsx';
|
import { FormFieldText } from 'form/fields/text.jsx';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormFieldTextarea } from 'form/fields/textarea.jsx';
|
import { FormFieldTextarea } from 'form/fields/textarea.jsx';
|
||||||
import { FormFieldSelect } from 'form/fields/select.jsx';
|
import { FormFieldSelect } from 'form/fields/select.jsx';
|
||||||
@ -151,8 +152,26 @@ class FormField extends Component {
|
|||||||
field = 'invalid';
|
field = 'invalid';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
typeof this.props.field.disabled === 'function'
|
||||||
|
? this.props.field.disabled(this.props.field)
|
||||||
|
: this.props.field.disabled;
|
||||||
|
|
||||||
|
const eventListeners = {
|
||||||
|
...(this.props.field.onWrapperClick
|
||||||
|
? { onClick: this.props.field.onWrapperClick }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mailpoet-form-field" key={`field-${data.index || 0}`}>
|
<div
|
||||||
|
className={classNames('mailpoet-form-field', {
|
||||||
|
'mailpoet-form-field-disabled': isDisabled,
|
||||||
|
})}
|
||||||
|
key={`field-${data.index || 0}`}
|
||||||
|
{...eventListeners}
|
||||||
|
>
|
||||||
{field}
|
{field}
|
||||||
{description}
|
{description}
|
||||||
</div>
|
</div>
|
||||||
@ -212,6 +231,8 @@ FormField.propTypes = {
|
|||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
fields: PropTypes.arrayOf(PropTypes.object),
|
fields: PropTypes.arrayOf(PropTypes.object),
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
|
onWrapperClick: PropTypes.func,
|
||||||
|
disabled: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import PropTypes from 'prop-types';
|
import { TokenFieldProps, TokenField } from 'common/form/tokenField/tokenField';
|
||||||
import { TokenField } from 'common/form/tokenField/tokenField';
|
import { FormTokenItem } from '../../automation/integrations/mailpoet/components/form-token-field';
|
||||||
|
|
||||||
function getItems(endpoint: string) {
|
interface TokenFormFieldProps {
|
||||||
|
onValueChange: TokenFieldProps['onChange'];
|
||||||
|
item?: Record<string, FormTokenItem[]>;
|
||||||
|
field: Omit<TokenFieldProps, 'id' | 'onChange' | 'selectedValues'> & {
|
||||||
|
endpoint: string;
|
||||||
|
getName: (item: FormTokenItem) => string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItems(endpoint: string): FormTokenItem[] {
|
||||||
let items = [];
|
let items = [];
|
||||||
if (typeof window[`mailpoet_${endpoint}`] !== 'undefined') {
|
if (typeof window[`mailpoet_${endpoint}`] !== 'undefined') {
|
||||||
items = window[`mailpoet_${endpoint}`];
|
items = window[`mailpoet_${endpoint}`];
|
||||||
@ -10,12 +19,15 @@ function getItems(endpoint: string) {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormFieldTokenField(props) {
|
function FormFieldTokenField(props: TokenFormFieldProps) {
|
||||||
const selectedValues = Array.isArray(props.item[props.field.name])
|
const selectedValues: TokenFieldProps['selectedValues'] = Array.isArray(
|
||||||
? props.item[props.field.name].map((item) => props.field.getName(item))
|
props.item[props.field.name],
|
||||||
|
)
|
||||||
|
? props.field.name &&
|
||||||
|
props.item[props.field.name].map((item) => props.field.getName(item))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
let suggestedValues = [];
|
let suggestedValues: readonly string[] = [];
|
||||||
if (props.field.endpoint) {
|
if (props.field.endpoint) {
|
||||||
const items = getItems(String(props.field.endpoint));
|
const items = getItems(String(props.field.endpoint));
|
||||||
suggestedValues = items.map((item) => props.field.getName(item));
|
suggestedValues = items.map((item) => props.field.getName(item));
|
||||||
@ -35,16 +47,4 @@ function FormFieldTokenField(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
FormFieldTokenField.propTypes = {
|
|
||||||
onValueChange: PropTypes.func,
|
|
||||||
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
|
||||||
field: PropTypes.shape({
|
|
||||||
name: PropTypes.string,
|
|
||||||
label: PropTypes.string,
|
|
||||||
suggestedValues: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
placeholder: PropTypes.string,
|
|
||||||
getName: PropTypes.func,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { FormFieldTokenField };
|
export { FormFieldTokenField };
|
||||||
|
@ -26,4 +26,5 @@ export type Field = {
|
|||||||
getLabel?: (segment: Segment) => string;
|
getLabel?: (segment: Segment) => string;
|
||||||
getCount?: (segment: Segment) => string;
|
getCount?: (segment: Segment) => string;
|
||||||
transformChangedValue?: (arg: unknown) => Segment;
|
transformChangedValue?: (arg: unknown) => Segment;
|
||||||
|
onWrapperClick?: () => void;
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,23 @@ import { partial } from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ColorGradientSettings } from '../components/color_gradient_settings';
|
import { ColorGradientSettings } from '../components/color_gradient_settings';
|
||||||
|
|
||||||
function InputStylesSettings({ styles, onChange }) {
|
type InputStyles = {
|
||||||
|
fullWidth: boolean;
|
||||||
|
inheritFromTheme: boolean;
|
||||||
|
bold: boolean;
|
||||||
|
backgroundColor: string;
|
||||||
|
borderSize: number;
|
||||||
|
borderRadius: number;
|
||||||
|
borderColor: string;
|
||||||
|
fontColor: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputStylesSettingsProps = {
|
||||||
|
styles: InputStyles;
|
||||||
|
onChange: (styles: InputStyles) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function InputStylesSettings({ styles, onChange }: InputStylesSettingsProps) {
|
||||||
const localStylesRef = useRef(styles);
|
const localStylesRef = useRef(styles);
|
||||||
const localStyles = localStylesRef.current;
|
const localStyles = localStylesRef.current;
|
||||||
|
|
||||||
@ -132,6 +148,10 @@ function InputStylesSettings({ styles, onChange }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since removal of propTypes for InputStylesSettings
|
||||||
|
* Remove when TextInputEdit is converted to tsx
|
||||||
|
*/
|
||||||
export const inputStylesPropTypes = PropTypes.shape({
|
export const inputStylesPropTypes = PropTypes.shape({
|
||||||
fullWidth: PropTypes.bool.isRequired,
|
fullWidth: PropTypes.bool.isRequired,
|
||||||
inheritFromTheme: PropTypes.bool.isRequired,
|
inheritFromTheme: PropTypes.bool.isRequired,
|
||||||
@ -142,9 +162,4 @@ export const inputStylesPropTypes = PropTypes.shape({
|
|||||||
borderColor: PropTypes.string,
|
borderColor: PropTypes.string,
|
||||||
});
|
});
|
||||||
|
|
||||||
InputStylesSettings.propTypes = {
|
|
||||||
styles: inputStylesPropTypes.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { InputStylesSettings };
|
export { InputStylesSettings };
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
SelectControl,
|
SelectControl,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
import { MailPoet } from 'mailpoet';
|
import { MailPoet } from 'mailpoet';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
import { partial } from 'lodash';
|
import { partial } from 'lodash';
|
||||||
import { HorizontalAlignment } from 'common/styles';
|
import { HorizontalAlignment } from 'common/styles';
|
||||||
@ -18,7 +17,12 @@ import { CloseButtonsSettings } from 'form_editor/components/close_button_settin
|
|||||||
import { formStyles as defaultFormStyles } from 'form_editor/store/defaults';
|
import { formStyles as defaultFormStyles } from 'form_editor/store/defaults';
|
||||||
import { FontFamilySettings } from '../font_family_settings';
|
import { FontFamilySettings } from '../font_family_settings';
|
||||||
|
|
||||||
function StylesSettingsPanel({ onToggle, isOpened }) {
|
type StylesSettingsPanelProps = {
|
||||||
|
onToggle: PanelBody.Props['onToggle'];
|
||||||
|
isOpened: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function StylesSettingsPanel({ onToggle, isOpened }: StylesSettingsPanelProps) {
|
||||||
const { changeFormSettings } = useDispatch('mailpoet-form-editor');
|
const { changeFormSettings } = useDispatch('mailpoet-form-editor');
|
||||||
const settings = useSelect(
|
const settings = useSelect(
|
||||||
(select) => select('mailpoet-form-editor').getFormSettings(),
|
(select) => select('mailpoet-form-editor').getFormSettings(),
|
||||||
@ -165,9 +169,4 @@ function StylesSettingsPanel({ onToggle, isOpened }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
StylesSettingsPanel.propTypes = {
|
|
||||||
onToggle: PropTypes.func.isRequired,
|
|
||||||
isOpened: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { StylesSettingsPanel };
|
export { StylesSettingsPanel };
|
||||||
|
@ -22,6 +22,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parent": ["woocommerce/checkout-contact-information-block"],
|
"parent": ["woocommerce/checkout-contact-information-block"],
|
||||||
"editorScript": "file:../../../dist/js/marketing_optin_block/marketing-optin-block.js",
|
"editorScript": "file:./marketing-optin-block.js",
|
||||||
"editorStyle": "file:../../../dist/js/marketing_optin_block/marketing-optin-block.css"
|
"editorStyle": "file:./marketing-optin-block.css"
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,14 @@ function EmptyState(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Placeholder
|
<Placeholder
|
||||||
icon={<Icon icon={megaphone} />}
|
icon={<Icon icon={megaphone} />}
|
||||||
label={__('marketing-opt-in-label', 'mailpoet')}
|
label={__('Marketing opt-in', 'mailpoet')}
|
||||||
className="wp-block-mailpoet-newsletter-block-placeholder"
|
className="wp-block-mailpoet-newsletter-block-placeholder"
|
||||||
>
|
>
|
||||||
<span className="wp-block-mailpoet-newsletter-block-placeholder__description">
|
<span className="wp-block-mailpoet-newsletter-block-placeholder__description">
|
||||||
{__('marketing-opt-in-not-shown', 'mailpoet')}
|
{__(
|
||||||
|
'MailPoet marketing opt-in would be shown here if enabled. You can enable from the settings page.',
|
||||||
|
'mailpoet',
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
isPrimary
|
isPrimary
|
||||||
@ -34,7 +37,7 @@ function EmptyState(): JSX.Element {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="wp-block-mailpoet-newsletter-block-placeholder__button"
|
className="wp-block-mailpoet-newsletter-block-placeholder__button"
|
||||||
>
|
>
|
||||||
{__('marketing-opt-in-enable', 'mailpoet')}
|
{__('Enable opt-in for Checkout', 'mailpoet')}
|
||||||
</Button>
|
</Button>
|
||||||
</Placeholder>
|
</Placeholder>
|
||||||
);
|
);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user