Compare commits
406 Commits
Author | SHA1 | Date | |
---|---|---|---|
22bf4ec38b | |||
7570c9fa0e | |||
7642c4da4f | |||
dce13e5b8e | |||
8b922681b6 | |||
bd33cdbc24 | |||
bc04ad2528 | |||
c54a5213b3 | |||
07cf0cd79b | |||
e64dc1669f | |||
48181994d8 | |||
31c2915075 | |||
67f05ecfe2 | |||
c121c60e8c | |||
110859508e | |||
efd3377ecd | |||
a7c1d8431d | |||
127f3e6363 | |||
6bec12c762 | |||
d1df2d4f42 | |||
5598f0af19 | |||
8dd80ce0f2 | |||
e45510e064 | |||
bfafff132a | |||
d6748fe979 | |||
2169522818 | |||
06cfa3fc60 | |||
f9e507eb15 | |||
90332a23b3 | |||
a82a810269 | |||
5a378b3c5f | |||
49a2df3dbb | |||
dee5ff38f5 | |||
70aefb421b | |||
764e1a1bf0 | |||
b140011f92 | |||
b48c9e437e | |||
f34efb4d6f | |||
324c03e8b9 | |||
e718101425 | |||
080ce50fea | |||
1b9eb223b0 | |||
ea2fa794ac | |||
c46c61a923 | |||
1136431551 | |||
957be23212 | |||
91a88b3e91 | |||
7c5d239267 | |||
c748b80447 | |||
725e0ecb00 | |||
eb71dd8a68 | |||
ce1687cf97 | |||
2e328b6d7f | |||
8489e63d34 | |||
f8c17730fc | |||
93e43eee7a | |||
9063dc3079 | |||
9d55d3f134 | |||
d199c3768a | |||
0e0c2447d9 | |||
99dfb3d24b | |||
c4366e009b | |||
1d31202607 | |||
78d25ffb69 | |||
b710682c66 | |||
52c6b94315 | |||
fd6b49e598 | |||
df2982454e | |||
8a19fd906f | |||
99198e5c2d | |||
9204f37560 | |||
765aa6efab | |||
50b613365f | |||
4fe8d10d6c | |||
162dab790d | |||
6e8c9731d8 | |||
235552f91d | |||
4ee08c296b | |||
bda979ec4f | |||
78f10f064e | |||
eca4a68fec | |||
9daa5d58c7 | |||
6cc232fbad | |||
6a07bd44ff | |||
63cd326191 | |||
40b15b0eb1 | |||
3825e9cb11 | |||
7db2942140 | |||
3a5d28f24a | |||
d02c63844d | |||
c3045dba07 | |||
c239566e1e | |||
dfdc8cfd09 | |||
4977b7ffa2 | |||
8e69299a6a | |||
903bcbb92e | |||
8d6492ac8c | |||
c39ae1fe1f | |||
e3539b06a2 | |||
d34a265ac2 | |||
9a72e361b1 | |||
a520e4bd93 | |||
9ab6ebbe0d | |||
6a3cfd05e7 | |||
f825e535e3 | |||
a2c5420d7f | |||
c6d3573652 | |||
e41bcd0d02 | |||
f805907954 | |||
d970efc0da | |||
c0ce5944dc | |||
467f354eb1 | |||
3f016b45f9 | |||
679f74e498 | |||
08b314e0b4 | |||
cc5959805b | |||
407f3d1609 | |||
503df3584c | |||
fa9510f0c1 | |||
cc92df4e7f | |||
d76c5d32f2 | |||
c6198cba4c | |||
e29dd4286e | |||
98c1c33341 | |||
78cc8743d5 | |||
89e75b3828 | |||
982d104ba0 | |||
2ce4461c63 | |||
146a55b921 | |||
7e883d3e5b | |||
7896efcd6c | |||
a1ef87ef78 | |||
d058f1741f | |||
ff0852fa39 | |||
5f07b68b07 | |||
0c2934a556 | |||
885b51a6cf | |||
6cb5ceca21 | |||
9a14ddb57c | |||
5c6f32488a | |||
4ec8f27d36 | |||
9554727370 | |||
394f9abd67 | |||
6ee45f0f54 | |||
2e9ae1ed0b | |||
74b12bd2d9 | |||
279adcfbcf | |||
64d655857b | |||
b2b5f3c22a | |||
588f3a9feb | |||
8c676b773d | |||
c76a0adb5e | |||
848fc51070 | |||
af1e09f46f | |||
f1abfe557a | |||
aa52ca804e | |||
273551fa36 | |||
17898b243a | |||
81ed12d454 | |||
2f78c99381 | |||
480c7d25e0 | |||
c1a6c5a215 | |||
8fadc87036 | |||
5176566882 | |||
b52b9990da | |||
8796599d89 | |||
9808245d30 | |||
12f2d1730f | |||
dac8c1e2f3 | |||
a3e1f8d8c6 | |||
2c28449b58 | |||
43197bf859 | |||
bd34c4e7ec | |||
511b8ad7ff | |||
a60562254c | |||
96368cbd3f | |||
e384e7dea3 | |||
86fbcd3a57 | |||
9730fb9272 | |||
a3f2ee8bbb | |||
a177b3b8da | |||
f369fdfa4a | |||
80a6e78d48 | |||
f65821256c | |||
dd10389c55 | |||
3dd6ef3da7 | |||
036d0a29ae | |||
d199e0b77e | |||
17ab79aa6f | |||
b10cd32449 | |||
f9001c1826 | |||
31050394b9 | |||
e9970f3cc8 | |||
516c460ace | |||
03d80290e6 | |||
d65b567858 | |||
0d10f4b1dd | |||
610d0204eb | |||
545a4adc66 | |||
38d8a1cd00 | |||
53201c7422 | |||
6f447e8a93 | |||
368a400fea | |||
902eeccea1 | |||
c8cf00a151 | |||
f41423e6a2 | |||
774485d5f2 | |||
8f4d62b080 | |||
8003a3e97f | |||
cf572985a7 | |||
c4966a9cc1 | |||
c6e6b13731 | |||
975041d211 | |||
ea16472ab1 | |||
713b0255f7 | |||
2aa63bbb8e | |||
f6708d9526 | |||
65b56271d4 | |||
c9c35d591b | |||
e9cfcb51ba | |||
dc5e5b4f8b | |||
ccb5369c57 | |||
bfe3ff17af | |||
756b0587bc | |||
af3d08ff36 | |||
a91913e633 | |||
736d8a8b12 | |||
9b5bdb2206 | |||
3ad606a767 | |||
e0cab11293 | |||
bffafdcd33 | |||
6dfeb7a100 | |||
a6431678ed | |||
cdcdfb135c | |||
7821c5767b | |||
6a5b7adc16 | |||
e58de01950 | |||
ae6e790879 | |||
d21b2fe21d | |||
6b8e8988b8 | |||
0db495eb1d | |||
6ad315b8de | |||
945ff65358 | |||
e0458ca444 | |||
5bd639e0be | |||
c78a050d86 | |||
e37a824d86 | |||
fe4d78992f | |||
1c380933e1 | |||
a8c3465133 | |||
edbfb957b1 | |||
161b8d8f34 | |||
96d9347cff | |||
dfb7133dbb | |||
a264b00200 | |||
100a301476 | |||
35c3ebfb20 | |||
e3db69282d | |||
45999f98b2 | |||
6f5c39e97c | |||
4644f3fe08 | |||
d951ebd351 | |||
a7bc4e7c70 | |||
a08553c9bc | |||
8161b83ded | |||
ad46d05c6b | |||
f231f3dc6d | |||
933b947f45 | |||
f6a5e0117c | |||
56c79dd66a | |||
25ba667cb1 | |||
1ac9c86a66 | |||
94a2e54c1e | |||
fbf4c853c1 | |||
5fba25d823 | |||
18efc02921 | |||
bdd803c517 | |||
382e232538 | |||
a2cddc761c | |||
6b4d2409cc | |||
da110bed80 | |||
363ee93c25 | |||
db0024dc58 | |||
1b59b95f52 | |||
2d0673a864 | |||
9f4b0d0ca1 | |||
97c1712fa6 | |||
455e3fbb90 | |||
3c718e3f68 | |||
63e797ba37 | |||
48340872f8 | |||
1b5d85d41a | |||
0595852d5a | |||
448e89d062 | |||
f92ee90e9b | |||
80f22c5b50 | |||
4b74d66529 | |||
ca72e11d9a | |||
5a0ec403f6 | |||
b6e7e39fb0 | |||
8c24d59f43 | |||
5ed0c3fb2d | |||
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 |
@ -71,13 +71,6 @@ anchors:
|
||||
- trunk
|
||||
- release
|
||||
|
||||
only_trunk_and_cot: &only_trunk_and_cot
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- trunk
|
||||
- /^cot-.*/
|
||||
|
||||
multisite_acceptance_config: &multisite_acceptance_config
|
||||
multisite: 1
|
||||
requires:
|
||||
@ -186,10 +179,10 @@ jobs:
|
||||
- run:
|
||||
name: Download additional WP Plugins for tests
|
||||
command: |
|
||||
./do download:woo-commerce-zip 6.8.2
|
||||
./do download:woo-commerce-subscriptions-zip 4.5.1
|
||||
./do download:woo-commerce-memberships-zip 1.23.0
|
||||
./do download:woo-commerce-blocks-zip 8.4.0
|
||||
./do download:woo-commerce-zip 7.1.0
|
||||
./do download:woo-commerce-subscriptions-zip 4.6.0
|
||||
./do download:woo-commerce-memberships-zip 1.23.1
|
||||
./do download:woo-commerce-blocks-zip 8.8.2
|
||||
- run:
|
||||
name: Dump tests ENV variables for acceptance tests
|
||||
command: |
|
||||
@ -318,7 +311,7 @@ jobs:
|
||||
parallelism: 20
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.07.1
|
||||
image: ubuntu-2204:2022.10.2
|
||||
parameters:
|
||||
multisite:
|
||||
type: integer
|
||||
@ -356,9 +349,6 @@ jobs:
|
||||
enable_cot_sync:
|
||||
type: integer
|
||||
default: 0
|
||||
allow_fail:
|
||||
type: integer
|
||||
default: 0
|
||||
environment:
|
||||
MYSQL_COMMAND: << parameters.mysql_command >>
|
||||
MYSQL_IMAGE_VERSION: << parameters.mysql_image_version >>
|
||||
@ -434,9 +424,6 @@ jobs:
|
||||
--xml
|
||||
-g circleci_split_group
|
||||
)
|
||||
if [[ << parameters.allow_fail >> == 1 ]]; then
|
||||
args+=(--no-exit)
|
||||
fi
|
||||
docker-compose run -e SKIP_DEPS=1 \
|
||||
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
|
||||
-e CIRCLE_JOB=${CIRCLE_JOB} \
|
||||
@ -444,11 +431,6 @@ jobs:
|
||||
-e ENABLE_COT=<< parameters.enable_cot >> \
|
||||
-e ENABLE_COT_SYNC=<< parameters.enable_cot_sync >> \
|
||||
codeception_acceptance "${args[@]}"
|
||||
- when:
|
||||
condition:
|
||||
not:
|
||||
equal: [1, << parameters.allow_fail >>]
|
||||
steps:
|
||||
- run:
|
||||
name: Check exceptions
|
||||
command: |
|
||||
@ -494,7 +476,7 @@ jobs:
|
||||
integration_tests:
|
||||
working_directory: /home/circleci/mailpoet/mailpoet
|
||||
machine:
|
||||
image: ubuntu-2204:2022.07.1
|
||||
image: ubuntu-2204:2022.10.2
|
||||
environment:
|
||||
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
|
||||
parameters:
|
||||
@ -519,12 +501,6 @@ jobs:
|
||||
multisite:
|
||||
type: integer
|
||||
default: 0
|
||||
woo_core_version:
|
||||
type: string
|
||||
default: ''
|
||||
allow_fail:
|
||||
type: integer
|
||||
default: 0
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /home/circleci
|
||||
@ -532,14 +508,6 @@ jobs:
|
||||
name: 'Pull test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- when:
|
||||
condition: << parameters.woo_core_version >>
|
||||
steps:
|
||||
- run:
|
||||
name: Download WooCommerce Core
|
||||
command: |
|
||||
cd tests/docker
|
||||
docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip << parameters.woo_core_version >>" --no-deps codeception_integration
|
||||
- run:
|
||||
name: 'PHP Integration tests'
|
||||
command: |
|
||||
@ -558,9 +526,6 @@ jobs:
|
||||
if [[ -n '<< parameters.skip_group >>' ]]; then
|
||||
args+=(--skip-group << parameters.skip_group >>)
|
||||
fi
|
||||
if [[ << parameters.allow_fail >> == 1 ]]; then
|
||||
args+=(--no-exit)
|
||||
fi
|
||||
docker-compose run -e SKIP_DEPS=1 \
|
||||
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
|
||||
-e CIRCLE_JOB=${CIRCLE_JOB} \
|
||||
@ -661,13 +626,10 @@ workflows:
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
name: acceptance_tests_woo_cot_sync
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 1
|
||||
allow_fail: 1
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -675,13 +637,10 @@ workflows:
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
name: acceptance_tests_woo_cot_no_sync
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 0
|
||||
allow_fail: 1
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -689,10 +648,8 @@ workflows:
|
||||
- qa_php
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
name: acceptance_tests_woo_cot_off
|
||||
group: woo
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
requires:
|
||||
- unit_tests
|
||||
- static_analysis_php8
|
||||
@ -713,12 +670,9 @@ workflows:
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 1
|
||||
allow_fail: 1
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_sync
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -727,12 +681,9 @@ workflows:
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
group: woo
|
||||
enable_cot: 1
|
||||
enable_cot_sync: 0
|
||||
allow_fail: 1
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_no_sync
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -741,9 +692,7 @@ workflows:
|
||||
- qa_php
|
||||
- integration_tests:
|
||||
<<: *slack-fail-post-step
|
||||
<<: *only_trunk_and_cot
|
||||
group: woo
|
||||
woo_core_version: woo-cot-beta # Temporarily force COT beta version
|
||||
name: integration_test_woo_cot_off
|
||||
requires:
|
||||
- unit_tests
|
||||
@ -783,6 +732,12 @@ workflows:
|
||||
- js_tests
|
||||
- integration_test_woocommerce
|
||||
- integration_test_base
|
||||
- integration_test_woo_cot_no_sync
|
||||
- integration_test_woo_cot_off
|
||||
- integration_test_woo_cot_sync
|
||||
- acceptance_tests_woo_cot_sync
|
||||
- acceptance_tests_woo_cot_off
|
||||
- acceptance_tests_woo_cot_no_sync
|
||||
|
||||
nightly:
|
||||
triggers:
|
||||
@ -807,14 +762,14 @@ workflows:
|
||||
- acceptance_tests:
|
||||
<<: *slack-fail-post-step
|
||||
name: acceptance_oldest
|
||||
woo_core_version: 6.2.2
|
||||
woo_core_version: 6.8.0
|
||||
woo_subscriptions_version: 4.3.0
|
||||
woo_memberships_version: 1.21.0
|
||||
woo_blocks_version: 5.3.2
|
||||
woo_blocks_version: 6.8.0
|
||||
mysql_command: --max_allowed_packet=100M
|
||||
mysql_image_version: 5.7.36
|
||||
codeception_image_version: 7.4-cli_20210126.1
|
||||
wordpress_image_version: wp-5.6_php7.2_20220406.1
|
||||
codeception_image_version: 7.4-cli_20220605.0
|
||||
wordpress_image_version: wp-5.8_php7.3_20221104.1
|
||||
requires:
|
||||
- build
|
||||
- unit_tests:
|
||||
|
@ -28,6 +28,7 @@ Class `\MailPoet\API\API` becomes available once MailPoet plugin is loaded by Wo
|
||||
- [Add List (addList)](api_methods/AddList.md)
|
||||
- [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md)
|
||||
- [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md)
|
||||
- [Delete List (deleteList)](api_methods/DeleteList.md)
|
||||
- [Get Lists (getLists)](api_methods/GetLists.md)
|
||||
- [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md)
|
||||
- [Get Subscribers (getSubscribers)](api_methods/GetSubscribers.md)
|
||||
@ -38,6 +39,7 @@ Class `\MailPoet\API\API` becomes available once MailPoet plugin is loaded by Wo
|
||||
- [Subscribe to Lists (subscribeToLists)](api_methods/SubscribeToLists.md)
|
||||
- [Unsubscribe from List (unsubscribeFromList)](api_methods/UnsubscribeFromList.md)
|
||||
- [Unsubscribe from Lists (unsubscribeFromLists)](api_methods/UnsubscribeFromLists.md)
|
||||
- [Update List (updateList)](api_methods/UpdateList.md)
|
||||
|
||||
### Usage examples
|
||||
|
||||
|
27
doc/api_methods/DeleteList.md
Normal file
27
doc/api_methods/DeleteList.md
Normal file
@ -0,0 +1,27 @@
|
||||
[back to list](../Readme.md)
|
||||
|
||||
# Delete List
|
||||
|
||||
## `bool deleteList(string $list_id)`
|
||||
|
||||
This method provides functionality for deleting a list that is of the type 'default'.
|
||||
|
||||
It returns a boolean value.
|
||||
|
||||
## Error handling
|
||||
|
||||
All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`.
|
||||
Code of the exception is populated to distinguish between different errors.
|
||||
|
||||
An exception of base class `\Exception` can be thrown when something unexpected happens.
|
||||
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| ---- | --------------------------------------------------------------- |
|
||||
| 5 | List does not exist |
|
||||
| 18 | List id is empty |
|
||||
| 20 | List cannot be deleted because it’s used for an automatic email |
|
||||
| 21 | List cannot be deleted because it’s used for a form |
|
||||
| 22 | The list couldn’t be deleted from the database |
|
||||
| 23 | Only lists of the type 'default' can be deleted |
|
@ -19,7 +19,7 @@ This method returns a list of subscribers. To see the subscriber data structure,
|
||||
Filter argument supports following array keys.
|
||||
|
||||
| Key | Type | Description |
|
||||
| -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| ------------ | ------------ | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| status | string | Specific status of subscribers. One of values: `unconfirmed`, `subscribed`, `unsubscribed`, `bounced`, `inactive` |
|
||||
| list_id | int | List id or dynamic segment id |
|
||||
| min_updated_at | DateTime\int | DateTime object or timestamp of the minimal last update of subscribers |
|
||||
| listId | int | List id or dynamic segment id |
|
||||
| minUpdatedAt | DateTime\int | DateTime object or timestamp of the minimal last update of subscribers |
|
||||
|
39
doc/api_methods/UpdateList.md
Normal file
39
doc/api_methods/UpdateList.md
Normal file
@ -0,0 +1,39 @@
|
||||
[back to list](../Readme.md)
|
||||
|
||||
# Add Subscriber
|
||||
|
||||
## `array updateList(array $list)`
|
||||
|
||||
This method provides functionality for updating a list name or description. Only lists of type 'default' are supported.
|
||||
|
||||
It returns the updated list. See [Get Lists](GetLists.md) for a list data structure description.
|
||||
|
||||
## Arguments
|
||||
|
||||
### `$list` (required)
|
||||
|
||||
An associative array which contains list data.
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| ---------------------- | ------------ | --------- | -------------------------- |
|
||||
| id (required) | string | 11 chars | A id of the list. |
|
||||
| name (required) | string | 90 chars | A name of the list. |
|
||||
| description (optional) | string\|null | 250 chars | A description of the list. |
|
||||
|
||||
## Error handling
|
||||
|
||||
All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`.
|
||||
Code of the exception is populated to distinguish between different errors.
|
||||
|
||||
An exception of base class `\Exception` can be thrown when something unexpected happens.
|
||||
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ----------------------------------------------- |
|
||||
| 5 | The list was not found by id |
|
||||
| 14 | Missing list name |
|
||||
| 15 | Trying to use a list name that is already used |
|
||||
| 18 | Missing list id |
|
||||
| 19 | The list couldn’t be updated in the database |
|
||||
| 23 | Only lists of the type 'default' can be updated |
|
@ -5,7 +5,6 @@
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-typescript-to-proptypes",
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
|
@ -117,11 +117,45 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
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()
|
||||
->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'
|
||||
)->run();
|
||||
|
||||
// HTML, HBS
|
||||
->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() {
|
||||
@ -351,6 +385,25 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->say("Validator metadata generated to: $validatorMetadataDir");
|
||||
}
|
||||
|
||||
public function migrationsNew() {
|
||||
$generator = new \MailPoet\Migrator\Repository();
|
||||
$result = $generator->create();
|
||||
$path = realpath($result['path']);
|
||||
$this->output->writeln('MAILPOET DATABASE MIGRATIONS');
|
||||
$this->output->writeln("============================\n");
|
||||
$this->output->writeln("New migration created ✔\n");
|
||||
$this->output->writeln(" Name: {$result['name']}");
|
||||
$this->output->writeln(" Path: $path");
|
||||
}
|
||||
|
||||
public function migrationsStatus() {
|
||||
return $this->taskExec('vendor/bin/wp mailpoet:migrations:status');
|
||||
}
|
||||
|
||||
public function migrationsRun() {
|
||||
return $this->taskExec('vendor/bin/wp mailpoet:migrations:run');
|
||||
}
|
||||
|
||||
public function qa() {
|
||||
$collection = $this->collectionBuilder();
|
||||
$collection->addCode([$this, 'qaPhp']);
|
||||
@ -746,6 +799,9 @@ class RoboFile extends \Robo\Tasks {
|
||||
->addCode(function () use ($version) {
|
||||
$this->releaseCreatePullRequest($version);
|
||||
})
|
||||
->addCode(function () use ($version) {
|
||||
$this->releaseRerunCircleWorkflow(\MailPoetTasks\Release\CircleCiController::PROJECT_PREMIUM);
|
||||
})
|
||||
->addCode(function () use ($version) {
|
||||
$this->translationsPrepareLanguagePacks($version);
|
||||
})
|
||||
@ -1063,6 +1119,18 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->say("Release '$version[name]' info was published on Slack.");
|
||||
}
|
||||
|
||||
public function releaseRerunCircleWorkflow(string $project = null) {
|
||||
$circleciController = $this->createCircleCiController();
|
||||
$result = $circleciController->rerunLatestWorkflow($project);
|
||||
// Sometimes can be useful to know which Circle project workflow was restarted
|
||||
$project = $project ? " for the project '{$project}'" : '';
|
||||
if (!$result) {
|
||||
$this->yell("Circle Workflow{$project} was not restarted", 40, 'red');
|
||||
} else {
|
||||
$this->say("Circle Workflow{$project} was started from the beginning");
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadWooCommerceBlocksZip($tag = null) {
|
||||
$this->createWpOrgDownloader('woo-gutenberg-products-block')
|
||||
->downloadPluginZip('woo-gutenberg-products-block.zip', __DIR__ . '/tests/plugins/', $tag);
|
||||
@ -1087,20 +1155,10 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
public function downloadWooCommerceZip($tag = null) {
|
||||
if ($tag === 'woo-cot-beta') {
|
||||
$this->downloadWooCommerceCotZip();
|
||||
return;
|
||||
}
|
||||
$this->createWpOrgDownloader('woocommerce')
|
||||
->downloadPluginZip('woocommerce.zip', __DIR__ . '/tests/plugins/', $tag);
|
||||
}
|
||||
|
||||
public function downloadWooCommerceCotZip() {
|
||||
$cotBuildUrl = 'https://github.com/woocommerce/woocommerce/files/9706609/woocommerce.zip';
|
||||
file_put_contents(__DIR__ . '/tests/plugins/woocommerce.zip', file_get_contents($cotBuildUrl));
|
||||
file_put_contents(__DIR__ . '/tests/plugins/woocommerce.zip-info', $cotBuildUrl);
|
||||
}
|
||||
|
||||
public function generateData($generatorName = null, $threads = 1) {
|
||||
require_once __DIR__ . '/tests/DataGenerator/_bootstrap.php';
|
||||
$generator = new \MailPoet\Test\DataGenerator\DataGenerator(new \Codeception\Lib\Console\Output([]));
|
||||
|
@ -1,4 +1,4 @@
|
||||
.mailpoet-automation-workflow-add-trigger {
|
||||
.mailpoet-automation-add-trigger {
|
||||
align-items: center;
|
||||
border: 1px dashed #c3c4c7;
|
||||
border-radius: 4px;
|
||||
|
@ -1,14 +1,14 @@
|
||||
.mailpoet-automation-editor-workflow {
|
||||
.mailpoet-automation-editor-automation {
|
||||
background: #fbfbfb;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mailpoet-automation-editor-workflow-wrapper {
|
||||
.mailpoet-automation-editor-automation-wrapper {
|
||||
display: grid;
|
||||
padding: 50px 20px;
|
||||
}
|
||||
|
||||
.mailpoet-automation-editor-workflow-end {
|
||||
.mailpoet-automation-editor-automation-end {
|
||||
background: #8c8f94;
|
||||
border-radius: 999999px;
|
||||
fill: white;
|
@ -1,4 +1,4 @@
|
||||
.mailpoet-automation-editor-empty-workflow {
|
||||
.mailpoet-automation-editor-empty-automation {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
height: 100%;
|
@ -30,3 +30,31 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-is-onboarding {
|
||||
.notice {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-listing-heading {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@ -15,6 +21,21 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mailpoet-automation-listing-cell-name {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
> a:only-child {
|
||||
bottom: 2px;
|
||||
display: flex;
|
||||
left: 0;
|
||||
padding: 16px 24px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-filter-tab-panel {
|
||||
background-color: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
|
@ -0,0 +1,201 @@
|
||||
@mixin full-width {
|
||||
margin-left: -20px;
|
||||
padding-left: 104px;
|
||||
padding-right: 104px;
|
||||
width: calc(100% + 60px);
|
||||
|
||||
@media screen and (max-width: 782px) {
|
||||
margin-left: -10px;
|
||||
width: calc(100% + 34px);
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-section {
|
||||
@include full-width;
|
||||
}
|
||||
|
||||
.mailpoet-automation-white-background {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.mailpoet-automation-section-content {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1072px;
|
||||
padding: 65px 0;
|
||||
|
||||
h2 {
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
margin: 0;
|
||||
padding: 0 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-section-hero {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: -20px;
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-top: 16px;
|
||||
max-width: 100%;
|
||||
width: 532px;
|
||||
|
||||
@media screen and (min-width: 1305px) {
|
||||
height: 100%;
|
||||
margin-top: 0;
|
||||
max-height: 294px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-preheading {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
letter-spacing: .2px;
|
||||
line-height: 16px;
|
||||
margin-bottom: 32px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.mailpoet-section-templates {
|
||||
padding: 48px 0;
|
||||
|
||||
.components-button {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
text-underline-offset: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-section-template-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40px;
|
||||
|
||||
> li {
|
||||
flex-grow: 1;
|
||||
margin-right: 8px;
|
||||
max-width: 336px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 0;
|
||||
color: #1d2327;
|
||||
cursor: pointer;
|
||||
padding: 24px;
|
||||
text-align: left;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-section-build-list-button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mailpoet-section-build-your-own {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
ol {
|
||||
list-style: decimal-leading-zero inside;
|
||||
margin: 0;
|
||||
max-width: 373px;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
border-bottom: 1px solid #dcdcde;
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
grid-template-columns: 16px auto;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
&.open {
|
||||
p {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mailpoet-section-build-list-button {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.marker {
|
||||
color: #ff5301;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
display: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
.mailpoet-option-button {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mailpoet-option-button-main {
|
||||
border-radius: 2px 0 0 2px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.mailpoet-option-button-opener {
|
||||
background: var(--wp-admin-theme-color);
|
||||
border-radius: 0 2px 2px 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mailpoet-option-button-opener svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.mailpoet-option-button-opener .is-opened svg {
|
||||
transform: scale(-1, -1);
|
||||
transform-origin: center 12.5px;
|
||||
}
|
@ -119,6 +119,10 @@
|
||||
color: $color-stats-average;
|
||||
}
|
||||
|
||||
.mailpoet-statistics-value-number-critical {
|
||||
color: $color-stats-critical;
|
||||
}
|
||||
|
||||
.mailpoet-statistics-value-number-excellent {
|
||||
color: $color-stats-excellent;
|
||||
}
|
||||
|
@ -63,6 +63,11 @@ $form-line-height: 1.4;
|
||||
.mailpoet-has-font-size {
|
||||
line-height: $form-line-height;
|
||||
}
|
||||
|
||||
.mailpoet_submit {
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset fieldset styles in form for backward compatibility. */
|
||||
|
@ -41,6 +41,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-tag-critical {
|
||||
border-color: $color-stats-critical;
|
||||
color: $color-stats-critical;
|
||||
|
||||
&.mailpoet-tag-inverted {
|
||||
background: $color-stats-critical;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-tag-good {
|
||||
border-color: $color-stats-good;
|
||||
color: $color-stats-good;
|
||||
|
@ -11,17 +11,17 @@
|
||||
|
||||
@import './components-automation-editor/add-step-button';
|
||||
@import './components-automation-editor/add-trigger';
|
||||
@import './components-automation-editor/automation';
|
||||
@import './components-automation-editor/block-icon';
|
||||
@import './components-automation-editor/chip';
|
||||
@import './components-automation-editor/dropdown';
|
||||
@import './components-automation-editor/empty-workflow';
|
||||
@import './components-automation-editor/empty-automation';
|
||||
@import './components-automation-editor/errors';
|
||||
@import './components-automation-editor/panel';
|
||||
@import './components-automation-editor/separator';
|
||||
@import './components-automation-editor/status';
|
||||
@import './components-automation-editor/step';
|
||||
@import './components-automation-editor/step-card';
|
||||
@import './components-automation-editor/workflow';
|
||||
@import './components-automation-editor/notices';
|
||||
@import './components-automation-editor/deactivate-modal';
|
||||
|
||||
|
@ -13,18 +13,34 @@ ul.mailpoet-automation-templates {
|
||||
margin: auto;
|
||||
max-width: 982px;
|
||||
padding: 48px 0;
|
||||
}
|
||||
|
||||
.mailpoet-automation-template-list-item {
|
||||
button.components-button {
|
||||
align-content: baseline;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
display: grid;
|
||||
grid-template-rows: 40px auto auto;
|
||||
height: 100%;
|
||||
padding: 24px 24px 26px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&:disabled,
|
||||
&[aria-disabled='true'] {
|
||||
color: #787c82;
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
|
||||
h2 {
|
||||
color: #787c82;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
@ -37,12 +53,15 @@ ul.mailpoet-automation-templates {
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, .15);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
>* {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #2271b1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 21px;
|
||||
@ -54,7 +73,7 @@ ul.mailpoet-automation-templates {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
.mailpoet-automation-from-scratch {
|
||||
&.mailpoet-automation-from-scratch {
|
||||
button {
|
||||
align-content: center;
|
||||
border: 2px dashed #dcdcde;
|
||||
@ -71,4 +90,27 @@ ul.mailpoet-automation-templates {
|
||||
fill: #dcdcde;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
text-align: right;
|
||||
transform: translateX(24px);
|
||||
|
||||
span {
|
||||
padding: 3px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-template-list-item-coming-soon {
|
||||
.badge span {
|
||||
background: #ffe9cc;
|
||||
color: #1d2327;
|
||||
}
|
||||
}
|
||||
|
||||
.mailpoet-automation-template-list-item-premium {
|
||||
.badge span {
|
||||
background: #ff5301;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,14 @@
|
||||
// automation components
|
||||
|
||||
@import './components-automation/statistics';
|
||||
@import './components-automation/option-button';
|
||||
|
||||
// automation listing
|
||||
|
||||
@import './components-automation-listing/sections';
|
||||
@import './components-automation-listing/listing';
|
||||
@import './components-automation-listing/header';
|
||||
@import './components-automation-listing/search';
|
||||
@import './components-automation-listing/cells/actions';
|
||||
@import './components-automation-listing/cells/status';
|
||||
@import './mailpoet-automation-templates';
|
||||
|
@ -69,6 +69,7 @@ $color-badge-video-guide: #46b450;
|
||||
$color-stats-average: #f559c3;
|
||||
$color-stats-good: #ff9f00;
|
||||
$color-stats-excellent: #7ed321;
|
||||
$color-stats-critical: #f00;
|
||||
$color-stats-unknown: $color-primary-inactive;
|
||||
|
||||
// Automation editor
|
||||
|
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 |
@ -1,81 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { api } from '../config';
|
||||
|
||||
const API_URL = `${api.root}/mailpoet/v1/automation`;
|
||||
|
||||
export const request = (
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): ReturnType<typeof fetch> => fetch(`${API_URL}/${path}`, init);
|
||||
|
||||
type Error<T> = {
|
||||
response?: Response;
|
||||
data?: T;
|
||||
};
|
||||
|
||||
type State<T> = {
|
||||
data?: T;
|
||||
loading: boolean;
|
||||
error?: Error<T>;
|
||||
};
|
||||
|
||||
type Result<T> = [(init?: RequestInit) => Promise<void>, State<T>];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Data = Record<string, any>;
|
||||
|
||||
export const useMutation = <T extends Data>(
|
||||
path: string,
|
||||
config?: RequestInit,
|
||||
): Result<T> => {
|
||||
const [state, setState] = useState<State<T>>({
|
||||
data: undefined,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const mutation = useCallback(
|
||||
async (init?: RequestInit) => {
|
||||
setState((prevState) => ({ ...prevState, loading: true }));
|
||||
const response = await request(path, {
|
||||
...config,
|
||||
...init,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...(init?.headers ?? {}),
|
||||
'x-wp-nonce': api.nonce,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await response.json();
|
||||
const error = response.ok ? null : { ...response, data };
|
||||
setState((prevState) => ({ ...prevState, data, error }));
|
||||
} catch (_) {
|
||||
const error = { response };
|
||||
setState((prevState) => ({ ...prevState, error }));
|
||||
} finally {
|
||||
setState((prevState) => ({ ...prevState, loading: false }));
|
||||
}
|
||||
},
|
||||
[config, path],
|
||||
);
|
||||
|
||||
return [mutation, state];
|
||||
};
|
||||
|
||||
export const useQuery = <T extends Data>(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): State<T> => {
|
||||
const [mutation, result] = useMutation<T>(path, init);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
void mutation();
|
||||
},
|
||||
[] /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */,
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { api } from '../config';
|
||||
|
||||
export * from './hooks';
|
||||
|
||||
const apiUrl = `${api.root}/mailpoet/v1/automation/`;
|
||||
const apiUrl = `${api.root}/mailpoet/v1/`;
|
||||
|
||||
export type ApiError = {
|
||||
code?: string;
|
||||
|
@ -1,94 +1,70 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { TopBarWithBeamer } from 'common/top_bar/top_bar';
|
||||
import { plusIcon } from 'common/button/icon/plus';
|
||||
import { Button, Flex, Popover, SlotFillProvider } from '@wordpress/components';
|
||||
import { Popover, SlotFillProvider } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { initializeApi, useMutation } from './api';
|
||||
import { initializeApi } from './api';
|
||||
import { registerTranslations } from './i18n';
|
||||
import { createStore, storeName } from './listing/store';
|
||||
import { AutomationListing } from './listing';
|
||||
import { AutomationListing, AutomationListingHeader } 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 {
|
||||
CreateEmptyWorkflowButton,
|
||||
CreateWorkflowFromTemplateButton,
|
||||
} from './testing';
|
||||
import { BuildYourOwnSection, HeroSection, TemplatesSection } from './sections';
|
||||
import { MailPoet } from '../mailpoet';
|
||||
|
||||
function Content(): JSX.Element {
|
||||
const count = useSelect((select) => select(storeName).getWorkflowCount());
|
||||
return count > 0 ? <AutomationListing /> : <Onboarding />;
|
||||
}
|
||||
const trackOpenEvent = () => {
|
||||
MailPoet.trackEvent('Automations > Listing viewed');
|
||||
};
|
||||
|
||||
function Workflows(): JSX.Element {
|
||||
function Content(): JSX.Element {
|
||||
const [isBooting, setIsBooting] = useState(true);
|
||||
const count = useSelect((select) => select(storeName).getAutomationCount());
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBooting || count === 0) {
|
||||
return;
|
||||
}
|
||||
trackOpenEvent();
|
||||
setIsBooting(false);
|
||||
}, [isBooting, count]);
|
||||
const content =
|
||||
count > 0 ? (
|
||||
<>
|
||||
<AutomationListingHeader />
|
||||
<AutomationListing />
|
||||
</>
|
||||
) : (
|
||||
<HeroSection />
|
||||
);
|
||||
|
||||
// Hide notices on onboarding screen
|
||||
useEffect(() => {
|
||||
const onboardingClass = 'mailpoet-automation-is-onboarding';
|
||||
const element = document.querySelector('body');
|
||||
if (count === 0 && !element.classList.contains(onboardingClass)) {
|
||||
element.classList.add(onboardingClass);
|
||||
}
|
||||
if (count > 0 && element.classList.contains(onboardingClass)) {
|
||||
element.classList.remove(onboardingClass);
|
||||
}
|
||||
}, [count]);
|
||||
return (
|
||||
<>
|
||||
<TopBarWithBeamer />
|
||||
<Flex className="mailpoet-automation-listing-heading">
|
||||
<h1 className="wp-heading-inline">Automations</h1>
|
||||
<Button
|
||||
href={MailPoet.urls.automationTemplates}
|
||||
icon={plusIcon}
|
||||
variant="primary"
|
||||
className="mailpoet-add-new-button"
|
||||
>
|
||||
New automation
|
||||
</Button>
|
||||
</Flex>
|
||||
<Notices />
|
||||
<Content />
|
||||
{content}
|
||||
<TemplatesSection />
|
||||
<BuildYourOwnSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RecreateSchemaButton(): JSX.Element {
|
||||
const [createSchema, { loading, error }] = useMutation('system/database', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
function Automations(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<WorkflowListingNotices />
|
||||
<button
|
||||
className="button button-link-delete"
|
||||
type="button"
|
||||
onClick={() => createSchema()}
|
||||
disabled={loading}
|
||||
>
|
||||
Recreate DB schema (data will be lost)
|
||||
</button>
|
||||
{error && (
|
||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteSchemaButton(): JSX.Element {
|
||||
const [deleteSchema, { loading, error }] = useMutation('system/database', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="button button-link-delete"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await deleteSchema();
|
||||
window.location.href =
|
||||
'/wp-admin/admin.php?page=mailpoet-experimental';
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
Delete DB schema & deactivate feature
|
||||
</button>
|
||||
{error && (
|
||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<TopBarWithBeamer />
|
||||
<Notices />
|
||||
<Content />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -96,26 +72,8 @@ function App(): JSX.Element {
|
||||
return (
|
||||
<SlotFillProvider>
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Workflows />
|
||||
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
|
||||
<CreateEmptyWorkflowButton />
|
||||
<CreateWorkflowFromTemplateButton slug="simple-welcome-email">
|
||||
Create testing workflow from template (welcome email)
|
||||
</CreateWorkflowFromTemplateButton>
|
||||
<CreateWorkflowFromTemplateButton slug="welcome-email-sequence">
|
||||
Create testing workflow from template (welcome sequence, only
|
||||
premium)
|
||||
</CreateWorkflowFromTemplateButton>
|
||||
<CreateWorkflowFromTemplateButton slug="advanced-welcome-email-sequence">
|
||||
Create testing workflow from template (advanced welcome sequence,
|
||||
only premium)
|
||||
</CreateWorkflowFromTemplateButton>
|
||||
<RecreateSchemaButton />
|
||||
<DeleteSchemaButton />
|
||||
</div>
|
||||
<Automations />
|
||||
<Popover.Slot />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
@ -126,6 +84,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const root = document.getElementById('mailpoet_automation');
|
||||
if (root) {
|
||||
registerTranslations();
|
||||
registerApiErrorHandler();
|
||||
initializeApi();
|
||||
ReactDOM.render(<App />, root);
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { Button, DropdownMenu } from '@wordpress/components';
|
||||
import { chevronDown } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import { StepMoreControlsType } from '../../types/filters';
|
||||
|
||||
type OptionButtonPropType = {
|
||||
variant: Button.ButtonVariant;
|
||||
controls: StepMoreControlsType;
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
export function OptionButton({
|
||||
controls,
|
||||
title,
|
||||
onClick,
|
||||
variant,
|
||||
}: OptionButtonPropType): JSX.Element {
|
||||
const slots = Object.values(controls).filter((item) => item.slot);
|
||||
return (
|
||||
<div className="mailpoet-option-button">
|
||||
<Button
|
||||
variant={variant}
|
||||
className="mailpoet-option-button-main"
|
||||
onClick={onClick}
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
{slots.length > 0 &&
|
||||
slots.map(({ key, slot }) => (
|
||||
<Fragment key={`slot-${key}`}>{slot}</Fragment>
|
||||
))}
|
||||
{Object.values(controls).length > 0 && (
|
||||
<DropdownMenu
|
||||
className="mailpoet-option-button-opener"
|
||||
label={__('More', 'mailpoet')}
|
||||
icon={chevronDown}
|
||||
controls={Object.values(controls).map((item) => item.control)}
|
||||
popoverProps={{ position: 'bottom left' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -4,9 +4,9 @@ declare global {
|
||||
root: string;
|
||||
nonce: string;
|
||||
};
|
||||
mailpoet_workflow_count: number;
|
||||
mailpoet_automation_count: number;
|
||||
}
|
||||
}
|
||||
|
||||
export const api = window.mailpoet_automation_api;
|
||||
export const workflowCount = window.mailpoet_workflow_count;
|
||||
export const automationCount = window.mailpoet_automation_count;
|
||||
|
@ -19,7 +19,7 @@ export const registerApiErrorHandler = (): void =>
|
||||
const status = errorObject.data?.status;
|
||||
const code = errorObject.code;
|
||||
|
||||
if (code === 'mailpoet_automation_workflow_not_valid') {
|
||||
if (code === 'mailpoet_automation_not_valid') {
|
||||
dispatch(storeName).setErrors({ steps: errorObject.data.errors });
|
||||
return undefined;
|
||||
}
|
||||
@ -30,6 +30,7 @@ export const registerApiErrorHandler = (): void =>
|
||||
message ?? __('An unknown error occurred.', 'mailpoet'),
|
||||
{ explicitDismiss: true },
|
||||
);
|
||||
dispatch(storeName).setErrors({ steps: [] });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,14 @@ import {
|
||||
Button,
|
||||
} from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
export function TrashButton(): JSX.Element {
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -20,8 +21,8 @@ export function TrashButton(): JSX.Element {
|
||||
<>
|
||||
<ConfirmDialog
|
||||
isOpen={showConfirmDialog}
|
||||
title="Delete workflow"
|
||||
confirmButtonText="Yes, delete"
|
||||
title={__('Delete automation', 'mailpoet')}
|
||||
confirmButtonText={__('Yes, delete', 'mailpoet')}
|
||||
onConfirm={async () => {
|
||||
trash(() => {
|
||||
setShowConfirmDialog(false);
|
||||
@ -30,7 +31,12 @@ export function TrashButton(): JSX.Element {
|
||||
onCancel={() => setShowConfirmDialog(false)}
|
||||
__experimentalHideHeader={false}
|
||||
>
|
||||
You are about to delete the “{workflow.name}” workflow.
|
||||
{sprintf(
|
||||
__('You are about to delete the automation "%s".', 'mailpoet'),
|
||||
automation.name,
|
||||
)}
|
||||
<br />
|
||||
{__(' This will stop it for all subscribers immediately.', 'mailpoet')}
|
||||
</ConfirmDialog>
|
||||
|
||||
<Button
|
||||
@ -38,7 +44,7 @@ export function TrashButton(): JSX.Element {
|
||||
isDestructive
|
||||
onClick={() => setShowConfirmDialog(true)}
|
||||
>
|
||||
Move to Trash
|
||||
{__('Move to Trash', 'mailpoet')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
||||
import { Icon, plus } from '@wordpress/icons';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
|
||||
type Props = {
|
||||
onClick?: (element: HTMLButtonElement) => void;
|
||||
@ -9,7 +9,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function AddStepButton({ onClick, previousStepId }: Props): JSX.Element {
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
return (
|
||||
<CompositeItem
|
||||
state={compositeState}
|
@ -3,7 +3,7 @@ import { __unstableCompositeItem as CompositeItem } from '@wordpress/components'
|
||||
import { Icon, plus } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { Step } from './types';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
@ -12,14 +12,14 @@ type Props = {
|
||||
};
|
||||
|
||||
export function AddTrigger({ step }: Props): JSX.Element {
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
const { setInserterPopover } = useDispatch(storeName);
|
||||
|
||||
return (
|
||||
<CompositeItem
|
||||
state={compositeState}
|
||||
role="treeitem"
|
||||
className="mailpoet-automation-workflow-add-trigger"
|
||||
className="mailpoet-automation-add-trigger"
|
||||
data-previous-step-id={step.id}
|
||||
focusable
|
||||
onClick={(event) => {
|
@ -1,5 +1,5 @@
|
||||
import { __unstableUseCompositeState as useCompositeState } from '@wordpress/components';
|
||||
import { createContext } from '@wordpress/element';
|
||||
|
||||
export const WorkflowCompositeContext =
|
||||
export const AutomationCompositeContext =
|
||||
createContext<ReturnType<typeof useCompositeState>>(undefined);
|
@ -0,0 +1,9 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export function EmptyAutomation(): JSX.Element {
|
||||
return (
|
||||
<div className="mailpoet-automation-editor-empty-automation">
|
||||
{__('No automation data.', 'mailpoet')}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -7,8 +7,8 @@ import { useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, check } from '@wordpress/icons';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { EmptyWorkflow } from './empty-workflow';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { EmptyAutomation } from './empty-automation';
|
||||
import { Separator } from './separator';
|
||||
import { Step } from './step';
|
||||
import { Step as StepData } from './types';
|
||||
@ -16,11 +16,15 @@ import { InserterPopover } from '../inserter-popover';
|
||||
import { storeName } from '../../store';
|
||||
import { AddTrigger } from './add-trigger';
|
||||
import { Statistics } from './statistics';
|
||||
import {
|
||||
RenderStepSeparatorType,
|
||||
RenderStepType,
|
||||
} from '../../../types/filters';
|
||||
|
||||
export function Workflow(): JSX.Element {
|
||||
const { workflowData, selectedStep } = useSelect(
|
||||
export function Automation(): JSX.Element {
|
||||
const { automationData, selectedStep } = useSelect(
|
||||
(select) => ({
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
}),
|
||||
[],
|
||||
@ -32,9 +36,9 @@ export function Workflow(): JSX.Element {
|
||||
shift: true,
|
||||
});
|
||||
|
||||
const stepMap = workflowData?.steps ?? undefined;
|
||||
const stepMap = automationData?.steps ?? undefined;
|
||||
|
||||
// serialize steps (for now, we support only one trigger and linear workflows)
|
||||
// serialize steps (for now, we support only one trigger and linear automations)
|
||||
const steps = useMemo(() => {
|
||||
const stepArray = [stepMap.root];
|
||||
|
||||
@ -50,9 +54,9 @@ export function Workflow(): JSX.Element {
|
||||
}, [stepMap]);
|
||||
|
||||
const renderStep = useMemo(
|
||||
() =>
|
||||
(): RenderStepType =>
|
||||
Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.render_step',
|
||||
'mailpoet.automation.render_step',
|
||||
(stepData: StepData) =>
|
||||
stepData.type === 'root' ? (
|
||||
<AddTrigger step={stepData} />
|
||||
@ -67,9 +71,9 @@ export function Workflow(): JSX.Element {
|
||||
);
|
||||
|
||||
const renderSeparator = useMemo(
|
||||
() =>
|
||||
(): RenderStepSeparatorType =>
|
||||
Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.render_step_separator',
|
||||
'mailpoet.automation.render_step_separator',
|
||||
(previousStepData: StepData) => (
|
||||
<Separator previousStepId={previousStepData.id} />
|
||||
),
|
||||
@ -77,20 +81,20 @@ export function Workflow(): JSX.Element {
|
||||
[],
|
||||
);
|
||||
|
||||
if (!workflowData) {
|
||||
return <EmptyWorkflow />;
|
||||
if (!automationData) {
|
||||
return <EmptyAutomation />;
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkflowCompositeContext.Provider value={compositeState}>
|
||||
<AutomationCompositeContext.Provider value={compositeState}>
|
||||
<Composite
|
||||
state={compositeState}
|
||||
role="tree"
|
||||
aria-label={__('Workflow', 'mailpoet')}
|
||||
aria-label={__('Automation', 'mailpoet')}
|
||||
aria-orientation="vertical"
|
||||
className="mailpoet-automation-editor-workflow"
|
||||
className="mailpoet-automation-editor-automation"
|
||||
>
|
||||
<div className="mailpoet-automation-editor-workflow-wrapper">
|
||||
<div className="mailpoet-automation-editor-automation-wrapper">
|
||||
<Statistics />
|
||||
{stepMap.root.next_steps.length === 0 ? (
|
||||
<>
|
||||
@ -115,13 +119,13 @@ export function Workflow(): JSX.Element {
|
||||
</Fragment>
|
||||
))}
|
||||
<Icon
|
||||
className="mailpoet-automation-editor-workflow-end"
|
||||
className="mailpoet-automation-editor-automation-end"
|
||||
icon={check}
|
||||
/>
|
||||
<div />
|
||||
</div>
|
||||
<InserterPopover />
|
||||
</Composite>
|
||||
</WorkflowCompositeContext.Provider>
|
||||
</AutomationCompositeContext.Provider>
|
||||
);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { _x } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store';
|
||||
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
||||
|
||||
export function Statistics(): JSX.Element {
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mailpoet-automation-editor-stats">
|
||||
<BaseStatistics
|
||||
items={[
|
||||
{
|
||||
key: 'entered',
|
||||
// translators: Total number of subscribers who entered an automation
|
||||
label: _x('Total Entered', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.entered,
|
||||
},
|
||||
{
|
||||
key: 'processing',
|
||||
// translators: Total number of subscribers who are being processed in an automation
|
||||
label: _x('Total Processing', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.in_progress,
|
||||
},
|
||||
{
|
||||
key: 'exited',
|
||||
// translators: Total number of subscribers who exited an automation, no matter the result
|
||||
label: _x('Total Exited', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.exited,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import { useState, Fragment } from 'react';
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { moreVertical, trash } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { PremiumModal } from 'common/premium_modal';
|
||||
import { Step as StepData } from './types';
|
||||
import { storeName } from '../../store';
|
||||
import { StepMoreControlsType } from '../../../types/filters';
|
||||
|
||||
type Props = {
|
||||
step: StepData;
|
||||
};
|
||||
|
||||
export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
const { stepType } = useSelect(
|
||||
(select) => ({
|
||||
stepType: select(storeName).getStepType(step.key),
|
||||
}),
|
||||
[step],
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const moreControls: StepMoreControlsType = Hooks.applyFilters(
|
||||
'mailpoet.automation.step.more-controls',
|
||||
{
|
||||
delete: {
|
||||
key: 'delete',
|
||||
control: {
|
||||
title: __('Delete step', 'mailpoet'),
|
||||
icon: trash,
|
||||
onClick: () => setShowModal(true),
|
||||
},
|
||||
slot: () => {
|
||||
if (!showModal) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<PremiumModal
|
||||
onRequestClose={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'remove_automation_step',
|
||||
}}
|
||||
>
|
||||
{__('You cannot remove a step from the automation.', 'mailpoet')}
|
||||
</PremiumModal>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
step,
|
||||
stepType,
|
||||
);
|
||||
|
||||
const slots = Object.values(moreControls).filter(
|
||||
(item) => item.slot !== undefined,
|
||||
);
|
||||
const controls = Object.values(moreControls).map((item) => item.control);
|
||||
return (
|
||||
<div className="mailpoet-automation-step-more-menu">
|
||||
{slots.map(({ key, slot }) => (
|
||||
<Fragment key={key}>{slot()}</Fragment>
|
||||
))}
|
||||
<DropdownMenu
|
||||
label={__('More', 'mailpoet')}
|
||||
icon={moreVertical}
|
||||
popoverProps={{ position: 'bottom right' }}
|
||||
toggleProps={{ isSmall: true }}
|
||||
controls={Object.values(controls)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -3,8 +3,8 @@ import { useContext } from 'react';
|
||||
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
|
||||
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
|
||||
import { blockMeta } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { WorkflowCompositeContext } from './context';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { AutomationCompositeContext } from './context';
|
||||
import { StepMoreMenu } from './step-more-menu';
|
||||
import { Step as StepData } from './types';
|
||||
import { Chip } from '../chip';
|
||||
@ -48,7 +48,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
||||
[step],
|
||||
);
|
||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||
const compositeState = useContext(WorkflowCompositeContext);
|
||||
const compositeState = useContext(AutomationCompositeContext);
|
||||
const { batch } = useRegistry();
|
||||
|
||||
const compositeItemId = `step-${step.id}`;
|
||||
@ -90,7 +90,7 @@ export function Step({ step, isSelected }: Props): JSX.Element {
|
||||
>
|
||||
{step.type !== 'trigger'
|
||||
? stepTypeData.title
|
||||
: __('Trigger', 'mailpoet')}
|
||||
: _x('Trigger', 'noun', 'mailpoet')}
|
||||
</label>
|
||||
<div className="mailpoet-automation-editor-step-subtitle">
|
||||
{step.type !== 'trigger'
|
@ -1,3 +1,5 @@
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
|
||||
export type NextStep = {
|
||||
id: string;
|
||||
};
|
||||
@ -10,10 +12,10 @@ export type Step = {
|
||||
next_steps: NextStep[];
|
||||
};
|
||||
|
||||
export type Workflow = {
|
||||
export type Automation = {
|
||||
id: number;
|
||||
name: string;
|
||||
status: 'active' | 'inactive' | 'draft' | 'trash';
|
||||
status: AutomationStatus;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
activated_at: string;
|
@ -1,16 +1,16 @@
|
||||
import { ComponentProps, ComponentType, Ref } from 'react';
|
||||
import {
|
||||
Dropdown as WpDropdown,
|
||||
Button,
|
||||
VisuallyHidden,
|
||||
__experimentalText as Text,
|
||||
Button,
|
||||
Dropdown as WpDropdown,
|
||||
VisuallyHidden,
|
||||
} from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useRef } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chevronDown } from '@wordpress/icons';
|
||||
import { storeName } from '../../store';
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
|
||||
// See: https://github.com/WordPress/gutenberg/blob/eff0cab2b3181c004dbd15398e570ecec28a3726/packages/edit-site/src/components/header/document-actions/index.js
|
||||
|
||||
@ -22,10 +22,10 @@ const Dropdown: ComponentType<
|
||||
> = WpDropdown;
|
||||
|
||||
export function DocumentActions({ children }): JSX.Element {
|
||||
const { workflowName, workflowStatus, showIconLabels } = useSelect(
|
||||
const { automationName, automationStatus, showIconLabels } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
workflowStatus: select(storeName).getWorkflowData().status,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
automationStatus: select(storeName).getAutomationData().status,
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
}),
|
||||
[],
|
||||
@ -36,9 +36,9 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
const titleRef = useRef();
|
||||
|
||||
let chipClass = 'mailpoet-automation-editor-chip-gray';
|
||||
if (workflowStatus === WorkflowStatus.ACTIVE) {
|
||||
if (automationStatus === AutomationStatus.ACTIVE) {
|
||||
chipClass = 'mailpoet-automation-editor-chip-success';
|
||||
} else if (workflowStatus === WorkflowStatus.INACTIVE) {
|
||||
} else if (automationStatus === AutomationStatus.DEACTIVATING) {
|
||||
chipClass = 'mailpoet-automation-editor-chip-danger';
|
||||
}
|
||||
|
||||
@ -64,19 +64,21 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
as="h1"
|
||||
>
|
||||
<VisuallyHidden as="span">
|
||||
{__('Editing workflow: ')}
|
||||
{__('Editing automation:', 'mailpoet')}
|
||||
</VisuallyHidden>
|
||||
{workflowName}
|
||||
{automationName}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
size="body"
|
||||
className={`edit-site-document-actions__secondary-item ${chipClass}`}
|
||||
>
|
||||
{workflowStatus === WorkflowStatus.ACTIVE && __('Active')}
|
||||
{workflowStatus === WorkflowStatus.INACTIVE &&
|
||||
__('Inactive')}
|
||||
{workflowStatus === WorkflowStatus.DRAFT && __('Draft')}
|
||||
{automationStatus === AutomationStatus.ACTIVE &&
|
||||
__('Active', 'mailpoet')}
|
||||
{automationStatus === AutomationStatus.DEACTIVATING &&
|
||||
__('Deactivating', 'mailpoet')}
|
||||
{automationStatus === AutomationStatus.DRAFT &&
|
||||
__('Draft', 'mailpoet')}
|
||||
</Text>
|
||||
</a>
|
||||
<Button
|
||||
@ -85,9 +87,9 @@ export function DocumentActions({ children }): JSX.Element {
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="true"
|
||||
onClick={onToggle}
|
||||
label={__('Change workflow name')}
|
||||
label={__('Change automation name', 'mailpoet')}
|
||||
>
|
||||
{showIconLabels && __('Rename')}
|
||||
{showIconLabels && __('Rename', 'mailpoet')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
@ -12,7 +12,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { Chip } from '../chip';
|
||||
import { ColoredIcon } from '../icons';
|
||||
import {
|
||||
StepError as StepErrorType,
|
||||
StepErrors as StepErrorType,
|
||||
stepSidebarKey,
|
||||
storeName,
|
||||
} from '../../store';
|
||||
@ -35,17 +35,17 @@ type StepErrorProps = {
|
||||
function StepError({ stepId }: StepErrorProps): JSX.Element {
|
||||
const compositeState = useContext(ErrorsCompositeContext);
|
||||
|
||||
const { steps, workflowData } = useSelect(
|
||||
const { steps, automationData } = useSelect(
|
||||
(select) => ({
|
||||
steps: select(storeName).getSteps(),
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { openSidebar, selectStep } = useDispatch(storeName);
|
||||
|
||||
const stepData = workflowData.steps[stepId];
|
||||
const stepData = automationData.steps[stepId];
|
||||
const step = steps.find(({ key }) => key === stepData.key);
|
||||
|
||||
return (
|
||||
@ -78,10 +78,10 @@ export function Errors(): JSX.Element | null {
|
||||
shift: true,
|
||||
});
|
||||
|
||||
const { errors, workflowData } = useSelect(
|
||||
const { errors, automationData } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -93,18 +93,18 @@ export function Errors(): JSX.Element | null {
|
||||
}
|
||||
|
||||
const visited = new Map<string, StepErrorType | undefined>();
|
||||
const ids = workflowData.steps.root.next_steps.map(({ id }) => id);
|
||||
const ids = automationData.steps.root.next_steps.map(({ id }) => id);
|
||||
while (ids.length > 0) {
|
||||
const id = ids.shift();
|
||||
if (!visited.has(id)) {
|
||||
visited.set(id, errors.steps[id]);
|
||||
workflowData.steps[id]?.next_steps?.forEach((step) =>
|
||||
automationData.steps[id]?.next_steps?.forEach((step) =>
|
||||
ids.push(step.id),
|
||||
);
|
||||
}
|
||||
}
|
||||
return [...visited.values()].filter((error) => !!error);
|
||||
}, [errors, workflowData]);
|
||||
}, [errors, automationData]);
|
||||
|
||||
// automatically open the popover when errors appear
|
||||
const hasErrors = stepErrors.length > 0;
|
||||
@ -151,11 +151,14 @@ export function Errors(): JSX.Element | null {
|
||||
<Composite
|
||||
state={compositeState}
|
||||
role="list"
|
||||
aria-label={__('Workflow errors', 'mailpoet')}
|
||||
aria-label={__('Automation errors', 'mailpoet')}
|
||||
className="mailpoet-automation-errors"
|
||||
>
|
||||
<div className="mailpoet-automation-errors-header">
|
||||
{__('The following steps are not fully set:', 'mailpoet')}
|
||||
{
|
||||
// translators: Label for a list of automation steps that are incomplete or have errors
|
||||
__('The following steps are not fully set:', 'mailpoet')
|
||||
}
|
||||
</div>
|
||||
{stepErrors.map((error) => (
|
||||
<StepError key={error.step_id} stepId={error.step_id} />
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, NavigableMenu, TextControl } from '@wordpress/components';
|
||||
import {
|
||||
Button,
|
||||
NavigableMenu,
|
||||
TextControl,
|
||||
Tooltip,
|
||||
} from '@wordpress/components';
|
||||
import { dispatch, useDispatch, useSelect } from '@wordpress/data';
|
||||
import { PinnedItems } from '@wordpress/interface';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
@ -8,36 +13,70 @@ import { Errors } from './errors';
|
||||
import { InserterToggle } from './inserter_toggle';
|
||||
import { MoreMenu } from './more_menu';
|
||||
import { storeName } from '../../store';
|
||||
import { WorkflowStatus } from '../../../listing/workflow';
|
||||
import { DeactivateModal } from '../modals/deactivate-modal';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
import {
|
||||
DeactivateImmediatelyModal,
|
||||
DeactivateModal,
|
||||
} from '../modals/deactivate-modal';
|
||||
|
||||
// See:
|
||||
// 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
|
||||
|
||||
function ActivateButton({ onClick }): JSX.Element {
|
||||
const { errors } = useSelect(
|
||||
function ActivateButton({ label }): JSX.Element {
|
||||
const { errors, isDeactivating } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
isDeactivating:
|
||||
select(storeName).getAutomationData().status ===
|
||||
AutomationStatus.DEACTIVATING,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { openActivationPanel } = useDispatch(storeName);
|
||||
|
||||
return (
|
||||
const button = (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="editor-post-publish-button"
|
||||
onClick={onClick}
|
||||
disabled={!!errors}
|
||||
onClick={openActivationPanel}
|
||||
disabled={isDeactivating || !!errors}
|
||||
>
|
||||
{__('Activate', 'mailpoet')}
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (isDeactivating) {
|
||||
return (
|
||||
<Tooltip
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
||||
delay={0}
|
||||
text={__(
|
||||
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||
'mailpoet',
|
||||
)}
|
||||
>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
function UpdateButton(): JSX.Element {
|
||||
const { save } = useDispatch(storeName);
|
||||
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
if (automation.stats.totals.in_progress === 0) {
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -47,6 +86,28 @@ function UpdateButton(): JSX.Element {
|
||||
{__('Update', 'mailpoet')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// The following error seems to be a mismatch. It claims the 'delay' prop does not exist, but it does.
|
||||
delay={0}
|
||||
text={__(
|
||||
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||
'mailpoet',
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="editor-post-publish-button"
|
||||
onClick={save}
|
||||
disabled
|
||||
>
|
||||
{__('Update', 'mailpoet')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
function SaveDraftButton(): JSX.Element {
|
||||
@ -65,7 +126,7 @@ function DeactivateButton(): JSX.Element {
|
||||
const { hasUsersInProgress } = useSelect(
|
||||
(select) => ({
|
||||
hasUsersInProgress:
|
||||
select(storeName).getWorkflowData().stats.totals.in_progress > 0,
|
||||
select(storeName).getAutomationData().stats.totals.in_progress > 0,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -99,20 +160,56 @@ function DeactivateButton(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function DeactivateNowButton(): JSX.Element {
|
||||
const [showDeactivateModal, setShowDeactivateModal] = useState(false);
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const { hasUsersInProgress } = useSelect(
|
||||
(select) => ({
|
||||
hasUsersInProgress:
|
||||
select(storeName).getAutomationData().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 = {
|
||||
showInserterToggle: boolean;
|
||||
toggleActivatePanel: () => void;
|
||||
};
|
||||
|
||||
export function Header({
|
||||
showInserterToggle,
|
||||
toggleActivatePanel,
|
||||
}: Props): JSX.Element {
|
||||
const { setWorkflowName } = useDispatch(storeName);
|
||||
const { workflowName, workflowStatus } = useSelect(
|
||||
export function Header({ showInserterToggle }: Props): JSX.Element {
|
||||
const { setAutomationName } = useDispatch(storeName);
|
||||
const { automationName, automationStatus } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
workflowStatus: select(storeName).getWorkflowData().status,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
automationStatus: select(storeName).getAutomationData().status,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -137,8 +234,8 @@ export function Header({
|
||||
{__('Automation name', 'mailpoet')}
|
||||
</div>
|
||||
<TextControl
|
||||
value={workflowName}
|
||||
onChange={(newName) => setWorkflowName(newName)}
|
||||
value={automationName}
|
||||
onChange={(newName) => setAutomationName(newName)}
|
||||
help={__(
|
||||
`Give the automation a name that indicates its purpose. E.g. "Abandoned cart recovery"`,
|
||||
'mailpoet',
|
||||
@ -152,18 +249,24 @@ export function Header({
|
||||
<div className="edit-site-header_end">
|
||||
<div className="edit-site-header__actions">
|
||||
<Errors />
|
||||
{workflowStatus !== WorkflowStatus.ACTIVE && (
|
||||
{automationStatus === AutomationStatus.DRAFT && (
|
||||
<>
|
||||
<SaveDraftButton />
|
||||
<ActivateButton onClick={toggleActivatePanel} />
|
||||
<ActivateButton label={__('Activate', 'mailpoet')} />
|
||||
</>
|
||||
)}
|
||||
{workflowStatus === WorkflowStatus.ACTIVE && (
|
||||
{automationStatus === AutomationStatus.ACTIVE && (
|
||||
<>
|
||||
<DeactivateButton />
|
||||
<UpdateButton />
|
||||
</>
|
||||
)}
|
||||
{automationStatus === AutomationStatus.DEACTIVATING && (
|
||||
<>
|
||||
<DeactivateNowButton />
|
||||
<ActivateButton label={__('Update & Activate', 'mailpoet')} />
|
||||
</>
|
||||
)}
|
||||
<PinnedItems.Slot scope={storeName} />
|
||||
<MoreMenu />
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button, ToolbarItem } from '@wordpress/components';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import { storeName } from '../../store';
|
||||
|
||||
@ -28,13 +28,11 @@ export function InserterToggle(): JSX.Element {
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={toggleInserterSidebar}
|
||||
icon={plus}
|
||||
label={_x(
|
||||
'Toggle step inserter',
|
||||
'Generic label for step inserter button',
|
||||
)}
|
||||
label={__('Toggle step inserter', 'mailpoet')}
|
||||
showTooltip={!showIconLabels}
|
||||
>
|
||||
{showIconLabels && (!isInserterOpened ? __('Add') : __('Close'))}
|
||||
{showIconLabels &&
|
||||
(!isInserterOpened ? __('Add', 'mailpoet') : __('Close', 'mailpoet'))}
|
||||
</ToolbarItem>
|
||||
);
|
||||
}
|
||||
|
@ -20,14 +20,14 @@ export function MoreMenu(): JSX.Element {
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<MenuGroup label={_x('View', 'noun')}>
|
||||
<MenuGroup label={_x('View', 'noun', 'mailpoet')}>
|
||||
<PreferenceToggleMenuItem
|
||||
scope={storeName}
|
||||
name="fullscreenMode"
|
||||
label={__('Fullscreen mode')}
|
||||
info={__('Work without distraction')}
|
||||
messageActivated={__('Fullscreen mode activated')}
|
||||
messageDeactivated={__('Fullscreen mode deactivated')}
|
||||
label={__('Fullscreen mode', 'mailpoet')}
|
||||
info={__('Work without distraction', 'mailpoet')}
|
||||
messageActivated={__('Fullscreen mode activated', 'mailpoet')}
|
||||
messageDeactivated={__('Fullscreen mode deactivated', 'mailpoet')}
|
||||
shortcut={displayShortcut.secondary('f')}
|
||||
/>
|
||||
</MenuGroup>
|
||||
|
@ -13,7 +13,10 @@ export const InserterListboxGroup = forwardRef<HTMLDivElement, Props>(
|
||||
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { PremiumModal } from 'common/premium_modal';
|
||||
import { Inserter } from '../inserter';
|
||||
import { Item } from '../inserter/item';
|
||||
import { storeName } from '../../store';
|
||||
import { AddStepCallbackType } from '../../../types/filters';
|
||||
|
||||
export function InserterPopover(): JSX.Element | null {
|
||||
const popoverRef = useRef<HTMLDivElement>();
|
||||
@ -20,8 +21,8 @@ export function InserterPopover(): JSX.Element | null {
|
||||
const { setInserterPopover } = useDispatch(storeName);
|
||||
|
||||
const onInsert = useCallback((item: Item) => {
|
||||
const addStepCallback = Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.add_step_callback',
|
||||
const addStepCallback: AddStepCallbackType = Hooks.applyFilters(
|
||||
'mailpoet.automation.add_step_callback',
|
||||
() => {
|
||||
setShowModal(true);
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ import { forwardRef, Fragment, useCallback, useMemo } from 'react';
|
||||
import { SearchControl } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useRef, useImperativeHandle, useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { blockDefault, Icon } from '@wordpress/icons';
|
||||
import { Group } from './group';
|
||||
import { Item } from './item';
|
||||
@ -41,21 +41,26 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
||||
{
|
||||
type: 'triggers',
|
||||
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'),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: 'actions',
|
||||
title: __('Actions', 'mailpoet'),
|
||||
label: __('Actions', 'mailpoet'),
|
||||
// translators: Label for a list of automation steps of type action
|
||||
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'),
|
||||
},
|
||||
{
|
||||
type: 'logical',
|
||||
title: __('Logical', 'mailpoet'),
|
||||
label: __('Logical', 'mailpoet'),
|
||||
// translators: Label for a list of logical automation steps (if/else, etc.)
|
||||
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'),
|
||||
},
|
||||
],
|
||||
@ -96,8 +101,8 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
||||
setFilterValue(value);
|
||||
}}
|
||||
value={filterValue}
|
||||
label={__('Search for blocks and patterns')}
|
||||
placeholder={__('Search')}
|
||||
label={__('Search for automation steps', 'mailpoet')}
|
||||
placeholder={__('Search', 'mailpoet')}
|
||||
ref={searchRef}
|
||||
/>
|
||||
|
||||
@ -135,7 +140,7 @@ export const Inserter = forwardRef(({ onInsert }: Props, ref): JSX.Element => {
|
||||
className="block-editor-inserter__no-results-icon"
|
||||
icon={blockDefault}
|
||||
/>
|
||||
<p>{__('No results found.')}</p>
|
||||
<p>{__('No results found.', 'mailpoet')}</p>
|
||||
</div>
|
||||
)}
|
||||
</InserterListbox>
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
store as keyboardShortcutsStore,
|
||||
} from '@wordpress/keyboard-shortcuts';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/keyboard-shortcuts/index.js
|
||||
@ -25,7 +25,7 @@ export function KeyboardShortcuts(): null {
|
||||
void registerShortcut({
|
||||
name: 'mailpoet/automation-editor/toggle-fullscreen',
|
||||
category: 'global',
|
||||
description: __('Toggle fullscreen mode.'),
|
||||
description: __('Toggle fullscreen mode.', 'mailpoet'),
|
||||
keyCombination: {
|
||||
modifier: 'secondary',
|
||||
character: 'f',
|
||||
@ -35,7 +35,7 @@ export function KeyboardShortcuts(): null {
|
||||
void registerShortcut({
|
||||
name: 'mailpoet/automation-editor/toggle-sidebar',
|
||||
category: 'global',
|
||||
description: __('Show or hide the settings sidebar.'),
|
||||
description: __('Show or hide the settings sidebar.', 'mailpoet'),
|
||||
keyCombination: {
|
||||
modifier: 'primaryShift',
|
||||
character: ',',
|
||||
@ -55,7 +55,7 @@ export function KeyboardShortcuts(): null {
|
||||
} else {
|
||||
const sidebarToOpen = selectedStep()
|
||||
? stepSidebarKey
|
||||
: workflowSidebarKey;
|
||||
: automationSidebarKey;
|
||||
openSidebar(sidebarToOpen);
|
||||
}
|
||||
});
|
||||
|
@ -3,23 +3,66 @@ 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';
|
||||
import { AutomationStatus } from '../../../listing/automation';
|
||||
|
||||
export function DeactivateModal({ onClose }): JSX.Element {
|
||||
const { workflowName } = useSelect(
|
||||
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 automation 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 { automationName } = useSelect(
|
||||
(select) => ({
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const [selected, setSelected] = useState<
|
||||
WorkflowStatus.INACTIVE | WorkflowStatus.DEACTIVATING
|
||||
>(WorkflowStatus.DEACTIVATING);
|
||||
AutomationStatus.DRAFT | AutomationStatus.DEACTIVATING
|
||||
>(AutomationStatus.DEACTIVATING);
|
||||
const [isBusy, setIsBusy] = useState<boolean>(false);
|
||||
// translators: %s is the name of the automation.
|
||||
const title = sprintf(
|
||||
__('Deactivate the "%s" automation?', 'mailpoet'),
|
||||
workflowName,
|
||||
automationName,
|
||||
);
|
||||
|
||||
return (
|
||||
@ -36,7 +79,7 @@ export function DeactivateModal({ onClose }): JSX.Element {
|
||||
<li>
|
||||
<label
|
||||
className={
|
||||
selected === WorkflowStatus.DEACTIVATING
|
||||
selected === AutomationStatus.DEACTIVATING
|
||||
? 'mailpoet-automation-option active'
|
||||
: 'mailpoet-automation-option'
|
||||
}
|
||||
@ -46,8 +89,8 @@ export function DeactivateModal({ onClose }): JSX.Element {
|
||||
type="radio"
|
||||
disabled={isBusy}
|
||||
name="deactivation-method"
|
||||
checked={selected === WorkflowStatus.DEACTIVATING}
|
||||
onChange={() => setSelected(WorkflowStatus.DEACTIVATING)}
|
||||
checked={selected === AutomationStatus.DEACTIVATING}
|
||||
onChange={() => setSelected(AutomationStatus.DEACTIVATING)}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
@ -64,7 +107,7 @@ export function DeactivateModal({ onClose }): JSX.Element {
|
||||
<li>
|
||||
<label
|
||||
className={
|
||||
selected === WorkflowStatus.INACTIVE
|
||||
selected === AutomationStatus.DRAFT
|
||||
? 'mailpoet-automation-option active'
|
||||
: 'mailpoet-automation-option'
|
||||
}
|
||||
@ -74,8 +117,8 @@ export function DeactivateModal({ onClose }): JSX.Element {
|
||||
type="radio"
|
||||
disabled={isBusy}
|
||||
name="deactivation-method"
|
||||
checked={selected === WorkflowStatus.INACTIVE}
|
||||
onChange={() => setSelected(WorkflowStatus.INACTIVE)}
|
||||
checked={selected === AutomationStatus.DRAFT}
|
||||
onChange={() => setSelected(AutomationStatus.DRAFT)}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
@ -96,12 +139,9 @@ export function DeactivateModal({ onClose }): JSX.Element {
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setIsBusy(true);
|
||||
if (selected === WorkflowStatus.DEACTIVATING) {
|
||||
// @ToDo Use the correct method provided in MAILPOET-4731
|
||||
dispatch(storeName).deactivate();
|
||||
return;
|
||||
}
|
||||
dispatch(storeName).deactivate();
|
||||
dispatch(storeName).deactivate(
|
||||
selected !== AutomationStatus.DEACTIVATING,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{__('Deactivate automation', 'mailpoet')}
|
||||
|
@ -4,7 +4,7 @@ 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 { AutomationStatus } from '../../../listing/automation';
|
||||
import { MailPoet } from '../../../../mailpoet';
|
||||
|
||||
function PreStep({ onClose }): JSX.Element {
|
||||
@ -58,9 +58,9 @@ function PreStep({ onClose }): JSX.Element {
|
||||
}
|
||||
|
||||
function PostStep({ onClose }): JSX.Element {
|
||||
const { workflow } = useSelect(
|
||||
const { automation } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -81,10 +81,10 @@ function PostStep({ onClose }): JSX.Element {
|
||||
|
||||
<div className="mailpoet-automation-activate-panel__body">
|
||||
<div className="mailpoet-automation-activate-panel__section">
|
||||
{sprintf(__('"%s" is now live.', 'mailpoet'), workflow.name)}
|
||||
{sprintf(__('"%s" is now live.', 'mailpoet'), automation.name)}
|
||||
</div>
|
||||
<p>
|
||||
<strong>{__("What's next?", 'mailpoet')}</strong>
|
||||
<strong>{__('What’s next?', 'mailpoet')}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{__(
|
||||
@ -100,29 +100,31 @@ function PostStep({ onClose }): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export function ActivatePanel({ onClose }): JSX.Element {
|
||||
const { workflow, errors } = useSelect(
|
||||
export function ActivatePanel(): JSX.Element {
|
||||
const { automation, errors } = useSelect(
|
||||
(select) => ({
|
||||
errors: select(storeName).getErrors(),
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const { closeActivationPanel } = useDispatch(storeName);
|
||||
|
||||
useEffect(() => {
|
||||
if (errors) {
|
||||
onClose();
|
||||
closeActivationPanel();
|
||||
}
|
||||
}, [errors, onClose]);
|
||||
}, [errors, closeActivationPanel]);
|
||||
|
||||
if (errors) {
|
||||
return null;
|
||||
}
|
||||
const isActive = workflow.status === WorkflowStatus.ACTIVE;
|
||||
const isActive = automation.status === AutomationStatus.ACTIVE;
|
||||
return (
|
||||
<div className="mailpoet-automation-activate-panel">
|
||||
{isActive && <PostStep onClose={onClose} />}
|
||||
{!isActive && <PreStep onClose={onClose} />}
|
||||
{isActive && <PostStep onClose={closeActivationPanel} />}
|
||||
{!isActive && <PreStep onClose={closeActivationPanel} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { PanelBody as WpPanelBody } from '@wordpress/components';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type Props = WpPanelBody.Props & {
|
||||
hasErrors?: boolean;
|
||||
};
|
||||
|
||||
export function PanelBody({ hasErrors = false, ...props }: Props): JSX.Element {
|
||||
const [isOpened, setIsOpened] = useState(props.initialOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasErrors) {
|
||||
setIsOpened(true);
|
||||
}
|
||||
}, [hasErrors]);
|
||||
|
||||
return (
|
||||
<WpPanelBody
|
||||
opened={isOpened}
|
||||
onToggle={() => setIsOpened((prevState) => !prevState)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Dropdown, TextControl } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { edit, Icon } from '@wordpress/icons';
|
||||
import { PlainBodyTitle } from './plain-body-title';
|
||||
import { TitleActionButton } from './title-action-button';
|
||||
@ -25,7 +26,7 @@ export function StepName({
|
||||
<TitleActionButton
|
||||
onClick={onToggle}
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Edit step name"
|
||||
aria-label={__('Edit step name', 'mailpoet')}
|
||||
>
|
||||
<Icon icon={edit} size={16} />
|
||||
</TitleActionButton>
|
||||
@ -33,13 +34,15 @@ export function StepName({
|
||||
)}
|
||||
renderContent={() => (
|
||||
<TextControl
|
||||
label="Step name"
|
||||
label={__('Step name', 'mailpoet')}
|
||||
className="mailpoet-step-name-input"
|
||||
placeholder={defaultName}
|
||||
value={currentName}
|
||||
onChange={update}
|
||||
help="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."
|
||||
help={__(
|
||||
'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,12 +1,13 @@
|
||||
import { PanelBody, PanelRow } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../../store';
|
||||
import { TrashButton } from '../../actions/trash-button';
|
||||
|
||||
export function WorkflowSidebar(): JSX.Element {
|
||||
const { workflowData } = useSelect(
|
||||
export function AutomationSidebar(): JSX.Element {
|
||||
const { automationData } = useSelect(
|
||||
(select) => ({
|
||||
workflowData: select(storeName).getWorkflowData(),
|
||||
automationData: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -18,33 +19,33 @@ export function WorkflowSidebar(): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<PanelBody title="Automation details" initialOpen>
|
||||
<PanelBody title={__('Automation details', 'mailpoet')} initialOpen>
|
||||
<PanelRow>
|
||||
<strong>Date added</strong>{' '}
|
||||
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
|
||||
{new Date(Date.parse(automationData.created_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<strong>Activated</strong>{' '}
|
||||
{workflowData.status === 'active' &&
|
||||
new Date(Date.parse(workflowData.updated_at)).toLocaleDateString(
|
||||
{automationData.status === 'active' &&
|
||||
new Date(Date.parse(automationData.updated_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
{workflowData.status !== 'active' &&
|
||||
workflowData.activated_at &&
|
||||
new Date(Date.parse(workflowData.activated_at)).toLocaleDateString(
|
||||
{automationData.status !== 'active' &&
|
||||
automationData.activated_at &&
|
||||
new Date(Date.parse(automationData.activated_at)).toLocaleDateString(
|
||||
undefined,
|
||||
dateOptions,
|
||||
)}
|
||||
{workflowData.status !== 'active' && !workflowData.activated_at && (
|
||||
{automationData.status !== 'active' && !automationData.activated_at && (
|
||||
<span className="mailpoet-deactive">Not activated yet.</span>
|
||||
)}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<strong>Author</strong> {workflowData.author.name}
|
||||
<strong>Author</strong> {automationData.author.name}
|
||||
</PanelRow>
|
||||
<PanelRow>
|
||||
<TrashButton />
|
@ -1,6 +1,7 @@
|
||||
import { Button } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/sidebar/settings-header/index.js
|
||||
@ -12,29 +13,29 @@ type Props = {
|
||||
|
||||
export function Header({ sidebarKey }: Props): JSX.Element {
|
||||
const { openSidebar } = useDispatch(storeName);
|
||||
const openWorkflowSettings = () => openSidebar(workflowSidebarKey);
|
||||
const openAutomationSettings = () => openSidebar(automationSidebarKey);
|
||||
const openStepSettings = () => openSidebar(stepSidebarKey);
|
||||
|
||||
const [workflowAriaLabel, workflowActiveClass] =
|
||||
sidebarKey === workflowSidebarKey
|
||||
? ['Workflow (selected)', 'is-active']
|
||||
: ['Workflow', ''];
|
||||
const [automationAriaLabel, automationActiveClass] =
|
||||
sidebarKey === automationSidebarKey
|
||||
? [__('Automation (selected)', 'mailpoet'), 'is-active']
|
||||
: [__('Automation', 'mailpoet'), ''];
|
||||
|
||||
const [stepAriaLabel, stepActiveClass] =
|
||||
sidebarKey === stepSidebarKey
|
||||
? ['Step (selected)', 'is-active']
|
||||
: ['Step', ''];
|
||||
? [__('Step (selected)', 'mailpoet'), 'is-active']
|
||||
: [__('Step', 'mailpoet'), ''];
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<li>
|
||||
<Button
|
||||
onClick={openWorkflowSettings}
|
||||
className={`edit-site-sidebar__panel-tab ${workflowActiveClass}`}
|
||||
aria-label={workflowAriaLabel}
|
||||
data-label="Workflow"
|
||||
onClick={openAutomationSettings}
|
||||
className={`edit-site-sidebar__panel-tab ${automationActiveClass}`}
|
||||
aria-label={automationAriaLabel}
|
||||
data-label={__('Automation', 'mailpoet')}
|
||||
>
|
||||
Workflow
|
||||
{__('Automation', 'mailpoet')}
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
@ -42,9 +43,9 @@ export function Header({ sidebarKey }: Props): JSX.Element {
|
||||
onClick={openStepSettings}
|
||||
className={`edit-site-sidebar__panel-tab ${stepActiveClass}`}
|
||||
aria-label={stepAriaLabel}
|
||||
data-label="Workflow"
|
||||
data-label={__('Step', 'mailpoet')}
|
||||
>
|
||||
Step
|
||||
{__('Step', 'mailpoet')}
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
|
||||
import { Header } from './header';
|
||||
import { StepSidebar } from './step';
|
||||
import { WorkflowSidebar } from './workflow';
|
||||
import { stepSidebarKey, storeName, workflowSidebarKey } from '../../store';
|
||||
import { AutomationSidebar } from './automation';
|
||||
import { stepSidebarKey, storeName, automationSidebarKey } from '../../store';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/5caeae34b3fb303761e3b9432311b26f4e5ea3a6/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js
|
||||
@ -26,7 +26,7 @@ const sidebarActiveByDefault = Platform.select({
|
||||
type Props = ComponentProps<typeof ComplementaryArea>;
|
||||
|
||||
export function Sidebar(props: Props): JSX.Element {
|
||||
const { keyboardShortcut, sidebarKey, showIconLabels, workflowName } =
|
||||
const { keyboardShortcut, sidebarKey, showIconLabels, automationName } =
|
||||
useSelect(
|
||||
(select) => ({
|
||||
keyboardShortcut: select(
|
||||
@ -36,9 +36,9 @@ export function Sidebar(props: Props): JSX.Element {
|
||||
),
|
||||
sidebarKey:
|
||||
select(interfaceStore).getActiveComplementaryArea(storeName) ??
|
||||
workflowSidebarKey,
|
||||
automationSidebarKey,
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
workflowName: select(storeName).getWorkflowData().name,
|
||||
automationName: select(storeName).getAutomationData().name,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -47,20 +47,20 @@ export function Sidebar(props: Props): JSX.Element {
|
||||
<ComplementaryArea
|
||||
identifier={sidebarKey}
|
||||
header={<Header sidebarKey={sidebarKey} />}
|
||||
closeLabel={__('Close settings')}
|
||||
closeLabel={__('Close settings', 'mailpoet')}
|
||||
headerClassName="edit-site-sidebar__panel-tabs"
|
||||
title={__('Settings')}
|
||||
title={__('Settings', 'mailpoet')}
|
||||
icon={cog}
|
||||
className="edit-site-sidebar mailpoet-automation-sidebar"
|
||||
panelClassName="edit-site-sidebar"
|
||||
smallScreenTitle={workflowName || __('(no title)')}
|
||||
smallScreenTitle={automationName || __('(no title)', 'mailpoet')}
|
||||
scope={storeName}
|
||||
toggleShortcut={keyboardShortcut}
|
||||
isActiveByDefault={sidebarActiveByDefault}
|
||||
showIconLabels={showIconLabels}
|
||||
{...props}
|
||||
>
|
||||
{sidebarKey === workflowSidebarKey && <WorkflowSidebar />}
|
||||
{sidebarKey === automationSidebarKey && <AutomationSidebar />}
|
||||
{sidebarKey === stepSidebarKey && <StepSidebar />}
|
||||
</ComplementaryArea>
|
||||
);
|
||||
|
@ -30,22 +30,11 @@ export function StepSidebar(): JSX.Element {
|
||||
icon={selectedStepType.icon}
|
||||
/>
|
||||
|
||||
<Edit />
|
||||
|
||||
<PanelBody title="Debug info" initialOpen={false}>
|
||||
<div>
|
||||
<strong>ID:</strong> {selectedStep.id}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Type:</strong> {selectedStep.type}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Key:</strong> {selectedStep.key}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Args:</strong> {JSON.stringify(selectedStep.args)}
|
||||
</div>
|
||||
</PanelBody>
|
||||
<Edit
|
||||
// Force sidebar remount to avoid different steps mixing their data.
|
||||
// This can happen e.g. when having "useState" or "useRef" internally.
|
||||
key={selectedStep.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
export function EmptyWorkflow(): JSX.Element {
|
||||
return (
|
||||
<div className="mailpoet-automation-editor-empty-workflow">
|
||||
No workflow data.
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store';
|
||||
import { Statistics as BaseStatistics } from '../../../components/statistics';
|
||||
|
||||
export function Statistics(): JSX.Element {
|
||||
const { workflow } = useSelect(
|
||||
(select) => ({
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mailpoet-automation-editor-stats">
|
||||
<BaseStatistics
|
||||
items={[
|
||||
{
|
||||
key: 'entered',
|
||||
label: __('Total Entered', 'mailpoet'),
|
||||
value: workflow.stats.totals.entered,
|
||||
},
|
||||
{
|
||||
key: 'processing',
|
||||
label: __('Total Processing', 'mailpoet'),
|
||||
value: workflow.stats.totals.in_progress,
|
||||
},
|
||||
{
|
||||
key: 'exited',
|
||||
label: __('Total Exited', 'mailpoet'),
|
||||
value: workflow.stats.totals.exited,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { moreVertical, trash } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { PremiumModal } from 'common/premium_modal';
|
||||
import { Step as StepData } from './types';
|
||||
|
||||
type Props = {
|
||||
step: StepData;
|
||||
};
|
||||
|
||||
export function StepMoreMenu({ step }: Props): JSX.Element {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const onDelete = useCallback((stepData: StepData) => {
|
||||
const deleteStepCallback = Hooks.applyFilters(
|
||||
'mailpoet.automation.workflow.delete_step_callback',
|
||||
() => {
|
||||
setShowModal(true);
|
||||
},
|
||||
);
|
||||
deleteStepCallback(stepData);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mailpoet-automation-step-more-menu">
|
||||
<DropdownMenu
|
||||
label={__('More', 'mailpoet')}
|
||||
icon={moreVertical}
|
||||
controls={[
|
||||
{
|
||||
title: __('Delete step', 'mailpoet'),
|
||||
icon: trash,
|
||||
onClick: () => onDelete(step),
|
||||
},
|
||||
]}
|
||||
popoverProps={{ position: 'bottom right' }}
|
||||
toggleProps={{ isSmall: true }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showModal && (
|
||||
<PremiumModal
|
||||
onRequestClose={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
tracking={{
|
||||
utm_medium: 'upsell_modal',
|
||||
utm_campaign: 'remove_automation_step',
|
||||
}}
|
||||
>
|
||||
{__('You cannot remove a new step from the automation.', 'mailpoet')}
|
||||
</PremiumModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,30 +1,39 @@
|
||||
import classnames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Icon, Popover, SlotFillProvider } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import {
|
||||
dispatch,
|
||||
select as globalSelect,
|
||||
StoreDescriptor,
|
||||
useSelect,
|
||||
} from '@wordpress/data';
|
||||
import { wordpress } from '@wordpress/icons';
|
||||
import {
|
||||
ComplementaryArea,
|
||||
InterfaceSkeleton,
|
||||
FullscreenMode,
|
||||
InterfaceSkeleton,
|
||||
} from '@wordpress/interface';
|
||||
import { ShortcutProvider } from '@wordpress/keyboard-shortcuts';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { Header } from './components/header';
|
||||
import { InserterSidebar } from './components/inserter-sidebar';
|
||||
import { KeyboardShortcuts } from './components/keyboard-shortcuts';
|
||||
import { EditorNotices } from './components/notices';
|
||||
import { Sidebar } from './components/sidebar';
|
||||
import { Workflow } from './components/workflow';
|
||||
import { Automation } from './components/automation';
|
||||
import { createStore, storeName } from './store';
|
||||
import { initializeApi } from '../api';
|
||||
import { initialize as initializeCoreIntegration } from '../integrations/core';
|
||||
import { initialize as initializeMailPoetIntegration } from '../integrations/mailpoet';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
import { LISTING_NOTICE_PARAMETERS } from '../listing/workflow-listing-notices';
|
||||
import { LISTING_NOTICE_PARAMETERS } from '../listing/automation-listing-notices';
|
||||
import { registerApiErrorHandler } from './api-error-handler';
|
||||
import { ActivatePanel } from './components/panel/activate-panel';
|
||||
import { registerTranslations } from '../i18n';
|
||||
import { AutomationStatus } from '../listing/automation';
|
||||
|
||||
// See:
|
||||
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
|
||||
@ -33,41 +42,97 @@ import { ActivatePanel } from './components/panel/activate-panel';
|
||||
// disable inserter sidebar until we implement drag & drop
|
||||
const showInserterSidebar = false;
|
||||
|
||||
/**
|
||||
* Show temporary message that active automations cant be updated
|
||||
*
|
||||
* see MAILPOET-4744
|
||||
*/
|
||||
function updatingActiveAutomationNotPossible() {
|
||||
const automation = globalSelect(storeName).getAutomationData();
|
||||
if (
|
||||
![AutomationStatus.ACTIVE, AutomationStatus.DEACTIVATING].includes(
|
||||
automation.status,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (automation.stats.totals.in_progress === 0) {
|
||||
return;
|
||||
}
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
void createNotice(
|
||||
'success',
|
||||
__(
|
||||
'Editing an active automation is temporarily unavailable. We are working on introducing this functionality.',
|
||||
'mailpoet',
|
||||
),
|
||||
{
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onUnload(event) {
|
||||
if (!globalSelect(storeName).getAutomationSaved()) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
event.returnValue = __(
|
||||
'There are unsaved changes that will be lost. Do you want to continue?',
|
||||
'mailpoet',
|
||||
);
|
||||
return event.returnValue;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function useConfirmUnsaved() {
|
||||
useEffect(() => {
|
||||
window.addEventListener('beforeunload', onUnload);
|
||||
return () => window.removeEventListener('beforeunload', onUnload);
|
||||
}, []);
|
||||
}
|
||||
|
||||
function Editor(): JSX.Element {
|
||||
const {
|
||||
isFullscreenActive,
|
||||
isInserterOpened,
|
||||
isActivationPanelOpened,
|
||||
isSidebarOpened,
|
||||
showIconLabels,
|
||||
workflow,
|
||||
automation,
|
||||
} = useSelect(
|
||||
(select) => ({
|
||||
isFullscreenActive: select(storeName).isFeatureActive('fullscreenMode'),
|
||||
isInserterOpened: select(storeName).isInserterSidebarOpened(),
|
||||
isSidebarOpened: select(storeName).isSidebarOpened(),
|
||||
isActivationPanelOpened: select(storeName).isActivationPanelOpened(),
|
||||
showIconLabels: select(storeName).isFeatureActive('showIconLabels'),
|
||||
workflow: select(storeName).getWorkflowData(),
|
||||
automation: select(storeName).getAutomationData(),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const [showActivatePanel, setShowActivatePanel] = useState(false);
|
||||
const [isBooting, setIsBooting] = useState(true);
|
||||
|
||||
useConfirmUnsaved();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBooting) {
|
||||
return;
|
||||
}
|
||||
updatingActiveAutomationNotPossible();
|
||||
setIsBooting(false);
|
||||
}, [isBooting]);
|
||||
const className = classnames('interface-interface-skeleton', {
|
||||
'is-sidebar-opened': isSidebarOpened,
|
||||
'show-icon-labels': showIconLabels,
|
||||
});
|
||||
|
||||
if (workflow.status === 'trash') {
|
||||
if (automation.status === 'trash') {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
[LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted]: workflow.id,
|
||||
[LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted]: automation.id,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleActivatePanel = () => {
|
||||
setShowActivatePanel(!showActivatePanel);
|
||||
};
|
||||
|
||||
return (
|
||||
<ShortcutProvider>
|
||||
<SlotFillProvider>
|
||||
@ -88,16 +153,11 @@ function Editor(): JSX.Element {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
header={
|
||||
<Header
|
||||
showInserterToggle={showInserterSidebar}
|
||||
toggleActivatePanel={toggleActivatePanel}
|
||||
/>
|
||||
}
|
||||
header={<Header showInserterToggle={showInserterSidebar} />}
|
||||
content={
|
||||
<>
|
||||
<EditorNotices />
|
||||
<Workflow />
|
||||
<Automation />
|
||||
</>
|
||||
}
|
||||
sidebar={<ComplementaryArea.Slot scope={storeName} />}
|
||||
@ -105,7 +165,7 @@ function Editor(): JSX.Element {
|
||||
showInserterSidebar && isInserterOpened ? <InserterSidebar /> : null
|
||||
}
|
||||
/>
|
||||
{showActivatePanel && <ActivatePanel onClose={toggleActivatePanel} />}
|
||||
{isActivationPanelOpened && <ActivatePanel />}
|
||||
<Popover.Slot />
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
@ -117,6 +177,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const root = document.getElementById('mailpoet_automation_editor');
|
||||
if (root) {
|
||||
registerTranslations();
|
||||
registerApiErrorHandler();
|
||||
initializeApi();
|
||||
initializeCoreIntegration();
|
||||
|
@ -7,14 +7,42 @@ import { store as preferencesStore } from '@wordpress/preferences';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { storeName } from './constants';
|
||||
import { Feature, State } from './types';
|
||||
import { LISTING_NOTICE_PARAMETERS } from '../../listing/workflow-listing-notices';
|
||||
import { LISTING_NOTICE_PARAMETERS } from '../../listing/automation-listing-notices';
|
||||
import { MailPoet } from '../../../mailpoet';
|
||||
import { WorkflowStatus } from '../../listing/workflow';
|
||||
import { AutomationStatus } from '../../listing/automation';
|
||||
|
||||
export const openSidebar =
|
||||
(key) =>
|
||||
({ registry }) =>
|
||||
const trackErrors = (errors) => {
|
||||
if (!errors?.steps) {
|
||||
return;
|
||||
}
|
||||
const payload = Object.keys(errors.steps as object).map((stepId) => {
|
||||
const error = errors.steps[stepId];
|
||||
const stepKey = select(storeName).getStepById(stepId)?.key;
|
||||
const fields = Object.keys(error.fields as object)
|
||||
.map((field) => `${stepKey}/${field}`)
|
||||
.reduce((prev, next) => prev.concat(next));
|
||||
return fields;
|
||||
});
|
||||
|
||||
MailPoet.trackEvent('Automations > Automation validation error', {
|
||||
errors: payload,
|
||||
});
|
||||
};
|
||||
|
||||
export const openActivationPanel = () => ({
|
||||
type: 'SET_ACTIVATION_PANEL_VISIBILITY',
|
||||
value: true,
|
||||
});
|
||||
export const closeActivationPanel = () => ({
|
||||
type: 'SET_ACTIVATION_PANEL_VISIBILITY',
|
||||
value: false,
|
||||
});
|
||||
|
||||
export const openSidebar = (key) => {
|
||||
dispatch(storeName).closeActivationPanel();
|
||||
return ({ registry }) =>
|
||||
registry.dispatch(interfaceStore).enableComplementaryArea(storeName, key);
|
||||
};
|
||||
|
||||
export const closeSidebar =
|
||||
() =>
|
||||
@ -46,44 +74,55 @@ export function selectStep(value) {
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function setWorkflowName(name) {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
export function setAutomationName(name) {
|
||||
const automation = select(storeName).getAutomationData();
|
||||
return {
|
||||
type: 'UPDATE_WORKFLOW',
|
||||
workflow: {
|
||||
...workflow,
|
||||
type: 'UPDATE_AUTOMATION',
|
||||
automation: {
|
||||
...automation,
|
||||
name,
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* save() {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: { ...workflow },
|
||||
data: { ...automation },
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
if (data?.data) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__('The automation has been saved.', 'mailpoet'),
|
||||
{
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'SAVE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* activate() {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: 'active',
|
||||
...automation,
|
||||
status: AutomationStatus.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
if (data?.data.status === WorkflowStatus.ACTIVE) {
|
||||
if (data?.data.status === AutomationStatus.ACTIVE) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__('Well done! Automation is now activated!', 'mailpoet'),
|
||||
@ -91,28 +130,33 @@ export function* activate() {
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
MailPoet.trackEvent('Automations > Automation activated');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ACTIVATE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
// @ToDo: Decide on best naming once MAILPOET-4731 decides about the "deactivating" status name
|
||||
export function* deactivate() {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
export function* deactivate(deactivateAutomationRuns = true) {
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: 'inactive',
|
||||
...automation,
|
||||
status: deactivateAutomationRuns
|
||||
? AutomationStatus.DRAFT
|
||||
: AutomationStatus.DEACTIVATING,
|
||||
},
|
||||
});
|
||||
|
||||
const { createNotice } = dispatch(noticesStore as StoreDescriptor);
|
||||
if (data?.data.status === WorkflowStatus.INACTIVE) {
|
||||
if (
|
||||
deactivateAutomationRuns &&
|
||||
data?.data.status === AutomationStatus.DRAFT
|
||||
) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__('Automation is now deactivated!', 'mailpoet'),
|
||||
@ -120,36 +164,58 @@ export function* deactivate() {
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
|
||||
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||
type: 'immediate',
|
||||
});
|
||||
}
|
||||
if (
|
||||
!deactivateAutomationRuns &&
|
||||
data?.data.status === AutomationStatus.DEACTIVATING
|
||||
) {
|
||||
void createNotice(
|
||||
'success',
|
||||
__(
|
||||
'Automation is deactivated. But recent users are still going through the flow.',
|
||||
'mailpoet',
|
||||
),
|
||||
{
|
||||
type: 'snackbar',
|
||||
},
|
||||
);
|
||||
MailPoet.trackEvent('Automations > Automation deactivated', {
|
||||
type: 'continuous',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'DEACTIVATE',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function* trash(onTrashed: () => void = undefined) {
|
||||
const workflow = select(storeName).getWorkflowData();
|
||||
const automation = select(storeName).getAutomationData();
|
||||
const data = yield apiFetch({
|
||||
path: `/workflows/${workflow.id}`,
|
||||
path: `/automations/${automation.id}`,
|
||||
method: 'PUT',
|
||||
data: {
|
||||
...workflow,
|
||||
status: 'trash',
|
||||
...automation,
|
||||
status: AutomationStatus.TRASH,
|
||||
},
|
||||
});
|
||||
|
||||
onTrashed?.();
|
||||
|
||||
if (data?.status === 'trash') {
|
||||
if (data?.status === AutomationStatus.TRASH) {
|
||||
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
|
||||
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
|
||||
[LISTING_NOTICE_PARAMETERS.automationDeleted]: automation.id,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'TRASH',
|
||||
workflow: data?.data ?? workflow,
|
||||
automation: data?.data ?? automation,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@ -170,6 +236,7 @@ export function updateStepArgs(stepId, name, value) {
|
||||
}
|
||||
|
||||
export function setErrors(errors) {
|
||||
trackErrors(errors);
|
||||
return {
|
||||
type: 'SET_ERRORS',
|
||||
errors,
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const storeName = 'mailpoet/automation-editor';
|
||||
|
||||
export const workflowSidebarKey = 'mailpoet/automation-editor/workflow';
|
||||
export const automationSidebarKey = 'mailpoet/automation-editor/automation';
|
||||
export const stepSidebarKey = 'mailpoet/automation-editor/step';
|
||||
|
@ -5,12 +5,15 @@ declare let window: AutomationEditorWindow;
|
||||
export const getInitialState = (): State => ({
|
||||
context: { ...window.mailpoet_automation_context },
|
||||
stepTypes: {},
|
||||
workflowData: { ...window.mailpoet_automation_workflow },
|
||||
workflowSaved: true,
|
||||
automationData: { ...window.mailpoet_automation },
|
||||
automationSaved: true,
|
||||
selectedStep: undefined,
|
||||
inserterSidebar: {
|
||||
isOpened: false,
|
||||
},
|
||||
activationPanel: {
|
||||
isOpened: false,
|
||||
},
|
||||
inserterPopover: undefined,
|
||||
errors: undefined,
|
||||
});
|
||||
|
@ -3,6 +3,14 @@ import { State } from './types';
|
||||
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case 'SET_ACTIVATION_PANEL_VISIBILITY':
|
||||
return {
|
||||
...state,
|
||||
activationPanel: {
|
||||
...state.activationPanel,
|
||||
isOpened: action.value,
|
||||
},
|
||||
};
|
||||
case 'TOGGLE_INSERTER_SIDEBAR':
|
||||
return {
|
||||
...state,
|
||||
@ -21,35 +29,35 @@ export function reducer(state: State, action: Action): State {
|
||||
...state,
|
||||
selectedStep: action.value,
|
||||
};
|
||||
case 'UPDATE_WORKFLOW':
|
||||
case 'UPDATE_AUTOMATION':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: false,
|
||||
automationData: action.automation,
|
||||
automationSaved: false,
|
||||
};
|
||||
case 'SAVE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'ACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'DEACTIVATE':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'TRASH':
|
||||
return {
|
||||
...state,
|
||||
workflowData: action.workflow,
|
||||
workflowSaved: true,
|
||||
automationData: action.automation,
|
||||
automationSaved: true,
|
||||
};
|
||||
case 'REGISTER_STEP_TYPE':
|
||||
return {
|
||||
@ -60,19 +68,21 @@ export function reducer(state: State, action: Action): State {
|
||||
},
|
||||
};
|
||||
case 'UPDATE_STEP_ARGS': {
|
||||
const prevArgs = state.workflowData.steps[action.stepId].args ?? {};
|
||||
const prevArgs = state.automationData.steps[action.stepId].args ?? {};
|
||||
|
||||
const value =
|
||||
typeof action.value === 'function'
|
||||
? action.value(prevArgs[action.name] ?? undefined)
|
||||
: action.value;
|
||||
|
||||
const args = {
|
||||
...prevArgs,
|
||||
[action.name]: value,
|
||||
};
|
||||
const args =
|
||||
value === undefined
|
||||
? Object.fromEntries(
|
||||
Object.entries(prevArgs).filter(([name]) => name !== action.name),
|
||||
)
|
||||
: { ...prevArgs, [action.name]: value };
|
||||
|
||||
const step = { ...state.workflowData.steps[action.stepId], args };
|
||||
const step = { ...state.automationData.steps[action.stepId], args };
|
||||
|
||||
const stepErrors = Object.values(state.errors?.steps ?? {}).filter(
|
||||
({ step_id }) => step_id !== action.stepId,
|
||||
@ -80,14 +90,14 @@ export function reducer(state: State, action: Action): State {
|
||||
|
||||
return {
|
||||
...state,
|
||||
workflowData: {
|
||||
...state.workflowData,
|
||||
automationData: {
|
||||
...state.automationData,
|
||||
steps: {
|
||||
...state.workflowData.steps,
|
||||
...state.automationData.steps,
|
||||
[action.stepId]: step,
|
||||
},
|
||||
},
|
||||
workflowSaved: false,
|
||||
automationSaved: false,
|
||||
selectedStep: step,
|
||||
errors:
|
||||
stepErrors.length > 0
|
||||
|
@ -2,9 +2,9 @@ import { createRegistrySelector } from '@wordpress/data';
|
||||
import { store as interfaceStore } from '@wordpress/interface';
|
||||
import { store as preferencesStore } from '@wordpress/preferences';
|
||||
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 { Step, Workflow } from '../components/workflow/types';
|
||||
import { Step, Automation } from '../components/automation/types';
|
||||
|
||||
export const isFeatureActive = createRegistrySelector(
|
||||
(select) =>
|
||||
@ -21,6 +21,10 @@ export function isInserterSidebarOpened(state: State): boolean {
|
||||
return state.inserterSidebar.isOpened;
|
||||
}
|
||||
|
||||
export function isActivationPanelOpened(state: State): boolean {
|
||||
return state.activationPanel.isOpened;
|
||||
}
|
||||
|
||||
export function getContext(state: State): Context {
|
||||
return state.context;
|
||||
}
|
||||
@ -54,18 +58,22 @@ export function getInserterPopover(
|
||||
return state.inserterPopover;
|
||||
}
|
||||
|
||||
export function getWorkflowData(state: State): Workflow {
|
||||
return state.workflowData;
|
||||
export function getAutomationData(state: State): Automation {
|
||||
return state.automationData;
|
||||
}
|
||||
|
||||
export function getWorkflowSaved(state: State): boolean {
|
||||
return state.workflowSaved;
|
||||
export function getAutomationSaved(state: State): boolean {
|
||||
return state.automationSaved;
|
||||
}
|
||||
|
||||
export function getSelectedStep(state: State): Step | undefined {
|
||||
return state.selectedStep;
|
||||
}
|
||||
|
||||
export function getStepById(state: State, id: string): Step | undefined {
|
||||
return state.automationData.steps[id] ?? undefined;
|
||||
}
|
||||
|
||||
export function getStepType(state: State, key: string): StepType | undefined {
|
||||
return state.stepTypes[key] ?? undefined;
|
||||
}
|
||||
@ -78,6 +86,6 @@ export function getErrors(state: State): Errors | undefined {
|
||||
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;
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
createReduxStore,
|
||||
register,
|
||||
StoreConfig,
|
||||
StoreDescriptor,
|
||||
} from '@wordpress/data';
|
||||
import { createReduxStore, register, StoreDescriptor } from '@wordpress/data';
|
||||
import { controls } from '@wordpress/data-controls';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import * as actions from './actions';
|
||||
@ -13,6 +8,7 @@ import { reducer } from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import { State } from './types';
|
||||
import { OmitFirstArgs } from '../../../types';
|
||||
import { EditorStoreConfigType } from '../../types/filters';
|
||||
|
||||
type StoreType = Omit<StoreDescriptor, 'name'> & {
|
||||
name: typeof storeName;
|
||||
@ -28,8 +24,8 @@ export const createStore = (): StoreType => {
|
||||
selectors,
|
||||
reducer,
|
||||
initialState: getInitialState(),
|
||||
} as StoreConfig<State>,
|
||||
) as StoreConfig<State>;
|
||||
} as EditorStoreConfigType,
|
||||
) as EditorStoreConfigType;
|
||||
|
||||
const store = createReduxStore<State>(storeName, storeConfig) as StoreType;
|
||||
register(store);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Step, Workflow } from '../components/workflow/types';
|
||||
import { Step, Automation } from '../components/automation/types';
|
||||
|
||||
export interface AutomationEditorWindow extends Window {
|
||||
mailpoet_automation_context: Context;
|
||||
mailpoet_automation_workflow: Workflow;
|
||||
mailpoet_automation: Automation;
|
||||
}
|
||||
|
||||
export type Context = {
|
||||
@ -32,26 +32,31 @@ export type StepType = {
|
||||
edit: ComponentType;
|
||||
foreground: string;
|
||||
background: string;
|
||||
createStep?: (step: Step, state: State) => Step;
|
||||
};
|
||||
|
||||
export type StepError = {
|
||||
export type StepErrors = {
|
||||
step_id: string;
|
||||
message: string;
|
||||
fields: Record<string, string>;
|
||||
};
|
||||
|
||||
export type Errors = {
|
||||
steps: Record<string, StepError>;
|
||||
steps: Record<string, StepErrors>;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
context: Context;
|
||||
stepTypes: Record<string, StepType>;
|
||||
workflowData: Workflow;
|
||||
workflowSaved: boolean;
|
||||
automationData: Automation;
|
||||
automationSaved: boolean;
|
||||
selectedStep: Step | undefined;
|
||||
inserterSidebar: {
|
||||
isOpened: boolean;
|
||||
};
|
||||
activationPanel: {
|
||||
isOpened: boolean;
|
||||
};
|
||||
inserterPopover?: {
|
||||
anchor: HTMLElement;
|
||||
type: 'steps' | 'triggers';
|
||||
|
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');
|
@ -12,25 +12,42 @@ import { storeName } from '../../../../editor/store';
|
||||
import { DelayTypeOptions } from './types/delayTypes';
|
||||
|
||||
export function Edit(): JSX.Element {
|
||||
const { selectedStep } = useSelect(
|
||||
const { selectedStep, errors } = useSelect(
|
||||
(select) => ({
|
||||
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 (
|
||||
<PanelBody opened>
|
||||
<label htmlFor={delayValueInputId}>
|
||||
<PlainBodyTitle title={__('Wait for', 'mailpoet')} />
|
||||
<PlainBodyTitle
|
||||
title={
|
||||
// translators: A label for a wait delay time selection form field - time unit follows
|
||||
__('Wait for', 'mailpoet')
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<Flex align="top">
|
||||
<FlexItem style={{ flex: '1 1 0' }}>
|
||||
<FlexItem
|
||||
style={{ flex: '1 1 0' }}
|
||||
className={
|
||||
delayErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||
}
|
||||
>
|
||||
<TextControl
|
||||
id={delayValueInputId}
|
||||
help={delayErrorMessage}
|
||||
type="number"
|
||||
placeholder="Number"
|
||||
placeholder={__('Number', 'mailpoet')}
|
||||
value={(selectedStep.args.delay as string) ?? ''}
|
||||
onChange={(rawValue) => {
|
||||
const value: number =
|
||||
@ -45,9 +62,15 @@ export function Edit(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem style={{ flex: '1 1 0' }}>
|
||||
<FlexItem
|
||||
style={{ flex: '1 1 0' }}
|
||||
className={
|
||||
delayTypeErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||
}
|
||||
>
|
||||
<SelectControl
|
||||
label=""
|
||||
help={delayTypeErrorMessage}
|
||||
value={(selectedStep.args.delay_type as string) ?? 'HOURS'}
|
||||
options={DelayTypeOptions}
|
||||
onChange={(value) =>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { Icon } from './icon';
|
||||
import { Edit } from './edit';
|
||||
import { StepType } from '../../../../editor/store/types';
|
||||
@ -14,13 +15,16 @@ const getDelayInformation = (delayTypeValue: string, value: number): string =>
|
||||
export const step: StepType = {
|
||||
key: 'core:delay',
|
||||
group: 'actions',
|
||||
title: 'Delay',
|
||||
title: _x('Delay', 'noun', 'mailpoet'),
|
||||
foreground: '#7F54B3',
|
||||
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 => {
|
||||
if (!data.args.delay || !data.args.delay_type) {
|
||||
return 'Not set up yet.';
|
||||
return __('Not set up yet.', 'mailpoet');
|
||||
}
|
||||
|
||||
return getDelayInformation(
|
||||
|
@ -1,25 +1,44 @@
|
||||
import { SelectControl } from '@wordpress/components';
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
|
||||
export type DelayTypes = SelectControl.Option & {
|
||||
subtitle: (value: number) => string;
|
||||
};
|
||||
export const DelayTypeOptions: DelayTypes[] = [
|
||||
{
|
||||
label: 'Hours',
|
||||
label: __('Minutes', 'mailpoet'),
|
||||
subtitle: (value: number) =>
|
||||
`Wait for ${value} ${value === 1 ? 'hour' : 'hours'}`,
|
||||
sprintf(
|
||||
_n('Wait for %d minute', 'Wait for %d minutes', value, 'mailpoet'),
|
||||
value,
|
||||
),
|
||||
value: 'MINUTES',
|
||||
},
|
||||
{
|
||||
label: __('Hours', 'mailpoet'),
|
||||
subtitle: (value: number) =>
|
||||
sprintf(
|
||||
_n('Wait for %d hour', 'Wait for %d hours', value, 'mailpoet'),
|
||||
value,
|
||||
),
|
||||
value: 'HOURS',
|
||||
},
|
||||
{
|
||||
label: 'Days',
|
||||
label: __('Days', 'mailpoet'),
|
||||
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',
|
||||
},
|
||||
{
|
||||
label: 'Weeks',
|
||||
label: __('Weeks', 'mailpoet'),
|
||||
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',
|
||||
},
|
||||
];
|
||||
|
@ -2,9 +2,11 @@ import { registerStepType } from '../../editor/store';
|
||||
import { step as SendEmailStep } from './steps/send_email';
|
||||
import { step as SomeoneSubscribesTrigger } from './steps/someone-subscribes';
|
||||
import { step as WpUserRegisteredTrigger } from './steps/wp-user-registered';
|
||||
import { registerStepControls } from './step-controls';
|
||||
|
||||
export const initialize = (): void => {
|
||||
registerStepType(SendEmailStep);
|
||||
registerStepType(WpUserRegisteredTrigger);
|
||||
registerStepType(SomeoneSubscribesTrigger);
|
||||
registerStepControls();
|
||||
};
|
||||
|
@ -0,0 +1,47 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { chartBar } from '@wordpress/icons';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { MoreControlType, StepMoreControlsType } from '../../../types/filters';
|
||||
import { StepType } from '../../../editor/store';
|
||||
import { Step } from '../../../editor/components/automation/types';
|
||||
|
||||
const emailStatisticsControl = (step: Step): MoreControlType => {
|
||||
const hasEmail = step.args?.email_id > 0;
|
||||
return {
|
||||
key: 'statistics',
|
||||
control: {
|
||||
icon: chartBar,
|
||||
title: __('Email statistics', 'mailpoet'),
|
||||
isDisabled: !hasEmail,
|
||||
onClick: () => {
|
||||
window.open(
|
||||
`admin.php?page=mailpoet-newsletters#/stats/${
|
||||
step.args.email_id as string
|
||||
}`,
|
||||
'_blank',
|
||||
);
|
||||
},
|
||||
},
|
||||
slot: () => null,
|
||||
};
|
||||
};
|
||||
|
||||
export function registerStepControls() {
|
||||
Hooks.addFilter(
|
||||
'mailpoet.automation.step.more-controls',
|
||||
'mailpoet',
|
||||
(
|
||||
controls: StepMoreControlsType,
|
||||
step: Step,
|
||||
stepType: StepType,
|
||||
): StepMoreControlsType => {
|
||||
if (stepType.key === 'mailpoet:send-email') {
|
||||
return {
|
||||
statistics: emailStatisticsControl(step),
|
||||
...controls,
|
||||
};
|
||||
}
|
||||
return controls;
|
||||
},
|
||||
);
|
||||
}
|
@ -1,25 +1,52 @@
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button } from '../../../components/button';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
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 {
|
||||
const [redirectToTemplateSelection, setRedirectToTemplateSelection] =
|
||||
useState(false);
|
||||
const [fetchingPreviewLink, setFetchingPreviewLink] = useState(false);
|
||||
|
||||
const { selectedStep, workflowId, workflowSaved } = useSelect(
|
||||
const { selectedStep, automationId, automationSaved, errors } = useSelect(
|
||||
(select) => ({
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
workflowId: select(storeName).getWorkflowData().id,
|
||||
workflowSaved: select(storeName).getWorkflowSaved(),
|
||||
automationId: select(storeName).getAutomationData().id,
|
||||
automationSaved: select(storeName).getAutomationSaved(),
|
||||
errors: select(storeName).getStepError(
|
||||
select(storeName).getSelectedStep().id,
|
||||
),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const emailId = selectedStep?.args?.email_id as number | undefined;
|
||||
const workflowStepId = selectedStep.id;
|
||||
const automationStepId = selectedStep.id;
|
||||
const errorFields = errors?.fields ?? {};
|
||||
const emailIdError = errorFields?.email_id ?? '';
|
||||
|
||||
const createEmail = useCallback(async () => {
|
||||
setRedirectToTemplateSelection(true);
|
||||
@ -31,31 +58,32 @@ export function EditNewsletter(): JSX.Element {
|
||||
type: 'automation',
|
||||
subject: '',
|
||||
options: {
|
||||
workflowId,
|
||||
workflowStepId,
|
||||
automationId,
|
||||
automationStepId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(storeName).updateStepArgs(
|
||||
workflowStepId,
|
||||
automationStepId,
|
||||
'email_id',
|
||||
parseInt(response.data.id as string, 10),
|
||||
);
|
||||
|
||||
dispatch(storeName).save();
|
||||
}, [workflowId, workflowStepId]);
|
||||
}, [automationId, automationStepId]);
|
||||
|
||||
// This component is rendered only when no email ID is set. Once we have the ID
|
||||
// and the workflow is saved, we can safely redirect to the email design flow.
|
||||
// and the automation is saved, we can safely redirect to the email design flow.
|
||||
useEffect(() => {
|
||||
if (redirectToTemplateSelection && emailId && workflowSaved) {
|
||||
if (redirectToTemplateSelection && emailId && automationSaved) {
|
||||
window.location.href = `admin.php?page=mailpoet-newsletters#/template/${emailId}`;
|
||||
}
|
||||
}, [emailId, workflowSaved, redirectToTemplateSelection]);
|
||||
}, [emailId, automationSaved, redirectToTemplateSelection]);
|
||||
|
||||
if (!emailId || redirectToTemplateSelection) {
|
||||
return (
|
||||
<div className={emailIdError ? 'mailpoet-automation-field__error' : ''}>
|
||||
<Button
|
||||
variant="sidebar-primary"
|
||||
centered
|
||||
@ -64,8 +92,17 @@ export function EditNewsletter(): JSX.Element {
|
||||
isBusy={redirectToTemplateSelection}
|
||||
disabled={redirectToTemplateSelection}
|
||||
>
|
||||
Design email
|
||||
{__('Design email', 'mailpoet')}
|
||||
</Button>
|
||||
{emailIdError && (
|
||||
<span className="mailpoet-automation-field-message">
|
||||
{__(
|
||||
'You need to design an email before you can activate the automation',
|
||||
'mailpoet',
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -78,10 +115,21 @@ export function EditNewsletter(): JSX.Element {
|
||||
selectedStep.args.email_id as string
|
||||
}`}
|
||||
>
|
||||
Edit content
|
||||
{__('Edit content', 'mailpoet')}
|
||||
</Button>
|
||||
<Button variant="secondary" centered>
|
||||
Preview
|
||||
<Button
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ComponentProps } from 'react';
|
||||
import { PanelBody, TextareaControl, TextControl } from '@wordpress/components';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ShortcodeHelpText } from './shortcode_help_text';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
@ -31,14 +32,21 @@ function SingleLineTextareaControl(
|
||||
}
|
||||
|
||||
export function EmailPanel(): JSX.Element {
|
||||
const { selectedStep, selectedStepType } = useSelect(
|
||||
const { selectedStep, selectedStepType, errors } = useSelect(
|
||||
(select) => ({
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
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 (
|
||||
<PanelBody opened>
|
||||
<StepName
|
||||
@ -49,8 +57,15 @@ export function EmailPanel(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<TextControl
|
||||
label="“From” name"
|
||||
placeholder="John Doe"
|
||||
className={
|
||||
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) ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
@ -61,9 +76,16 @@ export function EmailPanel(): JSX.Element {
|
||||
}
|
||||
/>
|
||||
<TextControl
|
||||
className={
|
||||
senderAddressErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||
}
|
||||
help={senderAddressErrorMessage}
|
||||
type="email"
|
||||
label="“From” email address"
|
||||
placeholder="you@domain.com"
|
||||
label={__('"From" email address', 'mailpoet')}
|
||||
placeholder={
|
||||
// translators: A placeholder for an email
|
||||
__('you@domain.com', 'mailpoet')
|
||||
}
|
||||
value={(selectedStep.args.sender_address as string) ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
@ -74,17 +96,25 @@ export function EmailPanel(): JSX.Element {
|
||||
}
|
||||
/>
|
||||
<SingleLineTextareaControl
|
||||
label="Subject"
|
||||
placeholder="Type in subject…"
|
||||
className={
|
||||
subjectErrorMessage ? 'mailpoet-automation-field__error' : ''
|
||||
}
|
||||
label={__('Subject', 'mailpoet')}
|
||||
placeholder={__('Type in subject…', 'mailpoet')}
|
||||
value={(selectedStep.args.subject as string) ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(selectedStep.id, 'subject', value)
|
||||
}
|
||||
help={<ShortcodeHelpText />}
|
||||
help={
|
||||
<>
|
||||
{`${subjectErrorMessage} `}
|
||||
<ShortcodeHelpText />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<SingleLineTextareaControl
|
||||
label="Preheader"
|
||||
placeholder="Type in preheader…"
|
||||
label={__('Preheader', 'mailpoet')}
|
||||
placeholder={__('Type in preheader…', 'mailpoet')}
|
||||
value={(selectedStep.args.preheader as string) ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
@ -97,7 +127,7 @@ export function EmailPanel(): JSX.Element {
|
||||
/>
|
||||
|
||||
<div className="mailpoet-automation-email-content-separator" />
|
||||
<PlainBodyTitle title="Email" />
|
||||
<PlainBodyTitle title={__('Email', 'mailpoet')} />
|
||||
<EditNewsletter />
|
||||
</PanelBody>
|
||||
);
|
||||
|
@ -1,27 +1,36 @@
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { useState } from 'react';
|
||||
import { ToggleControl } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { PremiumModal } from 'common/premium_modal';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { GoogleAnalyticsPanelBodyType } from '../../../types/filters';
|
||||
import { PanelBody } from '../../../../../editor/components/panel/panel-body';
|
||||
|
||||
export function GoogleAnalyticsPanel(): JSX.Element {
|
||||
const { selectedStep } = useSelect(
|
||||
(select) => ({ selectedStep: select(storeName).getSelectedStep() }),
|
||||
const { selectedStep, errors } = useSelect(
|
||||
(select) => ({
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
errors: select(storeName).getStepError(
|
||||
select(storeName).getSelectedStep().id,
|
||||
)?.fields?.ga_campaign,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const enabled = typeof selectedStep.args?.ga_campaign !== 'undefined';
|
||||
const panelBody = Hooks.applyFilters(
|
||||
const { updateStepArgs } = useDispatch(storeName);
|
||||
|
||||
const hasValue = typeof selectedStep.args?.ga_campaign !== 'undefined';
|
||||
const [enabled, setEnabled] = useState(hasValue);
|
||||
|
||||
const panelBody: GoogleAnalyticsPanelBodyType = Hooks.applyFilters(
|
||||
'mailpoet.automation.send_email.google_analytics_panel',
|
||||
<PremiumModal
|
||||
onRequestClose={() =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'ga_campaign',
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
onRequestClose={() => {
|
||||
setEnabled(false);
|
||||
updateStepArgs(selectedStep.id, 'ga_campaign', undefined);
|
||||
}}
|
||||
>
|
||||
{__(
|
||||
'Google Analytics tracking is not available in the free version of the MailPoet plugin.',
|
||||
@ -31,17 +40,20 @@ export function GoogleAnalyticsPanel(): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<PanelBody title="Google analytics" initialOpen={false}>
|
||||
<PanelBody
|
||||
title={__('Google Analytics', 'mailpoet')}
|
||||
initialOpen={false}
|
||||
hasErrors={!!errors}
|
||||
>
|
||||
<ToggleControl
|
||||
label="Enable custom GA tracking"
|
||||
label={__('Enable custom GA tracking', 'mailpoet')}
|
||||
checked={enabled}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'ga_campaign',
|
||||
value ? '' : undefined,
|
||||
)
|
||||
onChange={(value) => {
|
||||
setEnabled(value);
|
||||
if (!value) {
|
||||
updateStepArgs(selectedStep.id, 'ga_campaign', undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{enabled && panelBody}
|
||||
|
@ -1,67 +1,113 @@
|
||||
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { useRef, useState } from 'react';
|
||||
import { TextControl, ToggleControl } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { PanelBody } from '../../../../../editor/components/panel/panel-body';
|
||||
|
||||
type ReplyToArgs = {
|
||||
reply_to_name?: string;
|
||||
reply_to_address?: string;
|
||||
};
|
||||
|
||||
export function ReplyToPanel(): JSX.Element {
|
||||
const { selectedStep } = useSelect(
|
||||
const { context, selectedStep, errors } = useSelect(
|
||||
(select) => ({
|
||||
context: select(storeName).getContext(),
|
||||
selectedStep: select(storeName).getSelectedStep(),
|
||||
errors: select(storeName).getStepError(
|
||||
select(storeName).getSelectedStep().id,
|
||||
),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const replyToName = selectedStep.args.reply_to_name as string | undefined;
|
||||
const replyToAddress = selectedStep.args.reply_to_address as
|
||||
| string
|
||||
| undefined;
|
||||
const { updateStepArgs } = useDispatch(storeName);
|
||||
|
||||
const enabled =
|
||||
typeof replyToName !== 'undefined' || typeof replyToAddress !== 'undefined';
|
||||
const args = selectedStep.args as ReplyToArgs;
|
||||
const hasValue = !!args.reply_to_name || !!args.reply_to_address;
|
||||
const [expanded, setExpanded] = useState(hasValue);
|
||||
const prevValue = useRef<{ name?: string; address?: string }>();
|
||||
|
||||
// defaults
|
||||
const argsContext =
|
||||
context.steps['mailpoet:send-email']?.args_schema?.properties ?? {};
|
||||
const defaultName = argsContext.reply_to_name?.default;
|
||||
const defaultAddress = argsContext.reply_to_address?.default;
|
||||
|
||||
const errorFields = errors?.fields ?? {};
|
||||
const replyToNameError = errorFields?.reply_to_name ?? '';
|
||||
const replyToAddressError = errorFields?.reply_to_address ?? '';
|
||||
return (
|
||||
<PanelBody title="Reply to" initialOpen={false}>
|
||||
<PanelBody
|
||||
title={__('Reply to', 'mailpoet')}
|
||||
initialOpen={false}
|
||||
hasErrors={!!replyToNameError || !!replyToAddressError}
|
||||
>
|
||||
<ToggleControl
|
||||
label="Use different email address for getting replies to the email"
|
||||
checked={enabled}
|
||||
label={__(
|
||||
'Use different email address for getting replies to the email',
|
||||
'mailpoet',
|
||||
)}
|
||||
checked={expanded}
|
||||
onChange={(value) => {
|
||||
dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'reply_to_name',
|
||||
value ? '' : undefined,
|
||||
);
|
||||
dispatch(storeName).updateStepArgs(
|
||||
selectedStep.id,
|
||||
'reply_to_address',
|
||||
value ? '' : undefined,
|
||||
);
|
||||
setExpanded(value);
|
||||
const stepId = selectedStep.id;
|
||||
if (value) {
|
||||
const name = prevValue.current?.name ?? defaultName;
|
||||
const address = prevValue.current?.address ?? defaultAddress;
|
||||
updateStepArgs(stepId, 'reply_to_name', name);
|
||||
updateStepArgs(stepId, 'reply_to_address', address);
|
||||
} else {
|
||||
prevValue.current = {
|
||||
name: args.reply_to_name,
|
||||
address: args.reply_to_address,
|
||||
};
|
||||
updateStepArgs(stepId, 'reply_to_name', undefined);
|
||||
updateStepArgs(stepId, 'reply_to_address', undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{enabled && (
|
||||
{expanded && (
|
||||
<>
|
||||
<TextControl
|
||||
label="“Reply to” name"
|
||||
placeholder="John Doe"
|
||||
value={replyToName ?? ''}
|
||||
className={
|
||||
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={args.reply_to_name ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
updateStepArgs(
|
||||
selectedStep.id,
|
||||
'reply_to_name',
|
||||
value,
|
||||
value || undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextControl
|
||||
className={
|
||||
replyToAddressError ? 'mailpoet-automation-field__error' : ''
|
||||
}
|
||||
help={replyToAddressError}
|
||||
type="email"
|
||||
label="“Reply to” email address"
|
||||
placeholder="you@domain.com"
|
||||
value={replyToAddress ?? ''}
|
||||
label={__('"Reply to" email address', 'mailpoet')}
|
||||
placeholder={
|
||||
// translators: A placeholder for an email
|
||||
__('you@domain.com', 'mailpoet')
|
||||
}
|
||||
value={args.reply_to_address ?? ''}
|
||||
onChange={(value) =>
|
||||
dispatch(storeName).updateStepArgs(
|
||||
updateStepArgs(
|
||||
selectedStep.id,
|
||||
'reply_to_address',
|
||||
value,
|
||||
value || undefined,
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export function ShortcodeHelpText(): JSX.Element {
|
||||
return (
|
||||
<span className="mailpoet-shortcode-selector">
|
||||
@ -6,9 +8,8 @@ export function ShortcodeHelpText(): JSX.Element {
|
||||
href="https://kb.mailpoet.com/article/215-personalize-newsletter-with-shortcodes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-beacon-article="59d662ef042863379ddc6faa"
|
||||
>
|
||||
MailPoet shortcodes
|
||||
{__('MailPoet shortcodes', 'mailpoet')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ComponentProps, ComponentType, useEffect, useState } from 'react';
|
||||
import { Spinner as WpSpinner } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { MailPoetAjax } from '../../../../../../ajax';
|
||||
|
||||
// @types/wordpress__components don't define "className", which is supported
|
||||
@ -39,7 +40,7 @@ export function Thumbnail({ emailId }: Props): JSX.Element {
|
||||
<img
|
||||
className="mailpoet-automation-thumbnail-image"
|
||||
src={thumbnailUrl}
|
||||
alt="Email thumbnail"
|
||||
alt={__('Email thumbnail', 'mailpoet')}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Hooks } from 'wp-js-hooks';
|
||||
import { Icon } from './icon';
|
||||
import { Edit } from './edit';
|
||||
import { StepType } from '../../../../editor/store/types';
|
||||
import { State, StepType } from '../../../../editor/store/types';
|
||||
import { Step } from '../../../../editor/components/automation/types';
|
||||
|
||||
export const step: StepType = {
|
||||
key: 'mailpoet:send-email',
|
||||
group: 'actions',
|
||||
title: 'Send email',
|
||||
description: 'An email will be sent to subscriber',
|
||||
subtitle: (data) => (data.args.name as string) ?? 'Send email',
|
||||
title: __('Send email', 'mailpoet'),
|
||||
description: __('An email will be sent to subscriber', 'mailpoet'),
|
||||
subtitle: (data) =>
|
||||
(data.args.name as string) ?? __('Send email', 'mailpoet'),
|
||||
foreground: '#996800',
|
||||
background: '#FCF9E8',
|
||||
icon: Icon,
|
||||
edit: Edit,
|
||||
createStep: (stepData: Step, state: State) =>
|
||||
Hooks.applyFilters(
|
||||
'mailpoet.automation.send_email.create_step',
|
||||
stepData,
|
||||
state.automationData.id,
|
||||
),
|
||||
} as const;
|
||||
|
@ -30,7 +30,7 @@ export function ListPanel(): JSX.Element {
|
||||
|
||||
<FormTokenField
|
||||
label={__(
|
||||
'When someone subscribers to the following list(s):',
|
||||
'When someone subscribes to the following lists:',
|
||||
'mailpoet',
|
||||
)}
|
||||
placeholder={__('Any list', 'mailpoet')}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { commentAuthorAvatar } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { Edit } from './edit';
|
||||
@ -13,7 +13,7 @@ export const step: StepType = {
|
||||
'Starts the automation when a new subscriber is added to MailPoet.',
|
||||
'mailpoet',
|
||||
),
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
subtitle: () => _x('Trigger', 'noun', 'mailpoet'),
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.4' }}>
|
||||
{commentAuthorAvatar}
|
||||
|
@ -1,11 +1,31 @@
|
||||
import { PanelBody } from '@wordpress/components';
|
||||
import { dispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import ReactStringReplace from 'react-string-replace';
|
||||
import { storeName } from '../../../../../editor/store';
|
||||
import { PlainBodyTitle } from '../../../../../editor/components/panel';
|
||||
import { userRoles } from './role';
|
||||
import { FormTokenField } from '../../../components/form-token-field';
|
||||
|
||||
function SettingsInfoText(): JSX.Element {
|
||||
return (
|
||||
<p>
|
||||
{ReactStringReplace(
|
||||
__(
|
||||
'[link]Subscribe in registration form[/link] setting must be enabled.',
|
||||
'mailpoet',
|
||||
),
|
||||
/\[link\](.*?)\[\/link\]/g,
|
||||
(match) => (
|
||||
<a href="admin.php?page=mailpoet-settings#/basics" target="_blank">
|
||||
{match}
|
||||
</a>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function RolePanel(): JSX.Element {
|
||||
const { selectedStep } = useSelect(
|
||||
(select) => ({
|
||||
@ -20,9 +40,11 @@ export function RolePanel(): JSX.Element {
|
||||
const selected = userRoles.filter((role): boolean =>
|
||||
rawSelected.includes(role.id as string),
|
||||
);
|
||||
|
||||
return (
|
||||
<PanelBody opened>
|
||||
<PlainBodyTitle title={__('Trigger settings', 'mailpoet')} />
|
||||
<SettingsInfoText />
|
||||
<FormTokenField
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { wordpress } from '@wordpress/icons';
|
||||
import { StepType } from '../../../../editor/store';
|
||||
import { Edit } from './edit';
|
||||
@ -13,7 +13,7 @@ export const step: StepType = {
|
||||
'Starts the automation when a new user registered in WordPress.',
|
||||
'mailpoet',
|
||||
),
|
||||
subtitle: () => __('Trigger', 'mailpoet'),
|
||||
subtitle: () => _x('Trigger', 'noun', 'mailpoet'),
|
||||
icon: () => (
|
||||
<div style={{ width: '100%', height: '100%', scale: '1.12' }}>
|
||||
{wordpress}
|
||||
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* The types in this file document the expected return types of specific
|
||||
* filters.
|
||||
*/
|
||||
import { Step } from '../../../editor/components/automation/types';
|
||||
|
||||
// mailpoet.automation.send_email.create_step
|
||||
export type SendEmailCreateStepType = (
|
||||
step: Step,
|
||||
automationId: number,
|
||||
) => Step;
|
||||
|
||||
// mailpoet.automation.send_email.google_analytics_panel
|
||||
export type GoogleAnalyticsPanelBodyType = JSX.Element;
|
@ -3,27 +3,30 @@ import { __ } from '@wordpress/i18n';
|
||||
import { Notice } from '../../notices/notice';
|
||||
|
||||
export const LISTING_NOTICE_PARAMETERS = {
|
||||
workflowHadBeenDeleted: 'mailpoet-had-been-deleted',
|
||||
workflowDeleted: 'mailpoet-workflow-deleted',
|
||||
automationHadBeenDeleted: 'mailpoet-had-been-deleted',
|
||||
automationDeleted: 'mailpoet-automation-deleted',
|
||||
};
|
||||
|
||||
export function WorkflowListingNotices(): JSX.Element {
|
||||
const workflowHadBeenDeleted = parseInt(
|
||||
export function AutomationListingNotices(): JSX.Element {
|
||||
const automationHadBeenDeleted = parseInt(
|
||||
getQueryArg(
|
||||
window.location.href,
|
||||
LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted,
|
||||
LISTING_NOTICE_PARAMETERS.automationHadBeenDeleted,
|
||||
) as string,
|
||||
10,
|
||||
);
|
||||
const workflowDeleted = parseInt(
|
||||
const automationDeleted = parseInt(
|
||||
getQueryArg(
|
||||
window.location.href,
|
||||
LISTING_NOTICE_PARAMETERS.workflowDeleted,
|
||||
LISTING_NOTICE_PARAMETERS.automationDeleted,
|
||||
) as string,
|
||||
10,
|
||||
);
|
||||
|
||||
if (Number.isNaN(workflowHadBeenDeleted) && Number.isNaN(workflowDeleted)) {
|
||||
if (
|
||||
Number.isNaN(automationHadBeenDeleted) &&
|
||||
Number.isNaN(automationDeleted)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -32,7 +35,7 @@ export function WorkflowListingNotices(): JSX.Element {
|
||||
...Object.values(LISTING_NOTICE_PARAMETERS),
|
||||
);
|
||||
window.history.pushState('', '', urlWithoutNotices);
|
||||
if (workflowHadBeenDeleted) {
|
||||
if (automationHadBeenDeleted) {
|
||||
return (
|
||||
<Notice type="error" closable timeout={false}>
|
||||
<p>
|
||||
@ -44,10 +47,10 @@ export function WorkflowListingNotices(): JSX.Element {
|
||||
</Notice>
|
||||
);
|
||||
}
|
||||
if (workflowDeleted) {
|
||||
if (automationDeleted) {
|
||||
return (
|
||||
<Notice type="success" closable timeout={false}>
|
||||
<p>{__('1 workflow moved to the Trash.', 'mailpoet')}</p>
|
||||
<p>{__('1 automation moved to the Trash.', 'mailpoet')}</p>
|
||||
</Notice>
|
||||
);
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
export enum WorkflowStatus {
|
||||
export enum AutomationStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
DRAFT = 'draft',
|
||||
TRASH = 'trash',
|
||||
// @ToDo: Needs to be aligned with MAILPOET-4731
|
||||
DEACTIVATING = 'deactivating',
|
||||
}
|
||||
|
||||
export type Workflow = {
|
||||
export type Automation = {
|
||||
id: number;
|
||||
name: string;
|
||||
status: WorkflowStatus;
|
||||
status: AutomationStatus;
|
||||
stats: {
|
||||
totals: {
|
||||
entered: number;
|
@ -1,19 +1,19 @@
|
||||
import { Button } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { Workflow } from '../../workflow';
|
||||
import { Automation } from '../../automation';
|
||||
import { MailPoet } from '../../../../mailpoet';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
automation: Automation;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export function EditWorkflow({ workflow, label }: Props): JSX.Element {
|
||||
export function EditAutomation({ automation, label }: Props): JSX.Element {
|
||||
return (
|
||||
<Button
|
||||
variant="link"
|
||||
href={addQueryArgs(MailPoet.urls.automationEditor, { id: workflow.id })}
|
||||
href={addQueryArgs(MailPoet.urls.automationEditor, { id: automation.id })}
|
||||
>
|
||||
{label ?? __('Edit', 'mailpoet')}
|
||||
</Button>
|
@ -1,2 +1,2 @@
|
||||
export * from './edit-workflow';
|
||||
export * from './edit-automation';
|
||||
export * from './undo-trash';
|
||||
|
@ -2,23 +2,23 @@ import { Button } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { storeName } from '../../store/constants';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
previousStatus: WorkflowStatus;
|
||||
automation: Automation;
|
||||
previousStatus: AutomationStatus;
|
||||
};
|
||||
|
||||
export function UndoTrashButton({
|
||||
workflow,
|
||||
automation,
|
||||
previousStatus,
|
||||
}: Props): JSX.Element {
|
||||
const { restoreWorkflow } = useDispatch(storeName);
|
||||
const { restoreAutomation } = useDispatch(storeName);
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => restoreWorkflow(workflow, previousStatus)}
|
||||
onClick={() => restoreAutomation(automation, previousStatus)}
|
||||
>
|
||||
{__('Undo', 'mailpoet')}
|
||||
</Button>
|
||||
|
@ -2,32 +2,26 @@ 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';
|
||||
import { useDeleteButton, useRestoreButton, useTrashButton } from '../menu';
|
||||
import { Automation } from '../../automation';
|
||||
import { EditAutomation } from '../actions';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
automation: Automation;
|
||||
};
|
||||
|
||||
export function Actions({ workflow }: Props): JSX.Element {
|
||||
export function Actions({ automation }: 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 trash = useTrashButton(automation);
|
||||
const restore = useRestoreButton(automation);
|
||||
const del = useDeleteButton(automation);
|
||||
|
||||
const menuItems = [duplicate, trash, restore, del].filter((item) => item);
|
||||
const menuItems = [trash, restore, del].filter((item) => item);
|
||||
|
||||
return (
|
||||
<div className="mailpoet-automation-listing-cell-actions">
|
||||
<EditWorkflow workflow={workflow} />
|
||||
<EditAutomation automation={automation} />
|
||||
{menuItems.map(({ control, slot }) => (
|
||||
<Fragment key={control.title}>{slot}</Fragment>
|
||||
))}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { EditWorkflow } from '../actions';
|
||||
import { Workflow } from '../../workflow';
|
||||
import { EditAutomation } from '../actions';
|
||||
import { Automation } from '../../automation';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
automation: Automation;
|
||||
};
|
||||
|
||||
export function Name({ workflow }: Props): JSX.Element {
|
||||
return <EditWorkflow workflow={workflow} label={workflow.name} />;
|
||||
export function Name({ automation }: Props): JSX.Element {
|
||||
return <EditAutomation automation={automation} label={automation.name} />;
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
automation: Automation;
|
||||
};
|
||||
|
||||
export function Status({ workflow }: Props): JSX.Element {
|
||||
export function Status({ automation }: Props): JSX.Element {
|
||||
return (
|
||||
<div className="mailpoet-automation-listing-cell-status">
|
||||
{workflow.status === WorkflowStatus.ACTIVE
|
||||
{automation.status === AutomationStatus.ACTIVE
|
||||
? __('Active', 'mailpoet')
|
||||
: __('Not active', 'mailpoet')}
|
||||
: __('Draft', 'mailpoet')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,30 +1,33 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Workflow } from '../../workflow';
|
||||
import { _x } from '@wordpress/i18n';
|
||||
import { Automation } from '../../automation';
|
||||
import { Statistics } from '../../../components/statistics';
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
automation: Automation;
|
||||
};
|
||||
|
||||
export function Subscribers({ workflow }: Props): JSX.Element {
|
||||
export function Subscribers({ automation }: Props): JSX.Element {
|
||||
return (
|
||||
<Statistics
|
||||
labelPosition="after"
|
||||
items={[
|
||||
{
|
||||
key: 'entered',
|
||||
label: __('Entered', 'mailpoet'),
|
||||
value: workflow.stats.totals.entered,
|
||||
// translators: Total number of subscribers who entered an automation
|
||||
label: _x('Entered', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.entered,
|
||||
},
|
||||
{
|
||||
key: 'processing',
|
||||
label: __('Processing', 'mailpoet'),
|
||||
value: workflow.stats.totals.in_progress,
|
||||
// translators: Total number of subscribers who are being processed in an automation
|
||||
label: _x('Processing', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.in_progress,
|
||||
},
|
||||
{
|
||||
key: 'exited',
|
||||
label: __('Exited', 'mailpoet'),
|
||||
value: workflow.stats.totals.exited,
|
||||
// translators: Total number of subscribers who exited an automation, no matter the result
|
||||
label: _x('Exited', 'automation stats', 'mailpoet'),
|
||||
value: automation.stats.totals.exited,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -4,13 +4,13 @@ import { useDispatch } from '@wordpress/data';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Item } from './item';
|
||||
import { storeName } from '../../store';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
||||
export const useDeleteButton = (automation: Automation): Item | undefined => {
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const { deleteWorkflow } = useDispatch(storeName);
|
||||
const { deleteAutomation } = useDispatch(storeName);
|
||||
|
||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
||||
if (automation.status !== AutomationStatus.TRASH) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -27,16 +27,16 @@ export const useDeleteButton = (workflow: Workflow): Item | undefined => {
|
||||
title={__('Permanently delete automation', 'mailpoet')}
|
||||
confirmButtonText={__('Yes, permanently delete', 'mailpoet')}
|
||||
__experimentalHideHeader={false}
|
||||
onConfirm={() => deleteWorkflow(workflow)}
|
||||
onConfirm={() => deleteAutomation(automation)}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
>
|
||||
{sprintf(
|
||||
// translators: %s is the workflow name
|
||||
// translators: %s is the automation name
|
||||
__(
|
||||
'Are you sure you want to permanently delete "%s" and all associated data? This cannot be undone!',
|
||||
'mailpoet',
|
||||
),
|
||||
workflow.name,
|
||||
automation.name,
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
),
|
||||
|
@ -2,12 +2,14 @@ import { useDispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Item } from './item';
|
||||
import { storeName } from '../../store';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
|
||||
const { duplicateWorkflow } = useDispatch(storeName);
|
||||
export const useDuplicateButton = (
|
||||
automation: Automation,
|
||||
): Item | undefined => {
|
||||
const { duplicateAutomation } = useDispatch(storeName);
|
||||
|
||||
if (workflow.status === WorkflowStatus.TRASH) {
|
||||
if (automation.status === AutomationStatus.TRASH) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -16,7 +18,7 @@ export const useDuplicateButton = (workflow: Workflow): Item | undefined => {
|
||||
control: {
|
||||
title: __('Duplicate', 'mailpoet'),
|
||||
icon: null,
|
||||
onClick: () => duplicateWorkflow(workflow),
|
||||
onClick: () => duplicateAutomation(automation),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -2,12 +2,12 @@ import { useDispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Item } from './item';
|
||||
import { storeName } from '../../store';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
export const useRestoreButton = (workflow: Workflow): Item | undefined => {
|
||||
const { restoreWorkflow } = useDispatch(storeName);
|
||||
export const useRestoreButton = (automation: Automation): Item | undefined => {
|
||||
const { restoreAutomation } = useDispatch(storeName);
|
||||
|
||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
||||
if (automation.status !== AutomationStatus.TRASH) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export const useRestoreButton = (workflow: Workflow): Item | undefined => {
|
||||
control: {
|
||||
title: __('Restore', 'mailpoet'),
|
||||
icon: null,
|
||||
onClick: () => restoreWorkflow(workflow, WorkflowStatus.DRAFT),
|
||||
onClick: () => restoreAutomation(automation, AutomationStatus.DRAFT),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -1,42 +1,42 @@
|
||||
import { useState } from 'react';
|
||||
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { __, _x, sprintf } from '@wordpress/i18n';
|
||||
import { Item } from './item';
|
||||
import { storeName } from '../../store';
|
||||
import { Workflow, WorkflowStatus } from '../../workflow';
|
||||
import { Automation, AutomationStatus } from '../../automation';
|
||||
|
||||
export const useTrashButton = (workflow: Workflow): Item | undefined => {
|
||||
export const useTrashButton = (automation: Automation): Item | undefined => {
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const { trashWorkflow } = useDispatch(storeName);
|
||||
const { trashAutomation } = useDispatch(storeName);
|
||||
|
||||
if (workflow.status === WorkflowStatus.TRASH) {
|
||||
if (automation.status === AutomationStatus.TRASH) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'trash',
|
||||
control: {
|
||||
title: __('Trash', 'mailpoet'),
|
||||
title: _x('Trash', 'verb', 'mailpoet'),
|
||||
icon: null,
|
||||
onClick: () => setShowDialog(true),
|
||||
},
|
||||
slot: (
|
||||
<ConfirmDialog
|
||||
isOpen={showDialog}
|
||||
title={__('Trash workflow', 'mailpoet')}
|
||||
title={__('Trash automation', 'mailpoet')}
|
||||
confirmButtonText={__('Yes, move to trash', 'mailpoet')}
|
||||
__experimentalHideHeader={false}
|
||||
onConfirm={() => trashWorkflow(workflow)}
|
||||
onConfirm={() => trashAutomation(automation)}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
>
|
||||
{sprintf(
|
||||
// translators: %s is the workflow name
|
||||
// translators: %s is the automation name
|
||||
__(
|
||||
'Are you sure you want to move the workflow "%s" to the Trash?',
|
||||
'Are you sure you want to move the automation "%s" to the Trash?',
|
||||
'mailpoet',
|
||||
),
|
||||
workflow.name,
|
||||
automation.name,
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
),
|
||||
|
@ -1,27 +1,27 @@
|
||||
import { Workflow } from './workflow';
|
||||
import { Automation } from './automation';
|
||||
import { Actions, Name, Status, Subscribers } from './components/cells';
|
||||
|
||||
export function getRow(workflow: Workflow): object[] {
|
||||
export function getRow(automation: Automation): object[] {
|
||||
return [
|
||||
{
|
||||
id: workflow.id,
|
||||
value: workflow.name,
|
||||
display: <Name workflow={workflow} />,
|
||||
id: automation.id,
|
||||
value: automation.name,
|
||||
display: <Name automation={automation} />,
|
||||
},
|
||||
{
|
||||
id: workflow.id,
|
||||
id: automation.id,
|
||||
value: null,
|
||||
display: <Subscribers workflow={workflow} />,
|
||||
display: <Subscribers automation={automation} />,
|
||||
},
|
||||
{
|
||||
id: workflow.id,
|
||||
value: workflow.status,
|
||||
display: <Status workflow={workflow} />,
|
||||
id: automation.id,
|
||||
value: automation.status,
|
||||
display: <Status automation={automation} />,
|
||||
},
|
||||
{
|
||||
id: workflow.id,
|
||||
id: automation.id,
|
||||
value: null,
|
||||
display: <Actions workflow={workflow} />,
|
||||
display: <Actions automation={automation} />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -1,48 +1,65 @@
|
||||
import { Search, TableCard } from '@woocommerce/components/build';
|
||||
import { TabPanel } from '@wordpress/components';
|
||||
import { TableCard } from '@woocommerce/components/build';
|
||||
import { Button, Flex, TabPanel } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, _x } from '@wordpress/i18n';
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { plusIcon } from 'common/button/icon/plus';
|
||||
import { getRow } from './get-row';
|
||||
import { storeName } from './store/constants';
|
||||
import { Workflow, WorkflowStatus } from './workflow';
|
||||
import { storeName } from './store';
|
||||
import { Automation, AutomationStatus } from './automation';
|
||||
import { MailPoet } from '../../mailpoet';
|
||||
|
||||
const tabConfig = [
|
||||
{
|
||||
name: 'all',
|
||||
title: 'All',
|
||||
title: __('All', 'mailpoet'),
|
||||
className: 'mailpoet-tab-all',
|
||||
},
|
||||
{
|
||||
name: WorkflowStatus.ACTIVE,
|
||||
title: 'Active',
|
||||
name: AutomationStatus.ACTIVE,
|
||||
title: __('Active', 'mailpoet'),
|
||||
className: 'mailpoet-tab-active',
|
||||
},
|
||||
{
|
||||
name: WorkflowStatus.INACTIVE,
|
||||
title: 'Inactive',
|
||||
className: 'mailpoet-tab-inactive',
|
||||
},
|
||||
{
|
||||
name: WorkflowStatus.DRAFT,
|
||||
title: 'Draft',
|
||||
name: AutomationStatus.DRAFT,
|
||||
title: _x('Draft', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-draft',
|
||||
},
|
||||
{
|
||||
name: WorkflowStatus.TRASH,
|
||||
title: 'Trash',
|
||||
name: AutomationStatus.TRASH,
|
||||
title: _x('Trash', 'noun', 'mailpoet'),
|
||||
className: 'mailpoet-tab-trash',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const tableHeaders = [
|
||||
{ key: 'name', label: __('Name', 'mailpoet') },
|
||||
{
|
||||
key: 'name',
|
||||
label: __('Name', 'mailpoet'),
|
||||
cellClassName: 'mailpoet-automation-listing-cell-name',
|
||||
},
|
||||
{ key: 'subscribers', label: __('Subscribers', 'mailpoet') },
|
||||
{ key: 'status', label: __('Status', 'mailpoet') },
|
||||
{ key: 'actions' },
|
||||
] as const;
|
||||
|
||||
export function AutomationListingHeader(): JSX.Element {
|
||||
return (
|
||||
<Flex className="mailpoet-automation-listing-heading">
|
||||
<h1 className="wp-heading-inline">{__('Automations', 'mailpoet')}</h1>
|
||||
<Button
|
||||
href={MailPoet.urls.automationTemplates}
|
||||
icon={plusIcon}
|
||||
variant="primary"
|
||||
className="mailpoet-add-new-button"
|
||||
>
|
||||
{__('New automation', 'mailpoet')}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export function AutomationListing(): JSX.Element {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
@ -51,14 +68,14 @@ export function AutomationListing(): JSX.Element {
|
||||
[location],
|
||||
);
|
||||
|
||||
const workflows = useSelect((select) => select(storeName).getWorkflows());
|
||||
const { loadWorkflows } = useDispatch(storeName);
|
||||
const automations = useSelect((select) => select(storeName).getAutomations());
|
||||
const { loadAutomations } = useDispatch(storeName);
|
||||
|
||||
const status = pageSearch.get('status');
|
||||
|
||||
useEffect(() => {
|
||||
loadWorkflows();
|
||||
}, [loadWorkflows]);
|
||||
loadAutomations();
|
||||
}, [loadAutomations]);
|
||||
|
||||
// focus tab button on status change (needed due to the force re-mount below)
|
||||
useLayoutEffect(() => {
|
||||
@ -86,24 +103,24 @@ export function AutomationListing(): JSX.Element {
|
||||
[pageSearch, history],
|
||||
);
|
||||
|
||||
const groupedWorkflows = useMemo<Record<string, Workflow[]>>(() => {
|
||||
const groupedAutomations = useMemo<Record<string, Automation[]>>(() => {
|
||||
const grouped = { all: [] };
|
||||
(workflows ?? []).forEach((workflow) => {
|
||||
if (!grouped[workflow.status]) {
|
||||
grouped[workflow.status] = [];
|
||||
(automations ?? []).forEach((automation) => {
|
||||
if (!grouped[automation.status]) {
|
||||
grouped[automation.status] = [];
|
||||
}
|
||||
grouped[workflow.status].push(workflow);
|
||||
if (workflow.status !== WorkflowStatus.TRASH) {
|
||||
grouped.all.push(workflow);
|
||||
grouped[automation.status].push(automation);
|
||||
if (automation.status !== AutomationStatus.TRASH) {
|
||||
grouped.all.push(automation);
|
||||
}
|
||||
});
|
||||
return grouped;
|
||||
}, [workflows]);
|
||||
}, [automations]);
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
tabConfig.map((tab) => {
|
||||
const count = (groupedWorkflows[tab.name] ?? []).length;
|
||||
const count = (groupedAutomations[tab.name] ?? []).length;
|
||||
return {
|
||||
name: tab.name,
|
||||
title: (
|
||||
@ -115,50 +132,39 @@ export function AutomationListing(): JSX.Element {
|
||||
className: tab.className,
|
||||
};
|
||||
}),
|
||||
[groupedWorkflows],
|
||||
[groupedAutomations],
|
||||
);
|
||||
|
||||
const renderTabs = useCallback(
|
||||
(tab) => {
|
||||
const filteredWorkflows: Workflow[] = groupedWorkflows[tab.name] ?? [];
|
||||
const filteredAutomations: Automation[] =
|
||||
groupedAutomations[tab.name] ?? [];
|
||||
const rowsPerPage = parseInt(pageSearch.get('per_page') ?? '25', 10);
|
||||
const currentPage = parseInt(pageSearch.get('paged') ?? '1', 10);
|
||||
const start = (currentPage - 1) * rowsPerPage;
|
||||
const rows = filteredWorkflows
|
||||
.map((workflow) => getRow(workflow))
|
||||
const rows = filteredAutomations
|
||||
.map((automation) => getRow(automation))
|
||||
.slice(start, start + rowsPerPage);
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
className="mailpoet-automation-listing"
|
||||
title=""
|
||||
isLoading={!workflows}
|
||||
isLoading={!automations}
|
||||
headers={tableHeaders}
|
||||
rows={rows}
|
||||
rowKey={(_, i) => filteredWorkflows[i].id}
|
||||
rowKey={(_, i) => filteredAutomations[i].id}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onQueryChange={(key) => (value) => {
|
||||
updateUrlSearchString({ [key]: value });
|
||||
}}
|
||||
totalRows={filteredWorkflows.length}
|
||||
totalRows={filteredAutomations.length}
|
||||
query={Object.fromEntries(pageSearch)}
|
||||
hasSearch
|
||||
showMenu={false}
|
||||
actions={[
|
||||
<Search
|
||||
className="mailpoet-automation-listing-search"
|
||||
allowFreeTextSearch
|
||||
inlineTags
|
||||
key="search"
|
||||
type="custom"
|
||||
disabled={!workflows}
|
||||
autocompleter={{}}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[workflows, groupedWorkflows, pageSearch, updateUrlSearchString],
|
||||
[automations, groupedAutomations, pageSearch, updateUrlSearchString],
|
||||
);
|
||||
|
||||
return (
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user