Compare commits

...

433 Commits

Author SHA1 Message Date
fdcd343ed5 Release 3.96.0 2022-08-29 17:18:59 +02:00
42bec13574 Add acceptance test for creating newsletter as draft w/o list
[MAILPOET-4549]
2022-08-25 13:29:00 +02:00
7c4afd5aa8 Cleanup preparation of sending page form fields
[MAILPOET-4549]
2022-08-25 13:29:00 +02:00
c4952a636a Disable segments selector validation when saving draft
[MAILPOET-4549]
2022-08-25 13:29:00 +02:00
e54f1d6263 Make workflow details section collapsible
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
4c5e7095b1 Remove notice parameters from listing URL
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
bb9604f22f Use constants for redirecting parameters
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
2bec938336 Disable editing of trashed workflows
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
3dd37fa5b7 Redirect and show success message on trashing automation
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
5224009301 Add 'notice-${type} class to notices
In order to use the native CSS styles of the WordPress notices, we need
to add the specific notice-${type} classes to the notice.

[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
deb2c74a3a Add Trash button
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
ba58022259 Remove deprecated types
WARN  deprecated @types/wordpress__api-fetch@3.23.1: This is a stub types definition. @wordpress/api-fetch provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__element@2.14.1: This is a stub types definition. wordpress__element provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__escape-html@1.8.0: This is a stub types definition. wordpress__escape-html provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__hooks@2.11.0: This is a stub types definition. @wordpress/hooks provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__html-entities@2.7.0: This is a stub types definition. wordpress__html-entities provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__i18n@3.11.0: This is a stub types definition. wordpress__i18n provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__url@2.14.0: This is a stub types definition. wordpress__url provides its own type definitions, so you do not need this installed.
 WARN  deprecated @types/wordpress__a11y@2.9.0: This is a stub types definition. wordpress__a11y provides its own type definitions, so you do not need this installed.

[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
a699b0db8e Add activated date and author to workflow sidebar
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
9a1cd26a40 Add notices slot to editor
We use <EditorNotices /> to display notices in the automation editor. This
allows for the usage of createErrorNotice() from the notices store to create
notices.

[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
5f07b2a2b2 Add urls property to MailPoet object
In order to redirect to the listing page after trashing a workflow
we need the URL of that page. This URL will be stored in
MailPoet.urls.automationListing

[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
f930db422e Extend Workflow type with author, activated_at and new trash status
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
b546344e42 Allow to query workflows by status
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
dde3b159ea Add status parameter to getWorkflows method
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
9a74dec86a Add trash status to Workflow
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
dce1b1403c Add author property to Workflow endpoints
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
ffc7773a6a Communicate workflow author to editor
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
34d94edd3e Add author to Workflow
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
8861909859 Communicate activated_at to editor
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
4bdec5165d Add activated_at property to workflow endpoints
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
5bd994c01c Add activatedAt to workflow
[MAILPOET-4417]
2022-08-24 14:02:08 +02:00
cd3e2556dd Fix mailpoet_tracking_config type
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
4dd0218ce1 Allow wider modals to fit longer titles and translations
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
9930f2915c Implement plugin activation and download from within upgrade modal
[MAILPOET-4416]
[MAILPOET-4559]
2022-08-24 12:18:44 +02:00
5547613f31 Remove full settings from the MailPoet JS object
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
6342402e41 Move subscribers count to layout
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
24cca1ba04 Cache total subscribers count, add tests
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
7ce53adc33 Use static premium download URL instead of fetching it from bridge
This was causing HTTP requests to bridge on each page load for some of MP admin pages.

[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
84ca7143ff Move some more general properties to layout
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
5c08d22736 Move all premium & plan upgrade info to root layout, remove duplicities
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
558d709a18 Update premium upgrade modal to display info based on license status
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
3cc385dd52 Implement premium upgrade logic for upgrade dialog
[MAILPOET-4416]
2022-08-24 12:18:44 +02:00
e843d25c8b Check that changelog is not null before passing it to substr()
Starting with PHP 8.1, substr() generates a warning if the first
parameter is null. So, before calling it, we need to check if the
changelog is null or not.

[MAILPOET-4595]
2022-08-23 14:22:55 +02:00
a471e4d8c4 Don't show spacer after the last item in <Steps> component
[MAILPOET-4593]
2022-08-23 14:21:28 +02:00
26a6f1be32 Use step key when building workflows from sequence
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
53434d23b8 Use default args when building the Workflow from sequence
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
83f5c6cc46 Add test for WorkflowTemplatesGetEndpoint
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
ed56df2398 Add WorkflowTemplate GET endpoint
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
8b78fdd4b4 Update frontend to interact with new template create endpoint
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
baa4d369af Introduce template storage and rebuild create form template endpoint
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
1140ee3129 Add WorkflowTemplate entity
[MAILPOET-4538]
2022-08-23 14:03:24 +02:00
804ad97036 Release 3.95.1 2022-08-23 08:56:57 -03:00
b4bba9869e Fix Gutenberg token-field styles compatibility token
[MAILPOET-4582]
2022-08-22 13:44:35 +02:00
8567614697 Add integration test with SMTP filters
[MAILPOET-4437]
2022-08-22 13:16:51 +02:00
52795c3972 Add new filters to the SMTP sending method
[MAILPOET-4437]
2022-08-22 13:16:51 +02:00
b7cccdae95 Adjust tests, remove models and Tasks\Sending
[MAILPOET-4371]
2022-08-22 13:06:43 +02:00
873c295e99 Remove Paris models and Tasks\Sending from PostNotificationScheduler
[MAILPOET-4371]
2022-08-22 13:06:43 +02:00
e3b02a9a64 PR feedback adjustments
[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
18b1d9799f Fix failing test for getLatestTasks
[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
c22d23f91c Remove Tasks\State class and relevant test file
[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
e30e1847e2 Move buildTaskData tests to AdminPages/HelpTest.php
[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
73d15c8c44 Move getLatestTasks to ScheduledTasksRepository
- Move the method
- Adjust the usages
- Adjust and update tests

[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
bf1ddbedbc Move getCountsPerStatus to ScheduledTasksRepository
- Move getCountsPerStatus
- Defined test for getCountsPerStatus
- Update usage

[MAILPOET-4355]
2022-08-22 11:24:06 +02:00
4687fcd44f Remove refactored method from old model
[MAILPOET-4310]
2022-08-22 11:04:45 +02:00
5f88a27ccb Extend integration test
[MAILPOET-4310]
2022-08-22 11:04:45 +02:00
bfc2a7d2af Update dependencies in tests
[MAILPOET-4310]
2022-08-22 11:04:45 +02:00
c1e9f86a32 Remove usage of old model for creating post notification history
[MAILPOET-4310]
2022-08-22 11:04:45 +02:00
5dfda9f3e2 Move task type const from entity to worker
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
77eaa7e710 Remove check that sending queue is deleted at
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
a39b73041c Extend integration test
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
a841671695 Add SendingQueue factory
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
a362b7e014 Delete Sending::getRunningQueues()
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
bf74a506b7 Replace using Sending::getRunningQueues
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
5f30c7a511 Add method for getting running sending tasks
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
7ef341e38a Replace Sending::getRunningQueues in integration test
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
6f1e5c9a98 Add new const with the type sending
[MAILPOET-4366]
2022-08-19 15:11:23 +02:00
d54129670d Reorganize information sent to HelpScout
We are now using the `session-data` method of the Beacon API to send
site information to HelpScout
(https://developer.helpscout.com/beacon-2/web/javascript-api/#beacon-session-data).
This method has a limit of 20 attribute-value pairs and since we had
more, it was necessary to combinte a few values under the same
attribute.

[MAILPOET-4525]
2022-08-18 15:43:12 +02:00
f19de77f70 Fix sending system info when creating a HelpScout ticket
We were calling the wrong Beacon API method to send system info
data to HelpScout when users create tickets inside WordPress. This
commit fixes this by calling the method `session-data` instead of
`identify`. It is likely that `identify` was the correct method in the
past, but the Beacon API changed
(https://developer.helpscout.com/beacon-2/web/javascript-api/).
Nowadays, the `identify` method is used to add data to the HelpScout
users and fields need to be created in advance in the HelpScout web
interface. `session-data` is used to add information to the ticket which
is what we are trying to achieve. Only `name` and `email` should still
be passed to `identify` to make it possible to match the correct
HelpScout user.

[MAILPOET-4525]
2022-08-18 15:43:12 +02:00
cfc98c7aac Add sender validation when component did mount
[MAILPOET-4548]
2022-08-18 14:36:36 +02:00
e3f13dd986 Add tracking for MailPoet logo in free plan emails
MAILPOET-4543
2022-08-18 12:29:29 +02:00
79e11fe939 Move MP API addSubscriber tests under Subscribers class tests
[MAILPOET-4293]
2022-08-18 12:08:30 +02:00
2356c62be3 Refactor MP API addSubscriber to doctrine
[MAILPOET-4293]
2022-08-18 12:08:30 +02:00
07fa471ac3 Add tutorial button to newsletters header
[MAILPOET-4498]
2022-08-18 08:52:59 +02:00
5d63e9a462 Refactor email tutorial component to TS
[MAILPOET-4498]
2022-08-18 08:52:59 +02:00
f060219da3 Prefix email editor variables with mailpoet
[MAILPOET-4498]
2022-08-18 08:52:59 +02:00
5a3dc190d6 Use mailpoet-top-bar class in newsletter editor header
[MAILPOET-4498]
2022-08-18 08:52:59 +02:00
1a5952a876 Mark form editor tutorial as seen on init
[MAILPOET-4498]
2022-08-18 08:52:59 +02:00
8ca1a0a9b3 Prettier cleanup
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
6dbc50eb23 Make Step subtitle bold
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
1e1153bd77 Fix codestyle issue
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
24849afb7a Make name a property of Step.args instead of Step
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
2a09646e8d Use TextInput help and label for step name and adjust width and position of side name dropdown
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
70081a846d Add aria-label to edit step button
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
1c138a7d4b Fix type 'it’s' to 'its'
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
33fb02e2ce Add missing spaces and remove double spaces around "null,"
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
ae08a9e18e Return 'Trigger' as title for triggers
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
e2e07bba42 Mark workflow as not saved when name changes
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
6b238618bf Add UI to alter step name of send mail step
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
e37e26e380 Add store action to update step name
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
b5690f3d6c Render step name in workflow editor
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
4a19e4db46 Share Step name with editor application
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
d4bc448ac6 Add name property to Step
[MAILPOET-4445]
2022-08-17 16:01:45 +02:00
1bf751978c Make a parameter in private method in MP API non-optional
[MAILPOET-4291]
2022-08-17 13:20:39 +02:00
5d3c851d02 Change order of MP API subscribe/unsubscribe calls checks to be backward compatible
In the previous version we checked the lists parameter before the subscriber.
[MAILPOET-4291]
2022-08-17 13:20:39 +02:00
877fd9d7da Move MP API unsubscribe from list test to a separate file
[MAILPOET-4291]
2022-08-17 13:20:39 +02:00
c3251f4092 Refactor MP API unsubscribe from lists to doctrine
[MAILPOET-4291]
2022-08-17 13:20:39 +02:00
fbaf37ed0c Refactor getting subscriber and segments into a private methods
[MAILPOET-4291]
2022-08-17 13:20:39 +02:00
2ecf5fc2fa Simplify code by using intval() instead of if
[MAILPOET-4357]
2022-08-16 15:03:52 +02:00
6127336eab Simplify code by removing some duplication
[MAILPOET-4357]
2022-08-16 15:03:52 +02:00
d8770675a4 Replace Paris models with Doctrine entities in BatchIterator
[MAILPOET-4357]
2022-08-16 15:03:52 +02:00
ea1bbf7b70 Replace Paris models with Doctrine entities in BatchIteratorTest
[MAILPOET-4357]
2022-08-16 15:03:52 +02:00
2982fa3277 Release 3.95.0 2022-08-16 13:59:48 +03:00
1093d089fc Simplify webpack build output
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
7b12e00ae4 Add "noEmit" to tsconfig so editors know we use it only for type checking
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
13a79d0b77 Turn off no-void eslint rule for premium (same as the free config)
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
16217db6cd Use premium upgrade modal in Google Analytics panel
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
2a8dca6454 Add basic premium upgrade modal component
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
c8ac0ac4a7 Expose slugify for premium
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
b877bd4577 Expose whole @wordpress/data for premium
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
59315d03c7 Expose text control from @wordpress/components for premium
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
a01afc27e8 Add entrypoint for premium plugin exposed assets and libs
This simplifies Webpack configuration, removes duplicities between loaders and
the "admin_vendor" entrypoint definition, and makes the sharing more transparent.

[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
3886dd83d1 Unify naming of sender name and email address
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
72aa540b23 Simplify and unify default GA campaign name generation
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
697e7eba9a Add Google Analytics setting panel to automation email sidbar
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
922beb790f Add reply-to setting panel to automation email sidebar
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
a5b737ac86 Allow using setState-like callback for setting step args
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
f89b645ce2 Extract automation email panel to a component
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
cf1982a410 Make automation email edit component a directory
[MAILPOET-4446]
2022-08-15 16:05:12 +02:00
8d29c2df49 Deprecate MappingToExternalEntities model and stop creating its table
Since we are removing all the MP2 migration code the table
wp_mailpoet_mapping_to_external_entities is not needed anymore. It was
used to map MP2 objects to MP3 objects. This commit removes the code
that creates this table for new installs and deprecates its model.

[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
d874375c25 Remove MP2 migration main class and its test class
This commit removes the main MP2 migration class and its test class. It
also removes two SQL files that were used in the test class. One of
those files executed the following query:

`SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"`

Removing this query made the tests inside SubscriberSegmentTest fail
when running the whole integration test suite. To work around this, I
added the above mentioned query to SubscriberSegmentTest. I'm not sure
why this test class fails without this query when running all the tests
but it doesn't when it is executed alone. Probably there is another test
class changing the SQL_MODE. Since SubscriberSegmentTest is a test class
for a Paris model that we will eventually remove, I decided it was not
necessary to investigate this further.

[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
efab3be9ae Remove logic to handle MP2 subscribers when deactivating subscribers
This commit removes the logic that was added in #2045 to handle
subscribers migrated from MP2 when deactivating subscribers. Without it,
MP3 would deactive all subscribers imported from MP2 as the subscribe
date is migrated but the stats are not (see
https://mailpoet.atlassian.net/browse/MAILPOET-2040) for more details.

This code is not necessary anymore as we are removing all the MP2 migration
related code.

[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
98e056bec7 Remove code that checks if the MP2 migration is in progress
[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
9a5fbfbb24 Remove MP2 migration endpoints from the JSON API
[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
d828754f94 Remove MP2 migration admin page
[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
fdbc6ea603 Remove MP2 migration code from the welcome wizard
[MAILPOET-4376]
2022-08-15 12:46:22 +02:00
90ff2db0d1 Fix errors caught by translations code sniff
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
968cbb67d8 Refactor response caching in AuthorizedSenderDomainController
In the previous implementation the getDomainRecords couldn't be used without
calling getAllSenderDomains or getVerifiedSenderDomains upfront.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
e03e375942 Fix variable names and comments typos
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
d625457174 Add logging of unexpected formats from sender domain endpoints on Bridge API
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
574c1cdd8f Reuse mocked api data in Bridre test case
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
c58c613959 Use WordPress translation functions directly
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
f4346c8957 Add links to sender domains API docs
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
543465d9de Fix indentation in Bridge
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
1642a5477c Use constants and add missing test case in DmarcPolicyCheckerTest
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
e36f105387 Add basic acceptance tests for sender domain verification
These tests test that basic checks works in both settings and send pages.
We can't verify the test domain for acceptance test docker so the tests just check the error state.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
6411182705 Hide sender domains error when editing sender address
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
1ede10fce6 Update list of popular free ESP domains
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
baea06abf5 Unify loaders size in sender domain/email tabs and render in column
This makes sure that the modal has some reasonable initial size (small column).
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
39b431b7b3 Display correct tab in sender domain/email configuration modal based on clicked link
Previously when both errors were present authorized email tab was always opened no matter what error was clicked.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
866c22c336 Use common/button component fot copy button
The proprietary button had displaying issues. This commit add props
that enable connection with a tooltip into the common button and replaces
the proprietary button with the common component.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
de2b0c34b6 Remove unused max_width property from manage_sender_domain
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
838903bc41 Removing protypes from Typescript component
We use only typed props in components written in TS.
[MAILPOER-4302]
2022-08-11 12:36:23 +02:00
aee63444fe Skip DMARC address check when email domain is freemail
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
d389595d72 Prevent triggering domain and authorized email checks when MSS is not active
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
0fe260f605 Refactor sender address errors rendering on send page
Both auht. email error and DMARC check warning were rendered via parsley error.
There was not enough space for both messages to display and also both were rendered
as errors that block sending. But so far we allow sending when the DMARC warning is present.

This commit changes displaying of both error messages so that they are rendered in
a custom component and use the parsley only to mark the input red.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
67b6d2279f Fetch authorized domains and emails for JS Apps only when MSS is active
This prevents calling bridge API in cases when user use different sending methods.
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
af203ba29e Refactor AuthorizedSenderDomainControllerTest to avoid live api calls
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
d0b1ff3aef Refactor sender domain related bridge integration tests to use mocks
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
fd1ac46207 Fix typo in isDomainDmarcRestricted method name
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
4ffdd5bed5 Add declare(strict_types = 1); to newly added PHP files
[MAILPOET-4302]
2022-08-11 12:36:23 +02:00
ccb070c500 Update information provided for tracking
MAILPOET-4302
2022-08-11 12:36:23 +02:00
fcd12b41b5 Update DMARC Status check
Handle edge case for domains where DMARC `p` policy is set to  reject or quarantine but `sp` (subdomain policy) is set to none

The previous implementation will return dmarcStatus === none when
sp is none but will not check for p policy as well.

If the `sp` is reject or quarantine, it would supersede the `p` status

MAILPOET-4302
2022-08-11 12:36:23 +02:00
449fde75a7 Add tracking for authorized email and sender domain
MAILPOET-4302
2022-08-11 12:36:23 +02:00
bbd27a5b3e Update ManageSenderDomain component UI
Add DomainKeyComponent to assist with rendering the input fields
and DomainStatusComponent for rendering the DNS records status

MAILPOET-4302
2022-08-11 12:36:23 +02:00
f09616dc46 General refactor
Use a single click handler for css lookup

Set to mailpoet-js-button-authorize-email-and-sender-domain

MAILPOET-4302
2022-08-11 12:36:23 +02:00
55f10a403c Show sender domain verification modal on newsletter
sending page

MAILPOET-4302
2022-08-11 12:36:23 +02:00
bd5ea1041d Use unidirectional data store, remove unnecessary useState
MAILPOET-4302
2022-08-11 12:36:23 +02:00
25013e8c8c Add AuthorizeSenderEmailAndDomainModal component
This component wraps the AuthorizeSenderEmailModal  and
AuthorizeSenderDomainModal and displays them in a Tabbed
manner

This gives the user the ability to either authorize an email or verify
a domain

MAILPOET-4302
2022-08-11 12:36:23 +02:00
fcfcbcc4d7 Update SenderDomainModal and AuthorizeSenderEmailModal
Provide the ability to display content without modal

I included the `useModal` option for backward compatibility and
to not break any other components

MAILPOET-4302
2022-08-11 12:36:23 +02:00
f9588484a6 Feat: Add classname to tabs
This would give us the ability to selectively display or hide the
tab component with CSS classes without causing issues
with the JavaScript code

MAILPOET-4302
2022-08-11 12:36:23 +02:00
1a0796c627 Add clean up action after domain is verified
MAILPOET-4302
2022-08-11 12:36:23 +02:00
847d199352 Implement the Manage Sender Domain component and
The AuthorizeSenderDomainModal

MAILPOET-4302
2022-08-11 12:36:23 +02:00
5c3a9fe9b2 Add API methods for creating and verifying sender domains
We also included an API method for fetching Sender Domains

MAILPOET-4302
2022-08-11 12:36:23 +02:00
aba68d2305 Add mailpoet_all_sender_domains to Newsletter and settings
MAILPOET-4302
2022-08-11 12:36:23 +02:00
9e5f1d0ff0 Implement Show sender domain warning for settings
This would show the sender domain warning message for
domains that are not verified and have a Retricted DMARC Policy

MAILPOET-4302
2022-08-11 12:36:23 +02:00
370de8050a Add mailpoet_verified_sender_domains to newsletters and settings page
Update tests

MAILPOET-4302
2022-08-11 12:36:23 +02:00
c961e0a363 Add AuthorizedSenderDomainController class and tests
MAILPOET-4302
2022-08-11 12:36:23 +02:00
601b3e775b Add DMARC Policy Checker class and
getDomainDmarcPolicy method

MAILPOET-4302
2022-08-11 12:36:23 +02:00
417c0897e8 Add API methods for fetching, creating and verifying
sender domain

MAILPOET-4302
2022-08-11 12:36:23 +02:00
02af3d6c2e Update sed command for fixing code sniffer to be Mac OS compatible
[MAILPOET-4560]
2022-08-11 12:30:33 +02:00
42dce4280a Use correct error code and translator comment
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
0d1b51c7ba Query a workflow in a specific version in the StepHandler
The StepHandler must run a specific version of a workflow. Therefore, we need to
query this specific Workflow.

[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
9f2ae81835 Remove outdated $change variable
The usage of $change was a leftover from a rebase which was
necessary.

[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
293c1f4a46 Fix use statements in test
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
ee54440c03 Fix intendation for SQL strings
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
ea07bbe9fa Use INNER JOIN to combine workflows with their versions
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
f7722e4349 Rename workflowHeaderData method
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
7a0a6413d5 Update updatedAt when values are changed
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
457d361ee9 Introduce Workflow::equals() and move decision to update a record to storage
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
3885194b48 Add created and updated columns to versions table
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
7f78c387cf Test WorkflowStorage
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
7e9149f2aa Query workflow by version in StepRunner
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
02489a61a6 Add version_id to WorkflowRun
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
456417a467 Add version_id to Workflows
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
964f3ca69d Separate workflow versionable data into workflow_version table
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
c7f10a18c4 Update database entry only when an actual change happend
[MAILPOET-4430]
2022-08-10 16:57:25 +02:00
8432cfa904 Fix issue with recursion ending up breaking watch:js command
[MAILPOET-4522]
2022-08-09 17:11:53 +02:00
50d77741b7 Break compilation when compile:js hits a warning
[MAILPOET-4507]
2022-08-09 17:11:53 +02:00
4814e0a8e8 Prioritize main over module for @woocommerce/components
Webpack was prioritizing module over main for
@woocommerce/components which was problematic

[MAILPOET-4507]
2022-08-09 17:11:53 +02:00
d102556d28 Disable layout configuration
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
839265663e Unify color panel design
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
5148e2dca7 Fix sidebar's family-font select
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
5214a25a6f Remove old color component usage
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
08f62f4590 Use newer Gutenberg component for settings colors
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
6791375807 Remove ColorSettings component
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
9bbdc160ea Fix family-font select design in popover
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
c37173a30e Fix compatibility to allow compilation
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
2ae02db229 Update @types/wordpress__* packages
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
641a1cda47 Update @wordpress packages
[MAILPOET-4301]
2022-08-09 17:11:53 +02:00
7fde0b6bdc Remove unnecessary usage of WPFunctions from SubscribersListingRepository
We agreed that we can use WP translation functions directly.
This one was probably some leftover which remained in the file after merging branches.
The file was missing use statement so this was causing an error.
[MAILPOET-4551]
2022-08-09 14:39:31 +02:00
54a721b751 Stub for _x() function
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
402cf63e6b Make I18N sniff compatbile with PHP8.1+
This is a hacky workaround to enable us to use the translation sniffs in a PHP 8 environment. We can get rid of
it, once the coding standards are compatible.

[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
45b344727c Allow 'mailpoet-premium' text-domain
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
41a99f15f6 Allow for 'woocommerce' text-domain
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
97e160bf67 Fix WordPress.WP.I18n.MissingArgDomain
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
76048224d1 Fix WordPress.WP.I18n.NonSingularStringLiteralText errors
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
218de96024 Fix WordPress.WP.I18n.MissingTranslatorsComment errors
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
b05e6d414c Remove WP\Functions::__ and other translate functions
Under the new sniffer rules, those functions produce errors and, when those methods
are used, the sniffer can not properly be applied.

[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
0b2b211b08 Add WP.I18n sniffs to our ruleset
[MAILPOET-4524]
2022-08-09 13:23:16 +02:00
5d1f49b1bd Fix CSS code style
[MAILPOET-4515]
2022-08-09 13:20:53 +02:00
6532cc1bec Fix bug when displaying email subjects in the system status page
The commit f44afbddf2 introduced a bug
when replacing Paris code with Doctrine code in lib/Tasks/State.php.
This bug was causing the system status page to display "Preview" instead
of the subject of the listed emails. The problem was happening because
during the refactor there was a mistake and we were trying to get a
SendingQueueEntity via its ID using the ScheduledTaskEntity ID. This
commit fixes this problem by getting a SendingQueueEntity that matches
the given ScheduledTaskEntity ID.

[MAILPOET-4508]
2022-08-09 13:19:47 +02:00
3a383fdd82 Fix var name in fix-guzzle.php 2022-08-09 14:13:18 +03:00
7c71255231 Release 3.94.0 2022-08-09 14:13:18 +03:00
a1ef5f9568 Fix type hint in fix-guzzle.php
[PREMIUM-199]
2022-08-08 17:28:05 +03:00
e7534132d3 Fix update button in Safari
[MAILPOET-4479]
2022-08-08 13:34:34 +02:00
d1fddb85f7 Add before step save hook per step key to simplify usage
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
24a63f6448 Call step before save hook more safely and explicitly
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
08e8e88502 Use step defaults from schema in template generation
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
1f6ce667e5 Add schema definition for automation steps
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
0dbfb196e2 Redirect back to automation editor after editing automation email
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
4b5afeac54 Save workflow step ID to automation email options
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
bb24838881 Save workflow ID to automation email options
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
0d2c82ad72 Fix row key computation in automation listing
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
72b392fcd5 Do not hardcode email IDs in workflow templates
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
8319c1e4b6 Implement create functionality for automation email in send email step
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
c02e71c6e6 Add workflow saved status to editor store
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
0792bae953 Fix workflow activation discarding unsaved changes
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
4389d3ca5f Sync automation email step settings to newsletter entity
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
15e4635fe3 Add hook registration helper methods to registry
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
da58a5c416 Trigger workflow and step before save hooks
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
87f35abf8b Add workflow and step before save hooks
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
a6cbf99ee5 Fix wrong ...$args typehints
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
2faf0739a3 Check that step implementation exists when saving workflow
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
cc2f908236 Add registry methods for step interface implementations
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
ebe8d5d478 Add step interface as a parent of action and trigger
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
4aa323b612 Extract data-carrying classes to dedicated namespace
We need a separation of "Step" as an interface vs. "Step" as a serializable data structure.

[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
fbd27afedf Move step runner interface to the control namespace
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
ca308321fd Rename step runner to handler (free the name to step runner interface)
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
c9aa53df8b Extract workflow step updating to a separate controller
[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
fc11b2f97e Fix automation editor body class
We use edit-site styles now so we need to set the correct class for the footer to disappear.

[MAILPOET-4515]
2022-08-08 13:23:57 +02:00
18501385dc Extend integration test for filters
[MAILPOET-4442]
2022-08-08 10:57:42 +02:00
c917505170 Add acceptance test with filtering by tag
[MAILPOET-4442]
2022-08-08 10:57:42 +02:00
0b14a03236 Disable margin left for second select in a row
[MAILPOET-4442]
2022-08-08 10:57:42 +02:00
2b5d089a79 Add tag filter into subscribers response
[MAILPOET-4442]
2022-08-08 10:57:42 +02:00
5445bf9305 Add tags statistics method
[MAILPOET-4442]
2022-08-08 10:57:42 +02:00
98d9952a44 Fix prefetching subscriber relations
[MAILPOET-4501]
2022-08-08 10:57:42 +02:00
f899eb1c16 Parse name from an email
When the settings table is empty and the current user uses an email in the name. We try to parse the name.
[MAILPOET-4501]
2022-08-08 10:42:25 +02:00
e93befc965 Rename popup message over list/segment badges to 'View subscribers'
[MAILPOET-4505]
2022-08-08 09:04:34 +02:00
c8278dde2a Use correct exception type when saving segment to DB fails
[MAILPOET-4294]
2022-08-04 13:31:53 +02:00
1d4ec47b25 Move tests for addList under the related service test
[MAILPOET-4294]
2022-08-04 13:31:53 +02:00
9c5dca0206 Refactor MailPoet\API\MP\v1\API::addList to doctrine
[MAILPOET-4294]
2022-08-04 13:31:53 +02:00
73f51523f3 Replace Paris models with Doctrine entities in SubscriberLinkTokens
[MAILPOET-4345]
2022-08-04 11:52:57 +02:00
d3abbddf73 Add a basic integration test to cover the happy path of SubscriberLinkTokens
[MAILPOET-4345]
2022-08-04 11:52:57 +02:00
56b90247b2 Add method to set the link_token to the Subscriber factory
[MAILPOET-4345]
2022-08-04 11:52:57 +02:00
4a8ac483f6 Use getServiceWithOverrides() instead of instantiating the tested class
This commit changes the way InactiveSubscribersTest
instantiates the tested class InactiveSubscribers. Instead of calling it
directly, it uses \MailPoetTest::getServiceWithOverrides(). This way, we
don't have to update all the tests when there is a change to the
signature of the constructor of the tested class.

[MAILPOET-4344]
2022-08-04 11:10:50 +02:00
af396021db Move the query to calculate the max subscriber ID to SubscribersRepository
[MAILPOET-4344]
2022-08-04 11:10:50 +02:00
0f7a3c3e00 Replace Paris models with Doctrine entities in SimpleWorkerTest
[MAILPOET-4344]
2022-08-04 11:10:50 +02:00
8a29519b31 Replace Paris models with Doctrine entities in InactiveSubscribers
[MAILPOET-4344]
2022-08-04 11:10:50 +02:00
261f2eaa25 Replace Paris models with Doctrine entities in Shortcodes
[MAILPOET-4364]
2022-08-04 10:55:03 +02:00
4a0ee78467 Replace Paris models with Doctrine entities in ShortcodesTest
[MAILPOET-4364]
2022-08-04 10:55:03 +02:00
de4e76b65d Use is_array() instead of !empty() for a safer check
Just in case the option is changed and is not an array anymore.

[MAILPOET-4491]
2022-08-04 10:18:44 +02:00
ffecdf8528 Use update_option() instead of add_option()
See https://github.com/mailpoet/mailpoet/pull/4277#discussion_r933531137

[MAILPOET-4491]
2022-08-04 10:18:44 +02:00
ef89cae94a Include in the Beacon info about if MP was installed via WC or not
[MAILPOET-4491]
2022-08-04 10:18:44 +02:00
6a5207a366 Add new property to pass to Mixpanel to track if MP was installed via WC
[MAILPOET-4491]
2022-08-04 10:18:44 +02:00
13e06fe0cb Add helper method to check if MailPoet was installed via WooCommerce
The method will return true if MailPoet was installed via the
WooCommerce onboarding wizard and false otherwise.

[MAILPOET-4491]
2022-08-04 10:18:44 +02:00
57c02a2fd3 Inline form honey pot css style
Inline !important styling has more specificity that styling
added inside style tag, and is more likely to work on more
devices

[MAILPOET-4519]
2022-08-04 10:10:16 +02:00
ac473c412e Use WP functions from internal class
[MAILPOET-4516]
2022-08-03 16:52:02 +02:00
679d586d2b Fix settings action link order
[MAILPOET-4516]
2022-08-03 16:52:02 +02:00
85e7997197 Replace Paris models with Doctrine entities in AuthorizedEmailsController
[MAILPOET-4347]
2022-08-03 16:27:41 +02:00
d225f4e044 Replace Paris models with Doctrine entities in AuthorizedEmailsControllerTest
[MAILPOET-4347]
2022-08-03 16:27:41 +02:00
e7b32bdd4a Add method to set the status when creating a new Newsletter entity
This commit adds the method withStatus() to the newsletter factory
class so that it is possible to pass a variable with the status to the
factory. Before this change, it was necessary to class methods like
withActiveStatus() which make it impossible to create newsletters with
different status in the same call. This will be used in a subsequent
commit that is part of the same ticket to refactor
AuthorizedEmailsControllerTest.

[MAILPOET-4347]
2022-08-03 16:27:41 +02:00
b89a931e1a Improve CSS reset for fieldset and legend in form
These changes address issues found when testing with Storefront theme.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
c170641d83 Fix form checkbox html
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
35d9a6b2d3 Update form acceptance test check
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
6777a9b560 Update unit tests to reflect form rendering changes
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
bf5d27cc79 Unify error message for invalid email with other messages
Other error messages are also ended with period.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
7f525068b3 Improve legend tag rendering in forms
The legend html was not escaped and was ignoring block settings coming
from the form editor.
This commit adds a new helper function for rendering legend that respects
applies block settings and also escapes the output.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
243e1bdac8 Change form fieldset CSS reset to also affect manage subscription form
Previously the reset was applied to .mailpoet_form class but manage subscription form
which also uses these form blocks doesn't have the class.
This commit moves the reset to an internal class that wraps every input and is used
also in the manage subscription page.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
6671c573b8 Improve email error message in forms.
Added a 'type-message' for Parsley to display an error message when the input field is filled in incorrectly.
Also removed the global Parsley message to prevent it from overruling the 'required-message'.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
75fce3800c Add styling for the for fieldsets to avoid default.
Add styling for the fieldsets to avoid getting the default styling with the black border and unecessary padding and margin.
Applying default or theme styles would cause a BC break for existing forms.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
be220c4782 Add a fieldset and legend element to the radio buttons.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
b891ca4164 Add a fieldset and legend element to the checkboxes.
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
ba45281324 Add fieldset and legend to list selection
Made checkboxes in a form accessible according to the WCAG-standards by adding a fieldset around them and adding a legend describing the fieldset's purpose
(name).
[MAILPOET-4471]
2022-08-03 11:27:06 +02:00
45e1db5893 Make trigger run less frequent
Checking if there are task to execute every 2 minutes should be enough.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
db254986ee Fix initial trigger scheduling
When scheduling using timestamp the action scheduler uses default UTC timezone
See 0a294e6df6/functions.php (L330-L339)
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
8e9174442c Fix label for attribute in cron settings
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
c44758b90d Refactor action scheduler deactivation into DaemonActionSchedulerRunner::init
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
16807fe9e3 Update and fix comments related to usage of Action Scheduler in cron
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
d742164f21 Allow execution limit type to be float
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
aa030fde9c Simplify triggering background jobs in acceptance tests
After enabling using the action scheduler via WP CLI it its much simpler to use WP CLI then
manipulating WP-Cron schedules.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
869697e025 Allow Action Scheduler Cron trigger in cli environment
We don't want our loopback cron to run in cli environment
but we want to allow the action scheduler so that we can use it via wp cli
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
d6afe1c269 Update default scheduled tasks values for tests with MSS tasks
The default scheduled tasks should be setup so that there is no need to run cron and no background job is triggered in tests.
The previous setup was not covering case when MSS is set as active.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
977f9beb87 Refactor deactivation of MailPoet cron actions to run only once
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
2df7d2f686 Split integration tests after refactoring AS runner
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
d3e2bcdf34 Refactor daemon trigger and daemon run actions to extra classes
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
476ee1ede9 Refactor triggering new action scheduler executor into extra service
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
4f01d54742 Improve Action Scheduler related labels at help page
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
bb1d461c54 Fix code formatting issues in ActionSchedulerRunner class
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
dfae17ed94 Add missing return and parameter types to action scheduler related classes
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
f745975c03 Prevent triggering migrations when tables are being removed
This was causing flakiness of acceptance test ReinstallFromScratchCest.php:reinstallFromScratch
and also potential issue for production.
In Activator::deactivate we remove MailPoet's DB tables. This takes some time.
In case there is another request during the deletion it may detect some tables are missing and trigger activation
and we end up creating MailPoet tables and removing them at the same time.

This commit adds locking to deactivate method so that any other attempt for activation ends up with an error.

[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
2ba1696d53 Add adaptive execution limit when daemon runs inside Action Scheduler
We use action_scheduler_maximum_execution_time_likely_to_be_exceeded filter for reading remaining execution limit
tracked in the Action Scheduler runner.
Then we use the remaining limit minus safety margin for the daemon execution.

[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
91e72ee646 Lower execution limit of daemon in case it runs in action scheduler
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
f178ea5f7a Fix ManageSubscriptionsLinkCest
Clicking on Send button triggers ActionScheduler and it may take longer
then 2 seconds. In such case the test ends up reloading on send page instead of
newsletter listing page.

This commit fixes the issue by adding wait for newsletter listing.
[MAILPOET-4724]
2022-08-03 10:36:57 +02:00
cacb44444c Add DaemonActionSchedulerRunner integration test
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
ddb63bfa9f Setup integration test to run without action scheduler cron
Action Scheduler was running in background causing random test failures.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
b525f9f876 Deactivate action scheduler actions when another method is active
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
138354cfd8 Improve stability of SubscribeOnRegistrationPage acceptance test
This commit adds waiting for confirmation that settings were really saved before continuing with next steps.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
95ed809db0 Improve stability of WooCommerceSetupPageCest acceptance test
This commit adds waiting for listing to finish loading before checking presence of
a customer email. We do it in other tests that work with listing, but it was missing
in this place.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
28f497ec61 Improve ManageSubscribers acceptance test stability
The test was randomly failing when asserting subscriber state within subscriber edit form
and was also failing when attempting to click inactive subscribers filter in subscribers listing.
I found that both these issues were related to the web driver clicking too early when the form or
the list was still being rendered by React.

In this commit, I fixed those by adding some waiting on some elements and making sure the subscriber edit form is
already rendered.

The failures were flaky, and I could not identify what exactly caused these tests to start failing.
It is possible that some changes on the backend slightly affected API requests duration and the change in timing of rendering
was big enough to cause the flakiness.

[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
272e875643 Deactivate action scheduler recurring actions on plugin deactivation
We need to cleanup these recurring actions otherwise they would be rescheduled indefinitely.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
e3fbf2421d Move action scheduler initialization to Initializer as one of first actions
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
791a0631b5 Add Acceptance test helper for triggering background job
It may take up to one minute for Action Scheduler to start.
This commit adds a helper method that sets WP Cron trigger for action scheduler and
MailPoet action in Action Scheduler to run immediately and triggers WP Cron
that triggers action scheduler by page reload.

[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
2c2c9dba6a Make sure WP Cron is not locked when acceptance tests start
Action Scheduler uses WP Cron as trigger.
When it is initially locked it takes more time for Action Scheduler to start
and some tests that rely on background job fail with timeout.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
eedc18bb7f Trigger action scheduler runner immediately when starting sending
When users click send email in UI, they need to wait a minute or more to
see some emails started going out.

This commit adds additional trigger into sending queue API.
If emails is set to send it triggers Action Scheduler immediately.
Users will get immediate feedback that emails are sending.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
5deea8a724 Fix CronWorkerRunner integration test
CronWorkerRunner sets internal timer in its constructor function.
This works fine in classic web page request. But in integration tests
in might be created and cached in DI, so for some late running tests
it throws timeout exception.
This commit fixes the issue by using fresh instance of CronWorkerRunner
instead of "cached" one from DI container.

[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
b979c76445 Switch acceptance tests to use Action Scheduler instead of Wordpress visitors
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
b02aa1e1a6 Add Action scheduler status into on help page
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
86562e99b9 Trigger additional executor only when MailPoet daemon runs
In afterProcess callback we trigger a new runner when there are still some scheduled tasks
to process. Before this change we did this for every action scheduler run and it was causing that
we triggered more HTTP requests than needed.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
f59945d483 Add wrapper class for action scheduler in cron
The class add GROUP ID so that all MailPoet cron tasks are stored under the same group.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
7e5d632148 Make Action Scheduler the default method for MailPoet background job
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
8d05da2c37 Add Action Scheduler as WordPress cron into advanced settings
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
2b44684c38 Add action scheduler method to cron trigger
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
ccfdd229ec Add action scheduler daemon runner
This commit adds a class that handles continuous processing of MailPoet cron daemon
using Action Scheduler's recurring actions.
[MAILPOET-4274]
2022-08-03 10:36:57 +02:00
0ac617abf7 Check guzzlehttp/guzzle version before attempting to patch
[PREMIUM-199]
2022-08-02 15:01:42 +02:00
b2a5f47da7 Override http_build_query for in guzzle package
The http_build_query used in guzzle/src/Client.php
is not compatible with PHPv8.0+ since it accepts null as
second parameter while in newer versions of PHP this is
either deprecated or removed

[PREMIUM-199]
2022-08-02 15:01:42 +02:00
c9a464c4c9 Make 'HOURS' the default when creating a delay step
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
ace82c9abb Change delayType signature.
Adds subtitle callback and removes singular and plural properties.

[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
c6164ad88f Center icon in ColoredIcon vertically
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
78460bd248 Use ComponentType instead of JSX.Element for icon, use @wordpress/component icons
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
39ca2343b8 Simplify the DOM and CSS of ColorIcon component
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
1fbf29b7f6 Resize ColoredIcon
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
7fe4104af8 Use email icon without "padding"
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
3e0b5b0f76 Change delay icon
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
7f6159501a Extend ColorIcon to enable a distinct background color
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
81b1c70a28 Use mailpoet-automation prefix in class names
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
9a7e99602a Make type of delay field numeric
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
7f428c0c7a Change variable name from type to key
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
ec58c24228 Allow for JSX.Element in subtitle
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
e8d3edcc23 Remove unused icons
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
03bf653432 Enable a colored icon
This commit introduces a <ColoredIcon /> component with which you can define the
color and background of the circle behind the colored icon.

The SVG's have been adjusted and the StepType has now a color attribute, with
which the color can be defined.

[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
83c7f7bc7f Update configuration arguments and invalidate 2 years of waiting
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
a9a35333c4 Add delay step settings
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
a7bda3f465 Make step subtitle dynamic
This commit enables us to render the subtitle of a step
dynamically depending on a given step configuration. This enables us to render
a subtitle like 'Wait for 4 days'.

[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
e0f6751538 Introduce getStepType selector
[MAILPOET-4418]
2022-08-02 14:39:33 +02:00
db112dc03f Release 3.93.1 2022-08-02 14:35:08 +02:00
e706110230 Fix new subscriber notifications
[MAILPOET-4518]
2022-08-02 12:57:05 +02:00
b2718940df Remove models from SegmentsExporterTest
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
3ef32787cc Remove models from NewslettersExporterTest
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
5b0e7128c6 Remove usages of StatisticsNewsletters in newsletter exporter
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
31aa18868b Remove traces of older models in PersonalDataExports namespace
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
10d4eb5d7f Remove old models from SegmentsExporter
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
6b12255bb8 Move PersonalDataExporters to DI
[MAILPOET-4348]
2022-08-01 14:18:59 +02:00
84a6401590 Remove email signup step from Welcome Wizard
[MAILPOET-4497]
2022-08-01 13:44:44 +02:00
381565fc1f Improve integration ManageTest class
[MAILPOET-4350]
2022-08-01 13:28:57 +02:00
deaf293273 Remove redundant code removing relation
Removing inconsistent relations is solved in the class SubscriberEntity by the preFlush
[MAILPOET-4350]
2022-08-01 13:28:57 +02:00
fb05cc4297 Remove old model from Manage
[MAILPOET-4350]
2022-08-01 13:28:57 +02:00
155445b313 Remove old model from SendingQueue Migration
[MAILPOET-4358]
2022-08-01 13:09:20 +02:00
ce319e69c6 Remove old model from MigrationTest
[MAILPOET-4358]
2022-08-01 13:09:20 +02:00
c30fe1305b Replace Paris models with Doctrine entities in Shortcodes
It was necessary to deprecate the filter
mailpoet_archive_email_subject and create a new one since the type
of one of the parameters passed to the filter had to change.

[MAILPOET-4307]
2022-08-01 09:57:10 +02:00
351c4ca565 Replace Paris models with Doctrine entities in ShortcodesTest
[MAILPOET-4307]
2022-08-01 09:57:10 +02:00
6896758f13 Add a link to the MailPoet settings page in the page that lists plugins
[MAILPOET-4412]
2022-07-29 14:52:50 +02:00
c4cd053b34 Add inline class to MailPoet notices
The `inline` class is used to prevent moving notice by WP JavaScript (wp-admin/js/common.js) because it can break DOM, and events don't work anymore
[MAILPOET-4510]
2022-07-29 14:26:50 +02:00
8d4799b050 Fix models check conditions in sending queue
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
da4d72be50 Disable buttons on newsletter send page when newsletter is loading
The buttons were clickable even when newsletter data were not loaded.
This was causing acceptance test and potentially also production errors.
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
5042cdd15e Refactor fetching newsletter options in SendingQueue using doctrine
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
3b5d737143 Use WP ClI for setting wp-config.php constants
We experienced issues with replacing constants in wp-config.php using regular expressions.
E.g. it may stop working when new WP release changes default contents of the wp-config.php
Using WP CLI seems to be more reliable way of doing it.
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
1e1ef12d42 Remove no longer necessary WP CLI fix from acceptance tests
This seems to be already fixed in https://core.trac.wordpress.org/ticket/25239
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
09160a58ff Replace deprecated NewsletterOptionField::findArray with doctrine code
This commit is a quick fix for deprecation warnings which we get after we deprecated NewsletterOptions
and NewsletterOptionFields models.
We will soon stop using the old Newsletter model completely.
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
3d4155f6cc Fix setting wp-config.php for acceptance tests
The wp-config properties were not set correctly because there reg exp
that was used in sed was no longer matching the default value.
This was causing e.g. that errors and warnings were not caught by the acceptnace tests.
[MAILPOET-4511]
2022-07-29 10:33:05 +02:00
e53505354a Switch to SVG menu icon for Calypso compatibility
[MAILPOET-4472]
2022-07-27 09:43:42 +02:00
1396a979f5 Fix PHPMailer on WP 5.6 in acceptance tests
[MAILPOET-4481]

Use SMTP in PHPMailer on WP 5.6 because its Docker container
has a misconfigured mail() function (no sendmail or alternative)
2022-07-27 08:50:03 +02:00
6525f3a55c Remove integration tests for deprecated models
This commit removes the integration tests for the models
NewsletterOptionField and NewsletterOption as they have been deprecated.

[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
f27326b590 Deprecate models NewsletterOption and NewsletterOptionField
Those models are being deprecated as they are not used anymore in our
codebase.

[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
5985a10c8f Deprecate some unused Models\Newsletter methods
This commit deprecates the following Models\Newsletter methods:

- \MailPoet\Models\Newsletter::filterType()
- \MailPoet\Models\Newsletter::getWelcomeNotificationsForSegments()
- \MailPoet\Models\Newsletter::duplicate()

I'm doing this as part of a ticket to replace the models
NewsletterOption and NewsletterOptionField with Doctrine code. The
methods deprecated here use those models. But since they are not used
anymore in our codebase, I figured it would be easier to deprecate them
instead of replacing the models.

[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
3684530056 Replace NewsletterOption and NewsletterOptionField models
This commit repleaces the old NewsletterOption and NewsletterOptionField
models with Doctrine code in several test classes.

[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
81051e8295 Replace all Paris models with Doctrine code in SchedulerTest
[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
22010874b7 Add method to the Newsletter datafactory to set the type to automatic
[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
7d2fa9ff16 Use Doctrine entities and truncate only what is used in SubscriberTest
This commit replaces old Paris code with Doctrine entities in
SubscriberTest::_after() and it also truncate only the entities that are
actually used inside the test class. Before this change, the class was
unnecessarily truncating entities that were not used. I'm doing this
commit as part of task to remove all usages of the old NewsletterOption
model and that is why I'm not touching the remaining models in other
methods of this class.

[MAILPOET-4150]
2022-07-27 08:48:44 +02:00
ae13fcc771 Make sure $metas[] is always set for all subscribers in the foreach loop
See https://github.com/mailpoet/mailpoet/pull/4247#pullrequestreview-1050167548

[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
932512b720 Replace Paris models with Doctrine entities in MetaInfoTest
In the process it was necessary to change the signature of the method
\MailPoet\Mailer\MetaInfo::getNewsletterMetaInfo() to accept a
SubscriberEntity instead of a Subscriber model and also change
\MailPoet\Cron\Workers\SendingQueue\SendingQueue::processQueue() as it
calls getNewsletterMetaInfo().

Doing this was straightforward, but changing the test class for
SendingQueue was a bit more involved. In particular, the test
SendingQueueTest::testItEnforcesExecutionLimitsAfterQueueProcessing().

This test is a bit brittle and was creating Subscriber models without
saving them to the database with invalid statuses and sources. Switching
getNewsletterMetaInfo() to use entities, while
SendingQueue::processQueue() still uses models, meant that it was
necessary to save the subscribers to the database. Because of this, it
was not possible anymore to use invalid statuses and sources, and
thus, the test changed a bit.

[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
f6fbcee0d0 Remove integration test for when subscriber source is null
This test is being removed as the field source of the table
wp_mailpoet_subscribers does not accept a null value. When changing the
test class MetaInfoTest from Paris to Doctrine, there is no easy way to
set subscriber source to null and since it is not possible to store this
value in the database anyway, it seems to me this test is not necessary.

I'm opting to keep the code in MetaInfo that sets `subscriber_source` to
`unknown` if the subscriber source is null
(9f790efbf0/mailpoet/lib/Mailer/MetaInfo.php (L64))
just in case this is still possible in some old databases (which I don't
think is the case).

[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
15de269da0 Replace Paris models with Doctrine entities in LinksTest
[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
2f6dc17228 Replace Paris models with Doctrine entities ShortcodesTest
[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
831fb46c48 Replace Newsletter model with NewsletterEntity in MetaInfoTest
[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
026aa684a5 Replace Paris models with Doctrine entities in WorkerTest
[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
5f0f28a5c2 Remove unused import
[MAILPOET-4379]
2022-07-27 08:48:15 +02:00
ef477bc156 Remove SendingQueue Paris model from ClicksTest
It was not necessary to replace this model, as the tests still passed
without the code that used SendingQueue.

[MAILPOET-4380]
2022-07-26 16:16:47 +02:00
955731eeb5 Replace Paris models with Doctrine entities in FormTest
[MAILPOET-4380]
2022-07-26 16:16:47 +02:00
c141c9ea15 Replace Paris models with Doctrine entities in UnsubscribesTest
[MAILPOET-4380]
2022-07-26 16:16:47 +02:00
27e759b525 Share Node.js Corepack cache in a CI workspace
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
f88ec9bb82 Use rolling ranges for internal workspaces
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
4f7b90bba3 Remove no longer used (and needed) overrides
Now pnpm keeps only a single version of all of these packages:

  webpack@5.73.0
  underscore@1.13.4
  react@17.0.2

To verify, use "pnpm list webpack", "pnpm list underscore", and "pnpm list react".

[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
6faa8e30e1 Use pnpm patch for spectrum-colorpicker deprecated jQuery methods
This patch replaces deprecated jQuery methods in the spectrum-colorpicker dependency.
It can be removed when a fix is included in a package update.
See: https://github.com/bgrins/spectrum/pull/564

[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
4c51001558 Use pnpm patch for parsleyjs deprecated jQuery methods
This patch replaces deprecated jQuery methods in parsleyjs. It can be removed this when
a fix is included in the package update.

Note: deferred.pipe() fix is not implemented yet, see https://github.com/guillaumepotier/Parsley.js/pull/1347

[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
7dfb55e103 Use pnpm patch for backbone.supermodel strict mode fixes
This patch fixes strict mode issues in backbone.supermodel build.

[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
06bd80eb85 Use pnpm patch for @woocommerce/components CSS fix
The style.css of @woocommerce/components contains invalid CSS strings like "(1fr)[2]"
that are not parseable by SASS. This patch fixes them to "1fr [2]" format.

[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
67264710ed Make eslint-config a pnpm workspace
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
2e6248b63b Run scripts using pnpm
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
e2a494e98e Add info about dependencies and PNPM to readme
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
7c373a85ba Add root-level .nvmrc
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
052e175b76 Remove no longer used require
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
2357e7493d Explicitly install used packages
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
89010304da Remove unused Babel plugin configuration
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
dae3080f08 Remove all Babel plugins that are included in @babel/preset-env
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
c41ab7464c Make mailpoet a pnpm workspace
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
c83a3b7d8d Use pnpm in the monorepo root
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
ea996b30a7 Add Node.js version configs for Volta JS tool manager
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
5b154ce26e Update Node.js (so we can use Corepack)
[MAILPOET-4485]
2022-07-26 15:45:54 +02:00
30aad243aa Prevent multiline text in subject and preheader textareas
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
ef161311bf Implement save workflow frontend
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
7da2d2f7c8 Add basic workflow step saving to workflow update API
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
55fb3f6c82 Add email thumbail component (only frontend at the moment)
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
c44230b49f Extract button modifications to a component exending @wordpress/components
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
e2db0542ff Do not crash when an unknown step is selected
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
8aab1708c1 Keep selected step highlighted (as post editor does with blocks)
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
d998e48b62 Hide debug info folded in a panel body
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
c484514d88 Save step args into editor store
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
569df2be9c Use registered blocks in block inserter
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
03bc8cee03 Add basic email content sidebar section
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
b313c74901 Add email settings to step sidebar
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
bc502d8046 Add panel body title component with action button
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
47ccae2e6b Add mailpoet integration client setup with send email step
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
0fcef3c209 Add step editation rendering to step sidebar
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
4e9acb524d Add step card component and render it in step sidebar
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
4406d72980 Add functionality to register step types in automation editor
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
664594d1bd Extract store functionality out of index file (avoids dep cycles)
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
db6f0d14b1 Move api setup to automation root, fix initialization side-effect
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
1801f522f6 Add reusable step icon component
[MAILPOET-4420]
2022-07-26 15:10:05 +02:00
e5b0b4ae1d Release 3.93.0 2022-07-26 15:03:08 +02:00
623 changed files with 33947 additions and 92197 deletions

View File

@ -92,7 +92,7 @@ executors:
wpcli_php_latest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:8.1_20220309.1
- image: mailpoet/wordpress:8.1_20220718.1
wpcli_php_mysql_oldest:
<<: *default_job_config
@ -103,7 +103,7 @@ executors:
wpcli_php_mysql_latest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:8.1_20220309.1
- image: mailpoet/wordpress:8.1_20220718.1
- image: cimg/mysql:8.0
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_520_ci
@ -115,6 +115,9 @@ jobs:
steps:
- checkout:
path: /home/circleci/mailpoet
- run:
name: 'Set PNPM store directory'
command: pnpm config set store-dir ~/.pnpm-store
- run:
name: 'Compute checksum for prefixer'
command: find prefixer -type f -not -path 'prefixer/build/*' -not -path 'prefixer/vendor/*' | sort | xargs cat | sha512sum > prefixer-checksum
@ -130,8 +133,8 @@ jobs:
key: composer-prefixed-{{ checksum "prefixer-checksum" }}
- restore_cache:
keys:
- npm-{{ checksum "package-lock.json" }}
- npm- # fallback to most recent npm-* if not found by checksum
- pnpm-{{ checksum "../pnpm-lock.yaml" }}
- pnpm- # fallback to most recent pnpm-* if not found by checksum
- run:
name: 'Set up test environment'
command: |
@ -170,9 +173,9 @@ jobs:
- prefixer/vendor
- vendor-prefixed
- save_cache:
key: npm-{{ checksum "package-lock.json" }}
key: pnpm-{{ checksum "../pnpm-lock.yaml" }}
paths:
- ~/.npm
- ~/.pnpm-store
- run:
name: Download additional WP Plugins for tests
command: |
@ -185,16 +188,17 @@ jobs:
command: |
(printenv | grep WP_TEST_ > .env) || true
- persist_to_workspace:
root: /home/circleci/mailpoet
root: /home/circleci
paths:
- .
- .node
- mailpoet
build_premium:
executor: wpcli_php_latest
resource_class: medium
working_directory: /home/circleci/mailpoet
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- add_ssh_keys
- run:
name: 'Install Premium plugin'
@ -226,9 +230,11 @@ jobs:
paths:
- mailpoet-premium/vendor
- persist_to_workspace:
root: /home/circleci/mailpoet
root: /home/circleci
paths:
- .
- .node
- mailpoet
static_analysis:
executor: wpcli_php_latest
resource_class: medium
@ -239,7 +245,7 @@ jobs:
default: 70200
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Static analysis'
command: ./do qa:phpstan --php-version=<< parameters.php_version >>
@ -248,7 +254,7 @@ jobs:
working_directory: /home/circleci/mailpoet/mailpoet
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'QA Frontend Assets'
command: ./do qa:frontend-assets
@ -257,7 +263,7 @@ jobs:
working_directory: /home/circleci/mailpoet/mailpoet
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'QA PHP'
command: ./do qa:php
@ -266,7 +272,7 @@ jobs:
working_directory: /home/circleci/mailpoet/mailpoet
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'QA PHP'
command: ./do qa:php
@ -274,7 +280,7 @@ jobs:
executor: wpcli_php_max_wporg
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'QA PHP'
command: ./do qa:php-max-wporg
@ -283,7 +289,7 @@ jobs:
working_directory: /home/circleci/mailpoet/mailpoet
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Preparing test results folder'
command: mkdir test-results
@ -341,7 +347,7 @@ jobs:
WORDPRESS_IMAGE_VERSION: << parameters.wordpress_image_version >>
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Set up virtual host'
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
@ -415,7 +421,7 @@ jobs:
executor: << parameters.executor >>
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Set up virtual host'
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
@ -474,7 +480,7 @@ jobs:
codeception_integration --steps --debug -vvv --html --xml
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Pull test docker images'
# Pull docker images with 3 retries
@ -495,7 +501,7 @@ jobs:
resource_class: medium+
steps:
- attach_workspace:
at: /home/circleci/mailpoet
at: /home/circleci
- run:
name: 'Set up environment'
command: |

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
save-workspace-protocol=rolling
strict-peer-dependencies=false

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v17.9.1

View File

@ -6,6 +6,7 @@ _output
composer.json
composer.lock
node_modules
pnpm-lock.yaml
vendor
vendor-prefixed
/.mp_svn

View File

@ -12,6 +12,16 @@ If you'd like to use the plugin code directly, see details in [the plugin's read
3. Run `./do start` to start the stack.
4. Go to http://localhost:8888 to see the dashboard of the dev environment.
## ✅ Additional dependencies
Even though it possible to run everything using Docker, in the development workflow,
it may be faster and more convenient to run some tasks outside the container. Therefore,
the following tools are recommended:
1. **PHP** as per `composer.json` requirements.
2. **Node.js**, as specified by `.nvmrc`. For automatic management use [nvm](https://github.com/nvm-sh/nvm), [FNM](https://github.com/Schniz/fnm), or [Volta](https://github.com/volta-cli/volta).
3. **pnpm**, as specified in `package.json`. For automatic setup enable [Corepack](https://nodejs.org/docs/latest-v17.x/api/corepack.html) using `corepack enable`.
## 🔍 PHPStorm setup for XDebug
In `Languages & Preferences > PHP > Servers` set path mappings:

View File

@ -9,9 +9,10 @@ RUN apt-get update \
&& docker-php-ext-install pdo_mysql \
&& pecl install xdebug-2.9.8 && \
\
# Install NodeJS + NPM
curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
# Install WP-CLI
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
@ -35,5 +36,6 @@ RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# ensure existing content in /var/www/html respects UID and GID
RUN chown -R ${UID}:${GID} /var/www/html
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
RUN chown -R ${UID}:${GID} /var/www/html && \
mkdir -p /.node && chown -R ${UID}:${GID} /.node

View File

@ -7,9 +7,10 @@ ARG GID=1000
RUN apt-get update \
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
&& \
# Install NodeJS + NPM
curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
# Install WP-CLI
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
@ -39,5 +40,6 @@ RUN docker-php-ext-install pdo_mysql
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# ensure existing content in /var/www/html respects UID and GID
RUN chown -R ${UID}:${GID} /var/www/html
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
RUN chown -R ${UID}:${GID} /var/www/html && \
mkdir -p /.node && chown -R ${UID}:${GID} /.node

View File

@ -7,9 +7,10 @@ ARG GID=1000
RUN apt-get update \
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
&& \
# Install NodeJS + NPM
curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
# Install WP-CLI
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
@ -39,5 +40,6 @@ RUN docker-php-ext-install pdo_mysql
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# ensure existing content in /var/www/html respects UID and GID
RUN chown -R ${UID}:${GID} /var/www/html
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
RUN chown -R ${UID}:${GID} /var/www/html && \
mkdir -p /.node && chown -R ${UID}:${GID} /.node

View File

@ -49,10 +49,14 @@ services:
MAILPOET_DEV_SITE: 1
volumes:
- './wordpress:/var/www/html'
- './eslint-config:/var/www/html/wp-content/plugins/eslint-config'
- './tsconfig.base.json:/var/www/html/wp-content/plugins/tsconfig.base.json:ro'
- './package.json:/var/www/html/wp-content/plugins/package.json'
- './pnpm-lock.yaml:/var/www/html/wp-content/plugins/pnpm-lock.yaml'
- './pnpm-workspace.yaml:/var/www/html/wp-content/plugins/pnpm-workspace.yaml'
- './patches:/var/www/html/wp-content/plugins/patches'
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
- './packages:/var/www/html/wp-content/plugins/packages'
- './templates:/var/www/templates'
test_wordpress:
@ -76,9 +80,9 @@ services:
WORDPRESS_DB_PASSWORD: wordpress
PHP_IDE_CONFIG: 'serverName=Mailpoet'
volumes:
- './eslint-config:/var/www/html/wp-content/plugins/eslint-config'
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
- './packages:/var/www/html/wp-content/plugins/packages'
smtp:
container_name: mp-mailhog

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,7 @@
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-nullish-coalescing-operator",
"babel-plugin-typescript-to-proptypes",
"@babel/plugin-proposal-class-properties",
[
"@babel/plugin-transform-runtime",
{
@ -15,18 +13,7 @@
"corejs": 3
}
],
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions"
]
}

View File

@ -1 +1 @@
v16.13.0
v17.9.1

View File

@ -23,9 +23,7 @@ class RoboFile extends \Robo\Tasks {
return $this->taskExecStack()
->stopOnFail()
->exec('./tools/vendor/composer.phar install')
->exec('npm ci --prefer-offline')
->exec('cd .. && npm ci --prefer-offline')
->exec('cd ../eslint-config && npm ci --prefer-offline')
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
->addCode([$this, 'cleanupCachedFiles'])
->run();
}
@ -41,7 +39,7 @@ class RoboFile extends \Robo\Tasks {
return $this->taskExecStack()
->stopOnFail()
->exec('./tools/vendor/composer.phar update')
->exec('npm update')
->exec('pnpm update')
->run();
}
@ -52,8 +50,8 @@ class RoboFile extends \Robo\Tasks {
$file = $changedFile->getResource()->getResource();
$this->taskExecStack()
->stopOnFail()
->exec('npm run scss')
->exec('npm run autoprefixer')
->exec('pnpm run scss')
->exec('pnpm run autoprefixer')
->run();
})
->run();
@ -92,9 +90,9 @@ class RoboFile extends \Robo\Tasks {
// Clean up folder from previous files
array_map('unlink', glob("assets/dist/css/*.*"));
$this->_exec('npm run stylelint -- "assets/css/src/**/*.scss"');
$this->_exec('npm run scss');
$compilationResult = $this->_exec('npm run autoprefixer');
$this->_exec('pnpm run stylelint -- "assets/css/src/**/*.scss"');
$this->_exec('pnpm run scss');
$compilationResult = $this->_exec('pnpm run autoprefixer');
// Create manifest file
$manifest = [];
@ -415,11 +413,11 @@ class RoboFile extends \Robo\Tasks {
}
public function qaLintJavascript() {
return $this->_exec('npm run check-types && npm run lint');
return $this->_exec('pnpm run check-types && pnpm run lint');
}
public function qaLintCss() {
return $this->_exec('npm run stylelint-check -- "assets/css/src/**/*.scss"');
return $this->_exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"');
}
public function qaCodeSniffer(array $filesToCheck, $opts = ['severity' => 'all']) {
@ -554,11 +552,11 @@ class RoboFile extends \Robo\Tasks {
}
public function storybookBuild() {
return $this->_exec('npm run build-storybook');
return $this->_exec('pnpm run build-storybook');
}
public function storybookWatch() {
return $this->_exec('npm run storybook');
return $this->_exec('pnpm run storybook');
}
public function svnCheckout() {

View File

@ -1,14 +0,0 @@
@font-face {
font-family: 'mailpoet-icon';
font-style: normal;
font-weight: normal;
src: url('data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAYcAAsAAAAABdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIFHGNtYXAAAAFoAAAAVAAAAFQXVtKHZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAAAhQAAAIU239UpGhlYWQAAAPYAAAANgAAADYYSCB9aGhlYQAABBAAAAAkAAAAJAelA8ZobXR4AAAENAAAABQAAAAUCeMAAGxvY2EAAARIAAAADAAAAAwAKAEebWF4cAAABFQAAAAgAAAAIAAIAMJuYW1lAAAEdAAAAYYAAAGGmUoJ+3Bvc3QAAAX8AAAAIAAAACAAAwAAAAMC8gGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QADwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkA//3//wAAAAAAIOkA//3//wAB/+MXBAADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAgAA/8AD4wPAAGQAvwAAEzI2Nz4BNTQwOQEREx4BFzEeATM4ATMyNjcjPgE3MRMRHAExFBYXMR4BMzI2Nz4BNTA0OQERPAE1NCYnMS4BIyoBIyIGBzMOAQcVCwEuASc1LgEjKgEjIgYHMw4BFREUFhceATMFJy4BIyIGBzEOASsBOAExIgYHMQcnLgErAS4BJxcuASMiBgcxBw4BFTEcATEUFhcxHgEXMTM4ATEyFhcdARQWMzI2PQE+ATM4ATEzPgE3Iz4BNTwBOQEuASc19xEYBgYHbwQNCAgXEAEKEwkBCg0DdggGBxcPEhcHBgcMCgodEQEDAg8dDQEMEQSBhgQPCgscEwEDAQ8bDAELDAcGBxcQAtkQBg8JCRAHECoYjDJZIQcHIVkyjRgpEQEHEAkJDwYQCAoICCBSL40sRAwUJCMUDEQsjS9TIAEICAEKCAF5BwgHEQoBASv+2QsRBwcGBAUFEQoBSP6+AQILEwcHCAgHBxIKAQHdAQEBDBYGBwcHBggWDgH+gwGADRQHAQcHBwcHGBD+LA4UBwYHtAwFBQYGDhAmIQgHISYBEA4BBgcGBQwGEwsBAQsSBh0hATUoAQEEGRkEASk1ASEdBhMKAQELEwYBAAEAAAAAAACzy1ndXw889QALBAAAAAAA2qNuAAAAAADao24AAAD/wAPjA8AAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA+MAAQAAAAAAAAAAAAAAAAAAAAUEAAAAAAAAAAAAAAACAAAAA+MAAAAAAAAACgAUAB4BCgABAAAABQDAAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEABwAAAAEAAAAAAAIABwBgAAEAAAAAAAMABwA2AAEAAAAAAAQABwB1AAEAAAAAAAUACwAVAAEAAAAAAAYABwBLAAEAAAAAAAoAGgCKAAMAAQQJAAEADgAHAAMAAQQJAAIADgBnAAMAAQQJAAMADgA9AAMAAQQJAAQADgB8AAMAAQQJAAUAFgAgAAMAAQQJAAYADgBSAAMAAQQJAAoANACkaWNvbW9vbgBpAGMAbwBtAG8AbwBuVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwaWNvbW9vbgBpAGMAbwBtAG8AbwBuaWNvbW9vbgBpAGMAbwBtAG8AbwBuUmVndWxhcgBSAGUAZwB1AGwAYQByaWNvbW9vbgBpAGMAbwBtAG8AbwBuRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==') format('woff');
}
/* menu icon */
#adminmenu #toplevel_page_mailpoet-newsletters .wp-menu-image:before {
content: '\e900';
font-family: 'mailpoet-icon';
font-style: normal;
font-weight: normal;
}

View File

@ -0,0 +1,34 @@
// See: https://github.com/WordPress/gutenberg/blob/af7da80dd54d7fe52772890e2cc1b65073db9655/packages/block-editor/src/components/block-icon/style.scss
.block-editor-block-icon {
align-items: center;
display: flex;
height: 24px;
justify-content: center;
width: 24px;
&.has-colors {
svg {
fill: currentColor;
// Optimize for high contrast modes.
// See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/.
@media (forced-colors: active) {
fill: CanvasText;
}
}
}
// Icons with width/height attributes below 20px will be sized up to 20px,
// and icons with width/height attributes above 24px will be sized down to
// 24px. Icons with width/height >=20px and <=24px will display at the
// indicated size.
// See: https://github.com/WordPress/gutenberg/pull/9828
svg {
max-height: 24px;
max-width: 24px;
min-height: 20px;
min-width: 20px;
}
}

View File

@ -0,0 +1,5 @@
#mailpoet_automation_editor {
.components-notice {
margin: 0;
}
}

View File

@ -0,0 +1,54 @@
.components-panel__body-title.mailpoet-automation-panel-plain-body-title {
display: grid;
grid-template-columns: 1fr auto;
}
.mailpoet-automation-panel-plain-body-title-text {
font-size: 13px;
font-weight: 500;
line-height: normal;
padding: 16px 48px 16px 16px;
}
.mailpoet-automation-panel-plain-body-title-action {
align-items: center;
display: flex;
padding: 8px 16px;
button {
height: auto;
padding: 4px;
}
}
.mailpoet-step-name-dropdown {
display: block;
h2 {
margin: 0;
}
.mailpoet-automation-panel-plain-body-title-text {
padding-left: 0;
padding-top: 0;
}
.mailpoet-automation-panel-plain-body-title-action {
margin-top: -10px;
padding-right: 0;
padding-top: 0;
}
}
.mailpoet-step-name-popover {
margin-top: -25px;
padding: 8px;
}
.mailpoet-step-name-input {
min-width: 208px;
}
.mailpoet-deactive {
color: #757575;
font-style: italic;
}

View File

@ -0,0 +1,33 @@
// See: https://github.com/WordPress/gutenberg/blob/af7da80dd54d7fe52772890e2cc1b65073db9655/packages/block-editor/src/components/block-card/style.scss
.block-editor-block-card {
align-items: flex-start;
display: flex;
padding: 16px;
}
.block-editor-block-card__content {
flex-grow: 1;
margin-bottom: 4px;
}
.block-editor-block-card__title {
font-weight: 500;
&.block-editor-block-card__title {
line-height: 24px;
margin: 0 0 4px;
}
}
.block-editor-block-card__description {
font-size: 13px;
}
.block-editor-block-card .block-editor-block-icon {
flex: 0 0 24px;
height: 24px;
margin-left: 0;
margin-right: 12px;
width: 24px;
}

View File

@ -13,7 +13,8 @@
text-align: left;
width: 280px;
&:focus {
&:focus,
&.selected-step {
box-shadow:
0 0 0 1px #fbfbfb, // space
0 0 0 calc(var(--wp-admin-border-width-focus) + 1px) var(--wp-admin-theme-color), // focus ring
@ -31,4 +32,14 @@
.mailpoet-automation-editor-step-subtitle {
color: inherit;
font-weight: 600;
}
.mailpoet-automation-colored-icon {
border-radius: 50%;
box-sizing: content-box;
display: flex;
justify-content: center;
padding: 12px;
position: relative;
}

View File

@ -0,0 +1,3 @@
@import './mailpoet/button';
@import './mailpoet/edit';
@import './mailpoet/thumbnail';

View File

@ -0,0 +1,37 @@
.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: #1d2327;
width: 100%;
&:hover:not(:disabled) {
background: #1d2327;
}
&:disabled {
color: rgba(255, 255, 255, .4);
}
&.is-busy {
--background-color-1: #2c3236;
--background-color-2: #535659;
background-image:
linear-gradient(
-45deg,
var(--background-color-1) 33%,
var(--background-color-2) 33%,
var(--background-color-2) 70%,
var(--background-color-1) 70%
);
}
}
.components-button.mailpoet-automation-button-centered,
.components-button.mailpoet-automation-button-centered.has-text,
.components-button.mailpoet-automation-button-centered.has-icon {
justify-content: center;
svg {
margin-right: 0;
}
}

View File

@ -0,0 +1,3 @@
.mailpoet-automation-email-content-separator {
height: 16px;
}

View File

@ -0,0 +1,35 @@
.mailpoet-automation-thumbnail-box {
align-items: center;
background: #f6f7f7;
border: 1px solid #2c3338;
display: flex;
height: 280px;
justify-content: center;
margin-bottom: 16px;
width: 100%;
}
.mailpoet-automation-thumbnail-spinner {
height: 32px;
width: 32px;
}
.mailpoet-automation-thumbnail-wrapper {
display: flex;
height: 100%;
justify-content: center;
overflow: hidden;
width: 100%;
}
.mailpoet-automation-thumbnail-image {
display: block;
margin: auto;
max-width: 192px;
}
.mailpoet-automation-thumbnail-buttons {
display: grid;
gap: 8px;
grid-template-columns: 1fr 1fr;
}

View File

@ -83,13 +83,14 @@ h2 {
}
}
// Adjustments for family-font-select in popover
.mailpoet_toolbar_item {
align-items: center;
background-color: white;
display: flex;
.mailpoet-font-family-select {
width: $grid-column-small;
height: 48px;
.components-input-control__container .components-custom-select-control__button {
width: 200px;
}
}
// Force rendering of select arrow on the right

View File

@ -22,18 +22,31 @@
min-width: 5em;
}
.block-editor-panel-color-gradient-settings {
border: none;
padding: 10px 0;
h2 {
font-weight: normal;
}
}
hr {
margin: 0;
}
}
.mailpoet-font-family-select {
button {
width: 100%;
}
.mailpoet-font-family-select {
.components-flex {
height: auto;
}
.components-custom-select-control__label {
font-weight: bold;
.components-input-control__container {
width: 100%;
}
.components-custom-select-control__label {
font-weight: bold;
}
}
}

View File

@ -88,3 +88,33 @@ p.sender_email_address_warning:first-child {
margin-left: 10px;
}
}
.mailpoet_manage_sender_domain {
.mailpoet_table_header {
font-weight: 700 !important;
text-align: center !important; // to prevent being overwritten by widefat table classes
}
.dns_record_type_column {
font-weight: 550 !important;
text-align: center !important; // to prevent being overwritten by widefat table classes
}
}
.mailpoet-premium-modal.components-modal__frame {
max-width: 500px;
}
.mailpoet-premium-modal-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 16px;
padding-top: 12px;
}
.mailpoet-premium-modal-error {
display: flex;
justify-content: flex-end;
margin-top: $grid-gap-half;
}

View File

@ -92,17 +92,16 @@ h1.title.mailpoet-newsletter-listing-heading {
margin-bottom: $grid-gap;
}
.mailpoet-newsletter-listing-heading-wrapper {
.mailpoet-top-bar-logo {
cursor: pointer;
left: 17px;
position: absolute;
top: 25px;
z-index: 1;
#mailpoet_editor_steps_heading {
.mailpoet-top-bar {
left: 0;
svg {
max-height: 100%;
max-width: 100%;
.mailpoet-top-bar-logo {
z-index: 1;
}
.mailpoet-top-bar-beamer {
top: 4px;
}
}
}

View File

@ -1,38 +0,0 @@
#logger {
background-color: transparent;
border: 0;
border-top: 1px #aba9a9 solid;
font-size: .85em;
height: 300px;
margin-top: 20px;
overflow: scroll;
padding: 2px;
resize: both;
width: 100%;
}
#progressbar {
background-color: #d8d8d8;
border-radius: 5px;
width: 50%;
}
$progressbar_color: #fecf23;
$progressbar_gradient_to_color: #fd9215;
.ui-progressbar .ui-progressbar-value {
background-color: $progressbar_color;
background-image: linear-gradient(to bottom, $progressbar_color, $progressbar_gradient_to_color);
border: 0;
border-radius: 3px;
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset;
height: 100%;
}
.mailpoet_progress_label {
font-size: 15px;
}
.error_msg {
color: #f00;
}

View File

@ -65,6 +65,18 @@ $form-line-height: 1.4;
}
}
/* Reset fieldset styles in form for backward compatibility. */
.mailpoet_paragraph {
fieldset,
legend {
background: transparent;
border: 0;
color: inherit;
margin: 0;
padding: 0;
}
}
.mailpoet_textarea {
height: auto;
}

View File

@ -85,6 +85,8 @@ $beamer-dot-size: 8px;
border: none;
color: $color-wordpress-grey-dark;
cursor: pointer;
display: flex;
flex-direction: column;
height: 60px;
justify-content: center;
position: relative;

View File

@ -61,7 +61,7 @@
margin-top: $grid-gap;
}
&:not(.mailpoet-full-width) + .mailpoet-form-input:not(.mailpoet-full-width),
&:not(.mailpoet-full-width) + .mailpoet-form-input:not(.mailpoet-full-width):not(.mailpoet-form-select),
&:not(.mailpoet-full-width) + .mailpoet-button:not(.mailpoet-full-width) {
margin-left: $grid-gap;
}

View File

@ -3,7 +3,6 @@
border: 1px solid $color-input-border !important;
border-radius: $form-control-border-radius !important;
box-sizing: border-box;
display: inline-flex;
max-width: 100%;
min-height: $form-control-height;
min-width: 0;
@ -11,6 +10,7 @@
// To align the left padding with the other inputs
input[type=text].components-form-token-field__input {
margin-left: 0;
min-height: 30px;
padding-left: 8px;
}
// For better fit when the last item is active

View File

@ -1,5 +1,4 @@
// Utilities
// Helpers and overrides.
@import 'components-admin/menu';
@import 'components-admin/3rd-party-plugins/members';

View File

@ -4,9 +4,17 @@
@import '../../../node_modules/@wordpress/block-editor/build-style/style'; // for inserter styles
@import 'settings/colors';
@import './components-automation-editor/add-step-button';
@import './components-automation-editor/block-icon';
@import './components-automation-editor/dropdown';
@import './components-automation-editor/empty-workflow';
@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';
// integrations
@import './components-automation-integrations/mailpoet';

View File

@ -74,7 +74,6 @@
@import 'components-plugin/pages-custom';
@import 'components-plugin/premium-page';
@import 'components-plugin/menu';
@import 'components-plugin/mp2-migrator';
@import 'components-plugin/newsletter';
@import 'components-plugin/newsletter-templates';
@import 'components-plugin/newsletter-types';

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import { api } from './config';
import { api } from '../config';
const API_URL = `${api.root}/mailpoet/v1/automation`;

View File

@ -1,5 +1,7 @@
import apiFetch from '@wordpress/api-fetch';
import { api } from '../../config';
import { api } from '../config';
export * from './hooks';
const apiUrl = `${api.root}/mailpoet/v1/automation/`;

View File

@ -7,6 +7,7 @@ import {
CreateWorkflowFromTemplateButton,
} from './testing';
import { useMutation, useQuery } from './api';
import { WorkflowListingNotices } from './listing/workflow-listing-notices';
function Workflows(): JSX.Element {
const { data, loading, error } = useQuery<{ data: Workflow[] }>('workflows');
@ -34,6 +35,7 @@ function RecreateSchemaButton(): JSX.Element {
return (
<div>
<WorkflowListingNotices />
<button
className="button button-link-delete"
type="button"
@ -81,11 +83,15 @@ function App(): JSX.Element {
<Workflows />
<div style={{ marginTop: 30, display: 'grid', gridGap: 8 }}>
<CreateTestingWorkflowButton />
<CreateWorkflowFromTemplateButton template="delayed-email-after-signup">
<CreateWorkflowFromTemplateButton slug="simple-welcome-email">
Create testing workflow from template (welcome email)
</CreateWorkflowFromTemplateButton>
<CreateWorkflowFromTemplateButton template="welcome-email-sequence">
Create testing workflow from template (welcome sequence)
<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 />

View File

@ -0,0 +1,70 @@
import apiFetch from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
import { StoreDescriptor, useDispatch, useSelect } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { addQueryArgs } from '@wordpress/url';
import { confirmAlert } from '../../../../common/confirm_alert';
import { store } from '../../store';
import { Workflow } from '../workflow/types';
import { MailPoet } from '../../../../mailpoet';
import { LISTING_NOTICE_PARAMETERS } from '../../../listing/workflow-listing-notices';
export function TrashButton(): JSX.Element {
const { createErrorNotice } = useDispatch(noticesStore as StoreDescriptor);
const { workflow } = useSelect(
(select) => ({
workflow: select(store).getWorkflowData(),
}),
[],
);
const trash = () => {
apiFetch({
path: `/workflows/${workflow.id}`,
method: 'PUT',
data: {
...workflow,
status: 'trash',
},
})
.then(({ data }: { data: Workflow }) => {
if (data.status !== 'trash') {
void createErrorNotice('An error occurred!', {
explicitDismiss: true,
});
return;
}
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
[LISTING_NOTICE_PARAMETERS.workflowDeleted]: workflow.id,
});
})
.catch((): void => {
void createErrorNotice('An error occurred!', {
explicitDismiss: true,
});
});
return true;
};
return (
<Button
isSecondary
isDestructive
onClick={() => {
confirmAlert({
title: 'Delete workflow',
message: `You are about to delete the “${workflow.name}” workflow`,
cancelLabel: 'Cancel',
confirmLabel: 'Yes, delete',
onConfirm: () => {
trash();
},
});
}}
>
Move to Trash
</Button>
);
}

View File

@ -32,6 +32,16 @@ function UpdateButton(): JSX.Element {
);
}
function SaveDraftButton(): JSX.Element {
const { save } = useDispatch(store);
return (
<Button isTertiary onClick={save}>
{__('Save Draft')}
</Button>
);
}
export function Header(): JSX.Element {
const { setWorkflowName } = useDispatch(store);
const { workflowName, workflowStatus } = useSelect(
@ -75,7 +85,7 @@ export function Header(): JSX.Element {
<div className="edit-site-header_end">
<div className="edit-site-header__actions">
<Button isTertiary>{__('Save Draft')}</Button>
<SaveDraftButton />
{workflowStatus !== WorkflowStatus.ACTIVE && <ActivateButton />}
{workflowStatus === WorkflowStatus.ACTIVE && <UpdateButton />}
<PinnedItems.Slot scope={storeName} />

View File

@ -0,0 +1,30 @@
import { Icon } from '@wordpress/components';
import { ComponentType } from 'react';
export type ColoredIconProps = {
width: string;
height: string;
background: string;
foreground: string;
icon: ComponentType;
};
export function ColoredIcon({
foreground,
background,
...iconProps
}: ColoredIconProps): JSX.Element {
return (
<div
className="mailpoet-automation-colored-icon"
style={{
width: iconProps.width,
height: iconProps.height,
backgroundColor: background,
fill: foreground,
}}
>
<Icon {...iconProps} />
</div>
);
}

View File

@ -1,20 +0,0 @@
export function DelayIcon(): JSX.Element {
return (
<svg
width="41"
height="41"
viewBox="0 0 41 41"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.796875 20.5C0.796875 9.45431 9.75118 0.5 20.7969 0.5C31.8426 0.5 40.7969 9.45431 40.7969 20.5C40.7969 31.5457 31.8426 40.5 20.7969 40.5C9.75118 40.5 0.796875 31.5457 0.796875 20.5Z"
fill="#F7EDF7"
/>
<path
d="M20.7972 11.1665C15.6639 11.1665 11.4639 15.3665 11.4639 20.4998C11.4639 25.6332 15.6639 29.8332 20.7972 29.8332C25.9305 29.8332 30.1305 25.6332 30.1305 20.4998C30.1305 15.3665 25.9305 11.1665 20.7972 11.1665ZM20.7972 27.9665C16.6812 27.9665 13.3305 24.6158 13.3305 20.4998C13.3305 16.3838 16.6812 13.0332 20.7972 13.0332C24.9132 13.0332 28.2639 16.3838 28.2639 20.4998C28.2639 24.6158 24.9132 27.9665 20.7972 27.9665ZM21.2639 15.8332H19.8639V21.4332L24.7172 24.4198L25.4639 23.2065L21.2639 20.6865V15.8332Z"
fill="#7F54B3"
/>
</svg>
);
}

View File

@ -1,26 +0,0 @@
export function EmailIcon(): JSX.Element {
return (
<svg
width="41"
height="41"
viewBox="0 0 41 41"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.5 20.5C0.5 9.45431 9.45431 0.5 20.5 0.5C31.5457 0.5 40.5 9.45431 40.5 20.5C40.5 31.5457 31.5457 40.5 20.5 40.5C9.45431 40.5 0.5 31.5457 0.5 20.5Z"
fill="#FCF9E8"
/>
<path
d="M27.4998 14.375H13.4998C12.6944 14.375 12.0415 15.0279 12.0415 15.8333V25.1667C12.0415 25.9721 12.6944 26.625 13.4998 26.625H27.4998C28.3053 26.625 28.9582 25.9721 28.9582 25.1667V15.8333C28.9582 15.0279 28.3053 14.375 27.4998 14.375Z"
stroke="#996800"
strokeWidth="1.5"
/>
<path
d="M12.3335 14.6665L20.5002 21.6665L28.6668 14.6665"
stroke="#996800"
strokeWidth="1.5"
/>
</svg>
);
}

View File

@ -1,3 +1,2 @@
export { DelayIcon } from './delay';
export { EmailIcon } from './email';
export { TriggerIcon } from './trigger';
export { ColoredIcon } from './colored-icon';

View File

@ -1,24 +1,8 @@
export function TriggerIcon(): JSX.Element {
return (
<svg
width="40"
height="41"
viewBox="0 0 40 41"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 20.5C0 9.45431 8.95431 0.5 20 0.5C31.0457 0.5 40 9.45431 40 20.5C40 31.5457 31.0457 40.5 20 40.5C8.95431 40.5 0 31.5457 0 20.5Z"
fill="#F0F6FC"
/>
<path
d="M24.3163 12.5667L30.8496 19.1C31.6663 19.8 31.6663 21.2 30.7329 22.0167L24.1996 28.55C23.8496 28.9 23.2663 29.1333 22.7996 29.1333C22.3329 29.1333 21.7496 28.9 21.3996 28.55L14.8663 22.0167C14.0496 21.2 14.0496 19.9167 14.8663 19.1L21.3996 12.5667C22.2163 11.75 23.4996 11.75 24.3163 12.5667Z"
fill="#2271B1"
/>
<path
d="M17.6663 12.9167L10.3163 20.2667C10.1996 20.3833 10.1996 20.6167 10.4329 20.6167L17.7829 27.9667L16.4996 29.25L9.14961 22.0167C8.33294 21.2 8.33294 19.9167 9.14961 19.1L16.4996 11.75L17.6663 12.9167Z"
fill="#2271B1"
/>
<svg viewBox="0 0 24 18" xmlns="http://www.w3.org/2000/svg">
<path d="M16.1135 1.06667L22.6469 7.6C23.4635 8.3 23.4635 9.7 22.5302 10.5167L15.9969 17.05C15.6469 17.4 15.0635 17.6333 14.5968 17.6333C14.1302 17.6333 13.5468 17.4 13.1968 17.05L6.66352 10.5167C5.84685 9.7 5.84685 8.41667 6.66352 7.6L13.1968 1.06667C14.0135 0.25 15.2968 0.25 16.1135 1.06667Z" />
<path d="M9.46352 1.41667L2.11352 8.76667C1.99685 8.88333 1.99685 9.11667 2.23018 9.11667L9.58018 16.4667L8.29685 17.75L0.946851 10.5167C0.130184 9.7 0.130184 8.41667 0.946851 7.6L8.29685 0.25L9.46352 1.41667Z" />
</svg>
);
}

View File

@ -1,7 +1,9 @@
import { ComponentType } from 'react';
export type Item = {
id: string;
key: string;
title: string;
icon: JSX.Element;
description: string;
isDisabled: boolean;
icon: ComponentType | JSX.Element;
isDisabled?: boolean;
};

View File

@ -1,11 +0,0 @@
import { ReactNode } from 'react';
// See: https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/block-editor/src/components/block-icon/index.js
type Props = {
icon: ReactNode;
};
export function StepIcon({ icon }: Props): JSX.Element {
return <span className="block-editor-block-icon">{icon}</span>;
}

View File

@ -1,5 +1,5 @@
import { StepIcon } from './step_icon';
import { Item } from './item';
import { StepIcon } from '../step-icon';
// See: https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/block-editor/src/components/inserter/preview-panel.js

View File

@ -46,9 +46,9 @@ export function StepList({
<InserterListboxRow key={i}>
{row.map((item, j) => (
<InserterListItem
key={item.id}
key={item.key}
item={item}
className={getBlockMenuDefaultClassName(item.id)}
className={getBlockMenuDefaultClassName(item.key)}
onSelect={onSelect}
onHover={onHover}
isDraggable={isDraggable}

View File

@ -3,8 +3,8 @@ import { ComponentProps } from 'react';
import { useRef, memo } from '@wordpress/element';
import { ENTER } from '@wordpress/keycodes';
import { Item } from './item';
import { StepIcon } from './step_icon';
import { InserterListboxItem } from '../inserter-listbox/listbox_item';
import { StepIcon } from '../step-icon';
// See: https://github.com/WordPress/gutenberg/blob/628ae68152f572d0b395bb15c0f71b8821e7f130/packages/block-editor/src/components/inserter-list-item/index.js

View File

@ -22,7 +22,7 @@ export function KeyboardShortcuts(): null {
const { registerShortcut } = useDispatch(keyboardShortcutsStore);
useEffect(() => {
registerShortcut({
void registerShortcut({
name: 'mailpoet/automation-editor/toggle-fullscreen',
category: 'global',
description: __('Toggle fullscreen mode.'),
@ -32,7 +32,7 @@ export function KeyboardShortcuts(): null {
},
});
registerShortcut({
void registerShortcut({
name: 'mailpoet/automation-editor/toggle-sidebar',
category: 'global',
description: __('Show or hide the settings sidebar.'),

View File

@ -0,0 +1,2 @@
export * from './plain-body-title';
export * from './title-action-button';

View File

@ -0,0 +1,17 @@
import { ReactNode } from 'react';
type Props = {
title: string;
children?: ReactNode;
};
export function PlainBodyTitle({ title, children }: Props): JSX.Element {
return (
<h2 className="components-panel__body-title mailpoet-automation-panel-plain-body-title">
<div className="mailpoet-automation-panel-plain-body-title-text">
{title}
</div>
{children}
</h2>
);
}

View File

@ -0,0 +1,47 @@
import { Dropdown, TextControl } from '@wordpress/components';
import { edit, Icon } from '@wordpress/icons';
import { PlainBodyTitle } from './plain-body-title';
import { TitleActionButton } from './title-action-button';
type Props = {
currentName: string;
defaultName: string;
update: (value: string) => void;
};
export function StepName({
currentName,
defaultName,
update,
}: Props): JSX.Element {
return (
<Dropdown
className="mailpoet-step-name-dropdown"
contentClassName="mailpoet-step-name-popover"
position="bottom left"
renderToggle={({ isOpen, onToggle }) => (
<PlainBodyTitle
title={currentName.length > 0 ? currentName : defaultName}
>
<TitleActionButton
onClick={onToggle}
aria-expanded={isOpen}
aria-label="Edit step name"
>
<Icon icon={edit} size={16} />
</TitleActionButton>
</PlainBodyTitle>
)}
renderContent={() => (
<TextControl
label="Step name"
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."
/>
)}
/>
);
}

View File

@ -0,0 +1,12 @@
import { Button } from '@wordpress/components';
import { ComponentProps } from 'react';
type Props = ComponentProps<typeof Button>;
export function TitleActionButton(props: Props): JSX.Element {
return (
<div className="mailpoet-automation-panel-plain-body-title-action">
<Button {...props} />
</div>
);
}

View File

@ -1,11 +1,13 @@
import { PanelBody } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store } from '../../../store';
import { StepCard } from '../../step-card';
export function StepSidebar(): JSX.Element {
const { selectedStep } = useSelect(
const { selectedStep, selectedStepType } = useSelect(
(select) => ({
selectedStep: select(store).getSelectedStep(),
selectedStepType: select(store).getSelectedStepType(),
}),
[],
);
@ -14,20 +16,36 @@ export function StepSidebar(): JSX.Element {
return <PanelBody>No step selected.</PanelBody>;
}
if (!selectedStepType) {
return <PanelBody>Unknown step type.</PanelBody>;
}
const Edit = selectedStepType.edit;
return (
<PanelBody>
<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>
<div className="block-editor-block-inspector">
<StepCard
title={selectedStepType.title}
description={selectedStepType.description}
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>
</div>
);
}

View File

@ -1,6 +1,7 @@
import { PanelBody } from '@wordpress/components';
import { PanelBody, PanelRow } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store } from '../../../store';
import { TrashButton } from '../../actions/trash-button';
export function WorkflowSidebar(): JSX.Element {
const { workflowData } = useSelect(
@ -10,26 +11,44 @@ export function WorkflowSidebar(): JSX.Element {
[],
);
const dateOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric',
};
return (
<PanelBody>
<div>
<strong>{workflowData.name}</strong>
</div>
<br />
<div>
<strong>ID:</strong> {workflowData.id}
</div>
<div>
<strong>Status:</strong> {workflowData.status}
</div>
<div>
<strong>Created:</strong>{' '}
{new Date(Date.parse(workflowData.created_at)).toLocaleString()}
</div>
<div>
<strong>Updated:</strong>{' '}
{new Date(Date.parse(workflowData.updated_at)).toLocaleString()}
</div>
<PanelBody title="Automation details" initialOpen>
<PanelRow>
<strong>Date added</strong>{' '}
{new Date(Date.parse(workflowData.created_at)).toLocaleDateString(
undefined,
dateOptions,
)}
</PanelRow>
<PanelRow>
<strong>Activated</strong>{' '}
{workflowData.status === 'active' &&
new Date(Date.parse(workflowData.updated_at)).toLocaleDateString(
undefined,
dateOptions,
)}
{workflowData.status !== 'active' &&
workflowData.activated_at &&
new Date(Date.parse(workflowData.activated_at)).toLocaleDateString(
undefined,
dateOptions,
)}
{workflowData.status !== 'active' && !workflowData.activated_at && (
<span className="mailpoet-deactive">Not activated yet.</span>
)}
</PanelRow>
<PanelRow>
<strong>Author</strong> {workflowData.author.name}
</PanelRow>
<PanelRow>
<TrashButton />
</PanelRow>
</PanelBody>
);
}

View File

@ -0,0 +1,24 @@
import { ComponentType } from 'react';
import { StepIcon } from '../step-icon';
// See: https://github.com/WordPress/gutenberg/blob/af7da80dd54d7fe52772890e2cc1b65073db9655/packages/block-editor/src/components/block-card/index.js
type Props = {
title: string;
description: string;
icon: JSX.Element | ComponentType;
};
export function StepCard({ title, description, icon }: Props): JSX.Element {
return (
<div className="block-editor-block-card">
<StepIcon icon={icon} />
<div className="block-editor-block-card__content">
<h2 className="block-editor-block-card__title">{title}</h2>
<span className="block-editor-block-card__description">
{description}
</span>
</div>
</div>
);
}

View File

@ -0,0 +1,16 @@
import { ComponentType } from 'react';
import { Icon } from '@wordpress/components';
// See: https://github.com/WordPress/gutenberg/blob/af7da80dd54d7fe52772890e2cc1b65073db9655/packages/block-editor/src/components/block-icon/index.js
type Props = {
icon: ComponentType | JSX.Element;
};
export function StepIcon({ icon }: Props): JSX.Element {
return (
<span className="block-editor-block-icon">
<Icon icon={icon} />
</span>
);
}

View File

@ -4,6 +4,7 @@ import {
__unstableUseCompositeState as useCompositeState,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { EditorNotices } from '@wordpress/editor';
import { WorkflowCompositeContext } from './context';
import { EmptyWorkflow } from './empty-workflow';
import { Separator } from './separator';
@ -12,9 +13,10 @@ import { InserterPopover } from '../inserter-popover';
import { store } from '../../store';
export function Workflow(): JSX.Element {
const { workflowData } = useSelect(
const { workflowData, selectedStep } = useSelect(
(select) => ({
workflowData: select(store).getWorkflowData(),
selectedStep: select(store).getSelectedStep(),
}),
[],
);
@ -61,6 +63,7 @@ export function Workflow(): JSX.Element {
return (
<WorkflowCompositeContext.Provider value={compositeState}>
<EditorNotices />
<Composite
state={compositeState}
role="tree"
@ -72,7 +75,10 @@ export function Workflow(): JSX.Element {
{steps.map((step, i) => (
<Fragment key={step.id}>
{i > 0 && <Separator />}
<Step step={step} key={step.id} />
<Step
step={step}
isSelected={selectedStep && step.id === selectedStep.id}
/>
</Fragment>
))}
<div />

View File

@ -1,64 +1,60 @@
import { useContext } from 'react';
import { __unstableCompositeItem as CompositeItem } from '@wordpress/components';
import { useDispatch, useRegistry } from '@wordpress/data';
import { useDispatch, useRegistry, select } from '@wordpress/data';
import { WorkflowCompositeContext } from './context';
import { Step as StepType } from './types';
import { DelayIcon, EmailIcon, TriggerIcon } from '../icons';
import { Step as StepData } from './types';
import { stepSidebarKey, store } from '../../store';
import { TriggerIcon, ColoredIcon } from '../icons';
// mocked data
function getIcon(step: StepType): JSX.Element | null {
function getIcon(step: StepData): JSX.Element | null {
// mocked data
if (step.type === 'trigger') {
return <TriggerIcon />;
return (
<ColoredIcon
foreground="#2271b1"
background="#f0f6fc"
width="23px"
height="23px"
icon={TriggerIcon}
/>
);
}
if (step.key === 'core:delay') {
return <DelayIcon />;
}
if (step.key === 'mailpoet:send-email') {
return <EmailIcon />;
}
return null;
const selectedStepType = select(store).getStepType(step.key);
return selectedStepType ? (
<ColoredIcon
width="23px"
height="23px"
foreground={selectedStepType.foreground}
background={selectedStepType.background}
icon={selectedStepType.icon}
/>
) : null;
}
// mocked data
function getTitle(step: StepType): string {
function getTitle(step: StepData): string {
// mocked data
if (step.type === 'trigger') {
return 'Trigger';
}
if (step.key === 'core:delay') {
return 'Delay';
}
if (step.key === 'mailpoet:send-email') {
return 'Send email';
}
return '';
const selectedStepType = select(store).getStepType(step.key);
return selectedStepType ? selectedStepType.title : '';
}
// mocked data
function getSubtitle(step: StepType): string {
function getSubtitle(step: StepData): JSX.Element | string {
// mocked data
if (step.key === 'mailpoet:segment:subscribed') {
return 'Subscribed to segment';
}
if (step.key === 'core:delay') {
return `${step.args.seconds as number} seconds`;
}
if (step.key === 'mailpoet:send-email') {
return `Email ID: ${step.args.email_id as number}`;
}
return step.key;
const selectedStepType = select(store).getStepType(step.key);
return selectedStepType ? selectedStepType.subtitle(step) : null;
}
type Props = {
step: StepType;
step: StepData;
isSelected: boolean;
};
export function Step({ step }: Props): JSX.Element {
export function Step({ step, isSelected }: Props): JSX.Element {
const { openSidebar, selectStep } = useDispatch(store);
const compositeState = useContext(WorkflowCompositeContext);
const { batch } = useRegistry();
@ -67,7 +63,9 @@ export function Step({ step }: Props): JSX.Element {
<CompositeItem
state={compositeState}
role="treeitem"
className="mailpoet-automation-editor-step"
className={`mailpoet-automation-editor-step ${
isSelected ? 'selected-step' : ''
}`}
key={step.id}
focusable
onClick={() =>

View File

@ -9,8 +9,13 @@ export type Step = {
export type Workflow = {
id?: number;
name: string;
status: 'active' | 'inactive' | 'draft';
status: 'active' | 'inactive' | 'draft' | 'trash';
created_at: string;
updated_at: string;
activated_at: string;
author: {
id: number;
name: string;
};
steps: Record<string, Step>;
};

View File

@ -9,12 +9,18 @@ import {
FullscreenMode,
} from '@wordpress/interface';
import { ShortcutProvider } from '@wordpress/keyboard-shortcuts';
import { addQueryArgs } from '@wordpress/url';
import { Header } from './components/header';
import { InserterSidebar } from './components/inserter-sidebar';
import { KeyboardShortcuts } from './components/keyboard-shortcuts';
import { Sidebar } from './components/sidebar';
import { Workflow } from './components/workflow';
import { store, 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';
// See:
// https://github.com/WordPress/gutenberg/blob/9601a33e30ba41bac98579c8d822af63dd961488/packages/edit-post/src/components/layout/index.js
@ -26,12 +32,14 @@ function Editor(): JSX.Element {
isInserterOpened,
isSidebarOpened,
showIconLabels,
workflow,
} = useSelect(
(select) => ({
isFullscreenActive: select(store).isFeatureActive('fullscreenMode'),
isInserterOpened: select(store).isInserterSidebarOpened(),
isSidebarOpened: select(store).isSidebarOpened(),
showIconLabels: select(store).isFeatureActive('showIconLabels'),
workflow: select(store).getWorkflowData(),
}),
[],
);
@ -41,6 +49,12 @@ function Editor(): JSX.Element {
'show-icon-labels': showIconLabels,
});
if (workflow.status === 'trash') {
window.location.href = addQueryArgs(MailPoet.urls.automationListing, {
[LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted]: workflow.id,
});
return null;
}
return (
<ShortcutProvider>
<SlotFillProvider>
@ -75,6 +89,9 @@ function Editor(): JSX.Element {
window.addEventListener('DOMContentLoaded', () => {
const root = document.getElementById('mailpoet_automation_editor');
if (root) {
initializeApi();
initializeCoreIntegration();
initializeMailPoetIntegration();
ReactDOM.render(<Editor />, root);
}
});

View File

@ -49,13 +49,27 @@ export function setWorkflowName(name) {
} as const;
}
export function* save() {
const workflow = select(storeName).getWorkflowData();
const data = yield apiFetch({
path: `/workflows/${workflow.id}`,
method: 'PUT',
data: workflow,
});
return {
type: 'SAVE',
workflow: data.data,
} as const;
}
export function* activate() {
const workflow = select(storeName).getWorkflowData();
const data = yield apiFetch({
path: `/workflows/${workflow.id}`,
method: 'PUT',
data: {
name: workflow.name,
...workflow,
status: 'active',
},
});
@ -65,3 +79,19 @@ export function* activate() {
workflow: data.data,
} as const;
}
export function registerStepType(stepType) {
return {
type: 'REGISTER_STEP_TYPE',
stepType,
};
}
export function updateStepArgs(stepId, name, value) {
return {
type: 'UPDATE_STEP_ARGS',
stepId,
name,
value,
};
}

View File

@ -1,36 +1,3 @@
import { createReduxStore, register, StoreDescriptor } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import * as actions from './actions';
import { initializeApi } from './api';
import { storeName } from './constants';
import { initialState } from './initial_state';
import { reducer } from './reducer';
import * as selectors from './selectors';
import { State } from './types';
import { OmitFirstArgs } from '../../../types';
export * from './constants';
type StoreType = Omit<StoreDescriptor, 'name'> & {
name: typeof storeName;
};
initializeApi();
export const store = createReduxStore<State>(storeName, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the "Action" type is missing thunks with "dispatch"
actions: actions as any,
controls,
selectors,
reducer,
initialState,
}) as StoreType;
type StoreKey = typeof storeName | StoreType;
declare module '@wordpress/data' {
function select(key: StoreKey): OmitFirstArgs<typeof selectors>;
function dispatch(key: StoreKey): typeof actions;
}
register(store);
export * from './register_step_type';
export * from './store';

View File

@ -1,89 +1,12 @@
import {
atSymbol,
backup,
code,
commentAuthorAvatar,
commentEditLink,
flipHorizontal,
share,
tag,
} from '@wordpress/icons';
import { AutomationEditorWindow, State } from './types';
import { Item } from '../components/inserter/item';
declare let window: AutomationEditorWindow;
// mocked data
const actionSteps: Item[] = [
{
id: 'mailpoet/automation-send-email',
title: 'Send email',
icon: atSymbol,
description: 'Send an email.',
isDisabled: false,
},
{
id: 'mailpoet/automation-update-contact',
title: 'Update contact',
icon: commentEditLink,
description: 'Update contact information.',
isDisabled: false,
},
{
id: 'mailpoet/automation-webhook',
title: 'Webhook',
icon: code,
description: 'Trigger a webhook.',
isDisabled: false,
},
{
id: 'mailpoet/automation-tag-untag',
title: 'Tag/Untag',
icon: tag,
description: 'Add or remove tag',
isDisabled: false,
},
{
id: 'mailpoet/automation-unsubscribe',
title: 'Unsubscribe',
icon: commentAuthorAvatar,
description: 'Unsubscribe MailPoet subscriber.',
isDisabled: false,
},
];
// mocked data
const logicalSteps: Item[] = [
{
id: 'mailpoet/automation-delay',
title: 'Delay',
icon: backup,
description: 'Add a delay.',
isDisabled: false,
},
{
id: 'mailpoet/automation-if-else',
title: 'If/Else',
icon: share,
description: 'Execute a conditional statement.',
isDisabled: false,
},
{
id: 'mailpoet/automation-a-b-test',
title: 'A/B split test',
icon: flipHorizontal,
description: 'Run an A/B test.',
isDisabled: false,
},
];
export const initialState: State = {
stepTypes: {},
workflowData: { ...window.mailpoet_automation_workflow },
workflowSaved: true,
selectedStep: undefined,
inserter: {
actionSteps,
logicalSteps,
},
inserterSidebar: {
isOpened: false,
},

View File

@ -28,12 +28,56 @@ export function reducer(state: State, action: Action): State {
return {
...state,
workflowData: action.workflow,
workflowSaved: false,
};
case 'SAVE':
return {
...state,
workflowData: action.workflow,
workflowSaved: true,
};
case 'ACTIVATE':
return {
...state,
workflowData: action.workflow,
workflowSaved: true,
};
case 'REGISTER_STEP_TYPE':
return {
...state,
stepTypes: {
...state.stepTypes,
[action.stepType.key]: action.stepType,
},
};
case 'UPDATE_STEP_ARGS': {
const prevArgs = state.workflowData.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 step = { ...state.workflowData.steps[action.stepId], args };
return {
...state,
workflowData: {
...state.workflowData,
steps: {
...state.workflowData.steps,
[action.stepId]: step,
},
},
workflowSaved: false,
selectedStep: step,
};
}
default:
return state;
}

View File

@ -0,0 +1,7 @@
import { dispatch } from '@wordpress/data';
import { store } from './store';
import { StepType } from './types';
export const registerStepType = (stepType: StepType): void => {
dispatch(store).registerStepType(stepType);
};

View File

@ -2,7 +2,7 @@ import { createRegistrySelector } from '@wordpress/data';
import { store as interfaceStore } from '@wordpress/interface';
import { store as preferencesStore } from '@wordpress/preferences';
import { storeName } from './constants';
import { Feature, State } from './types';
import { Feature, State, StepType } from './types';
import { Item } from '../components/inserter/item';
import { Step, Workflow } from '../components/workflow/types';
@ -22,11 +22,15 @@ export function isInserterSidebarOpened(state: State): boolean {
}
export function getInserterActionSteps(state: State): Item[] {
return state.inserter.actionSteps;
return Object.values(state.stepTypes).filter(
({ group }) => group === 'actions',
);
}
export function getInserterLogicalSteps(state: State): Item[] {
return state.inserter.logicalSteps;
return Object.values(state.stepTypes).filter(
({ group }) => group === 'logical',
);
}
export function getInserterPopoverAnchor(
@ -39,6 +43,18 @@ export function getWorkflowData(state: State): Workflow {
return state.workflowData;
}
export function getWorkflowSaved(state: State): boolean {
return state.workflowSaved;
}
export function getSelectedStep(state: State): Step | undefined {
return state.selectedStep;
}
export function getStepType(state: State, key: string): StepType | undefined {
return state.stepTypes[key] ?? undefined;
}
export function getSelectedStepType(state: State): StepType | undefined {
return getStepType(state, state.selectedStep?.key);
}

View File

@ -0,0 +1,31 @@
import { createReduxStore, register, StoreDescriptor } from '@wordpress/data';
import { controls } from '@wordpress/data-controls';
import * as actions from './actions';
import { storeName } from './constants';
import { initialState } from './initial_state';
import { reducer } from './reducer';
import * as selectors from './selectors';
import { State } from './types';
import { OmitFirstArgs } from '../../../types';
type StoreType = Omit<StoreDescriptor, 'name'> & {
name: typeof storeName;
};
export const store = createReduxStore<State>(storeName, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the "Action" type is missing thunks with "dispatch"
actions: actions as any,
controls,
selectors,
reducer,
initialState,
}) as StoreType;
type StoreKey = typeof storeName | StoreType;
declare module '@wordpress/data' {
function select(key: StoreKey): OmitFirstArgs<typeof selectors>;
function dispatch(key: StoreKey): typeof actions;
}
register(store);

View File

@ -1,17 +1,29 @@
import { Item } from '../components/inserter/item';
import { ComponentType } from 'react';
import { Step, Workflow } from '../components/workflow/types';
export interface AutomationEditorWindow extends Window {
mailpoet_automation_workflow: Workflow;
}
export type StepGroup = 'actions' | 'logical';
export type StepType = {
key: string;
group: StepGroup;
title: string;
description: string;
subtitle: (step: Step) => JSX.Element | string;
icon: ComponentType;
edit: ComponentType;
foreground: string;
background: string;
};
export type State = {
stepTypes: Record<string, StepType>;
workflowData: Workflow;
workflowSaved: boolean;
selectedStep: Step | undefined;
inserter: {
actionSteps: Item[];
logicalSteps: Item[];
};
inserterSidebar: {
isOpened: boolean;
};

View File

@ -0,0 +1,6 @@
import { registerStepType } from '../../editor/store';
import { step as DelayStep } from './steps/delay';
export const initialize = (): void => {
registerStepType(DelayStep);
};

View File

@ -0,0 +1,57 @@
import {
PanelBody,
TextControl,
SelectControl,
Flex,
FlexItem,
} from '@wordpress/components';
import { dispatch, useSelect } from '@wordpress/data';
import { PlainBodyTitle } from '../../../../editor/components/panel';
import { store } from '../../../../editor/store';
import { DelayTypeOptions } from './types/delayTypes';
export function Edit(): JSX.Element {
const { selectedStep } = useSelect(
(select) => ({
selectedStep: select(store).getSelectedStep(),
}),
[],
);
return (
<PanelBody opened>
<PlainBodyTitle title="Wait for" />
<Flex align="top">
<FlexItem style={{ flex: '1 1 0' }}>
<TextControl
label=""
type="number"
placeholder="Number"
value={(selectedStep.args.delay as string) ?? ''}
onChange={(rawValue) => {
const value: number =
rawValue.length === 0 || parseInt(rawValue, 10) < 1
? 1
: parseInt(rawValue, 10);
dispatch(store).updateStepArgs(selectedStep.id, 'delay', value);
}}
/>
</FlexItem>
<FlexItem style={{ flex: '1 1 0' }}>
<SelectControl
label=""
value={(selectedStep.args.delay_type as string) ?? 'HOURS'}
options={DelayTypeOptions}
onChange={(value) =>
dispatch(store).updateStepArgs(
selectedStep.id,
'delay_type',
value,
)
}
/>
</FlexItem>
</Flex>
</PanelBody>
);
}

View File

@ -0,0 +1,7 @@
export function Icon(): JSX.Element {
return (
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M9.79683 0.666656C4.6635 0.666656 0.463501 4.86666 0.463501 9.99999C0.463501 15.1333 4.6635 19.3333 9.79683 19.3333C14.9302 19.3333 19.1302 15.1333 19.1302 9.99999C19.1302 4.86666 14.9302 0.666656 9.79683 0.666656ZM9.79683 17.4667C5.68083 17.4667 2.33017 14.116 2.33017 9.99999C2.33017 5.88399 5.68083 2.53332 9.79683 2.53332C13.9128 2.53332 17.2635 5.88399 17.2635 9.99999C17.2635 14.116 13.9128 17.4667 9.79683 17.4667ZM10.2635 5.33332H8.8635V10.9333L13.7168 13.92L14.4635 12.7067L10.2635 10.1867V5.33332Z" />
</svg>
);
}

View File

@ -0,0 +1,33 @@
import { Icon } from './icon';
import { Edit } from './edit';
import { StepType } from '../../../../editor/store/types';
import { DelayTypeOptions } from './types/delayTypes';
const getDelayInformation = (delayTypeValue: string, value: number): string =>
DelayTypeOptions.reduce((previousValue, current): string => {
if (current.value !== delayTypeValue) {
return previousValue;
}
return current.subtitle(value);
}, '');
export const step: StepType = {
key: 'core:delay',
group: 'actions',
title: 'Delay',
foreground: '#7F54B3',
background: '#f7edf7',
description: 'Wait some time before proceeding with the steps below',
subtitle: (data): string => {
if (!data.args.delay || !data.args.delay_type) {
return 'Not set up yet.';
}
return getDelayInformation(
data.args.delay_type as string,
data.args.delay as number,
);
},
icon: Icon,
edit: Edit,
} as const;

View File

@ -0,0 +1,25 @@
import { SelectControl } from '@wordpress/components';
export type DelayTypes = SelectControl.Option & {
subtitle: (value: number) => string;
};
export const DelayTypeOptions: DelayTypes[] = [
{
label: 'Hours',
subtitle: (value: number) =>
`Wait for ${value} ${value === 1 ? 'hour' : 'hours'}`,
value: 'HOURS',
},
{
label: 'Days',
subtitle: (value: number) =>
`Wait for ${value} ${value === 1 ? 'day' : 'days'}`,
value: 'DAYS',
},
{
label: 'Weeks',
subtitle: (value: number) =>
`Wait for ${value} ${value === 1 ? 'week' : 'weeks'}`,
value: 'WEEKS',
},
];

View File

@ -0,0 +1,26 @@
import classnames from 'classnames';
import { Button as WpButton } from '@wordpress/components';
type ExtendedProps = {
variant: WpButton.ButtonVariant | 'sidebar-primary';
centered?: boolean;
};
type Props =
| (Omit<WpButton.ButtonProps, keyof ExtendedProps> & ExtendedProps)
| (Omit<WpButton.AnchorProps, keyof ExtendedProps> & ExtendedProps);
export function Button({ centered, variant, ...props }: Props): JSX.Element {
return (
<WpButton
className={classnames([
variant === 'sidebar-primary'
? 'mailpoet-automation-button-sidebar-primary'
: '',
centered ? 'mailpoet-automation-button-centered' : '',
])}
variant={variant === 'sidebar-primary' ? 'primary' : variant}
{...props}
/>
);
}

View File

@ -0,0 +1,6 @@
import { registerStepType } from '../../editor/store';
import { step as SendEmailStep } from './steps/send_email';
export const initialize = (): void => {
registerStepType(SendEmailStep);
};

View File

@ -0,0 +1,68 @@
import { useCallback, useEffect, useState } from 'react';
import { dispatch, useSelect } from '@wordpress/data';
import { plus } from '@wordpress/icons';
import { Button } from '../../../components/button';
import { store } from '../../../../../editor/store';
import { MailPoet } from '../../../../../../mailpoet';
export function DesignEmailButton(): JSX.Element {
const [isSaving, setIsSaving] = useState(false);
const { selectedStep, workflowId, workflowSaved } = useSelect(
(select) => ({
selectedStep: select(store).getSelectedStep(),
workflowId: select(store).getWorkflowData().id,
workflowSaved: select(store).getSelectedStep(),
}),
[],
);
const emailId = selectedStep?.args?.email_id as string | undefined;
const workflowStepId = selectedStep.id;
const createEmail = useCallback(async () => {
setIsSaving(true);
const response = await MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',
action: 'create',
data: {
type: 'automation',
subject: '',
options: {
workflowId,
workflowStepId,
},
},
});
dispatch(store).updateStepArgs(
workflowStepId,
'email_id',
parseInt(response.data.id as string, 10),
);
dispatch(store).save();
}, [workflowId, workflowStepId]);
// 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.
useEffect(() => {
if (emailId && workflowSaved) {
window.location.href = `admin.php?page=mailpoet-newsletter-editor&id=${emailId}`;
}
}, [emailId, workflowSaved]);
return (
<Button
variant="sidebar-primary"
centered
icon={plus}
onClick={createEmail}
isBusy={isSaving}
disabled={isSaving}
>
Design email
</Button>
);
}

View File

@ -0,0 +1,98 @@
import { ComponentProps } from 'react';
import { PanelBody, TextareaControl, TextControl } from '@wordpress/components';
import { dispatch, useSelect } from '@wordpress/data';
import { DesignEmailButton } from './design_email_button';
import { Thumbnail } from './thumbnail';
import { PlainBodyTitle } from '../../../../../editor/components/panel';
import { store } from '../../../../../editor/store';
import { StepName } from '../../../../../editor/components/panel/step-name';
function SingleLineTextareaControl(
props: ComponentProps<typeof TextareaControl>,
): JSX.Element {
return (
<TextareaControl
{...props}
onChange={(value) =>
// replace a newline or a group of multiple newlines by a space (text pasting)
props.onChange(value.replaceAll(/(\r?\n)+/g, ' '))
}
onKeyDown={(event) => {
// disable inserting newlines via "Enter" key
if (event.key === 'Enter') {
event.preventDefault();
}
if (props.onKeyDown) {
props.onKeyDown(event);
}
}}
/>
);
}
export function EmailPanel(): JSX.Element {
const { selectedStep, selectedStepType } = useSelect(
(select) => ({
selectedStep: select(store).getSelectedStep(),
selectedStepType: select(store).getSelectedStepType(),
}),
[],
);
return (
<PanelBody opened>
<StepName
currentName={(selectedStep.args.name as string) ?? ''}
defaultName={selectedStepType.title}
update={(value) => {
dispatch(store).updateStepArgs(selectedStep.id, 'name', value);
}}
/>
<TextControl
label="“From” name"
placeholder="John Doe"
value={(selectedStep.args.sender_name as string) ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(selectedStep.id, 'sender_name', value)
}
/>
<TextControl
type="email"
label="“From” email address"
placeholder="you@domain.com"
value={(selectedStep.args.sender_address as string) ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(
selectedStep.id,
'sender_address',
value,
)
}
/>
<SingleLineTextareaControl
label="Subject"
placeholder="Type in subject…"
value={(selectedStep.args.subject as string) ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(selectedStep.id, 'subject', value)
}
/>
<SingleLineTextareaControl
label="Preheader"
placeholder="Type in preheader…"
value={(selectedStep.args.preheader as string) ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(selectedStep.id, 'preheader', value)
}
/>
<div className="mailpoet-automation-email-content-separator" />
<PlainBodyTitle title="Email content" />
{selectedStep.args.email_id ? (
<Thumbnail emailId={selectedStep.args.email_id as number} />
) : (
<DesignEmailButton />
)}
</PanelBody>
);
}

View File

@ -0,0 +1,50 @@
import { PanelBody, ToggleControl } from '@wordpress/components';
import { dispatch, useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { PremiumModal } from 'common/premium_modal';
import { Hooks } from 'wp-js-hooks';
import { store } from '../../../../../editor/store';
export function GoogleAnalyticsPanel(): JSX.Element {
const { selectedStep } = useSelect(
(select) => ({ selectedStep: select(store).getSelectedStep() }),
[],
);
const enabled = typeof selectedStep.args?.ga_campaign !== 'undefined';
const panelBody = Hooks.applyFilters(
'mailpoet.automation.send_email.google_analytics_panel',
<PremiumModal
onRequestClose={() =>
dispatch(store).updateStepArgs(
selectedStep.id,
'ga_campaign',
undefined,
)
}
>
{__(
'Google Analytics tracking is not available in the free version of the MailPoet plugin.',
'mailpoet',
)}
</PremiumModal>,
);
return (
<PanelBody title="Google analytics" initialOpen={false}>
<ToggleControl
label="Enable custom GA tracking"
checked={enabled}
onChange={(value) =>
dispatch(store).updateStepArgs(
selectedStep.id,
'ga_campaign',
value ? '' : undefined,
)
}
/>
{enabled && panelBody}
</PanelBody>
);
}

View File

@ -0,0 +1,13 @@
import { EmailPanel } from './email_panel';
import { GoogleAnalyticsPanel } from './google_analytics_panel';
import { ReplyToPanel } from './reply_to_panel';
export function Edit(): JSX.Element {
return (
<>
<EmailPanel />
<ReplyToPanel />
<GoogleAnalyticsPanel />
</>
);
}

View File

@ -0,0 +1,72 @@
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
import { dispatch, useSelect } from '@wordpress/data';
import { store } from '../../../../../editor/store';
export function ReplyToPanel(): JSX.Element {
const { selectedStep } = useSelect(
(select) => ({
selectedStep: select(store).getSelectedStep(),
}),
[],
);
const replyToName = selectedStep.args.reply_to_name as string | undefined;
const replyToAddress = selectedStep.args.reply_to_address as
| string
| undefined;
const enabled =
typeof replyToName !== 'undefined' || typeof replyToAddress !== 'undefined';
return (
<PanelBody title="Reply to" initialOpen={false}>
<ToggleControl
label="Use different email address for getting replies to the email"
checked={enabled}
onChange={(value) => {
dispatch(store).updateStepArgs(
selectedStep.id,
'reply_to_name',
value ? '' : undefined,
);
dispatch(store).updateStepArgs(
selectedStep.id,
'reply_to_address',
value ? '' : undefined,
);
}}
/>
{enabled && (
<>
<TextControl
label="“Reply to” name"
placeholder="John Doe"
value={replyToName ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(
selectedStep.id,
'reply_to_name',
value,
)
}
/>
<TextControl
type="email"
label="“Reply to” email address"
placeholder="you@domain.com"
value={replyToAddress ?? ''}
onChange={(value) =>
dispatch(store).updateStepArgs(
selectedStep.id,
'reply_to_address',
value,
)
}
/>
</>
)}
</PanelBody>
);
}

View File

@ -0,0 +1,66 @@
import { ComponentProps, ComponentType, useEffect, useState } from 'react';
import { Spinner as WpSpinner } from '@wordpress/components';
import { Button } from '../../../components/button';
import { MailPoetAjax } from '../../../../../../ajax';
// @types/wordpress__components don't define "className", which is supported
const Spinner = WpSpinner as ComponentType<
ComponentProps<typeof WpSpinner> & { className?: string }
>;
type Props = {
emailId: number;
};
export function Thumbnail({ emailId }: Props): JSX.Element {
const [thumbnailUrl, setThumbnailUrl] = useState<string>();
useEffect(() => {
const getData = async () => {
const data = await MailPoetAjax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',
action: 'get',
data: { id: emailId },
});
// TODO: we need to implement thumbnails backend first
if (data?.data?.thumbnail_url) {
setThumbnailUrl(data.data.thumbnail_url as string);
}
};
void getData();
}, [emailId]);
return (
<>
<div className="mailpoet-automation-thumbnail-box">
{thumbnailUrl ? (
<div className="mailpoet-automation-thumbnail-wrapper">
<img
className="mailpoet-automation-thumbnail-image"
src={thumbnailUrl}
alt="Email thumbnail"
/>
</div>
) : (
<Spinner className="mailpoet-automation-thumbnail-spinner" />
)}
</div>
<div className="mailpoet-automation-thumbnail-buttons">
<Button
variant="sidebar-primary"
centered
href={`?page=mailpoet-newsletter-editor&id=${emailId}`}
>
Edit content
</Button>
<Button variant="secondary" centered>
Preview
</Button>
</div>
</>
);
}

View File

@ -0,0 +1,11 @@
export function Icon(): JSX.Element {
return (
<svg viewBox="0 0 16 12" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.73578 1.5L8 6.01219L13.2642 1.5H2.73578ZM14.5 2.41638L8.48809 7.56944L8 7.98781L7.51191 7.56944L1.5 2.41638V10C1.5 10.2761 1.72386 10.5 2 10.5H14C14.2761 10.5 14.5 10.2761 14.5 10V2.41638ZM0 2C0 0.89543 0.89543 0 2 0H14C15.1046 0 16 0.895431 16 2V10C16 11.1046 15.1046 12 14 12H2C0.895431 12 0 11.1046 0 10V2Z"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
import { Icon } from './icon';
import { Edit } from './edit';
import { StepType } from '../../../../editor/store/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',
foreground: '#996800',
background: '#FCF9E8',
icon: Icon,
edit: Edit,
} as const;

View File

@ -1,4 +1,4 @@
import { EllipsisMenu, MenuItem } from '@woocommerce/components';
import { EllipsisMenu, MenuItem } from '@woocommerce/components/build';
import { __ } from '@wordpress/i18n';
import { Workflow } from '../../workflow';

View File

@ -1,4 +1,4 @@
import { Search, TableCard } from '@woocommerce/components';
import { Search, TableCard } from '@woocommerce/components/build';
import { Button, Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { getRow } from './get-row';
@ -27,7 +27,7 @@ export function AutomationListing({ workflows, loading }: Props): JSX.Element {
isLoading={workflows.length === 0 || loading}
headers={headers}
rows={rows}
rowKey={(data, i) => data[i].id}
rowKey={(_, i) => workflows[i].id}
query={{ page: 2 }}
rowsPerPage={7}
totalRows={workflows.length}

View File

@ -0,0 +1,55 @@
import { getQueryArg, removeQueryArgs } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
import { Notice } from '../../notices/notice';
export const LISTING_NOTICE_PARAMETERS = {
workflowHadBeenDeleted: 'mailpoet-had-been-deleted',
workflowDeleted: 'mailpoet-workflow-deleted',
};
export function WorkflowListingNotices(): JSX.Element {
const workflowHadBeenDeleted = parseInt(
getQueryArg(
window.location.href,
LISTING_NOTICE_PARAMETERS.workflowHadBeenDeleted,
) as string,
10,
);
const workflowDeleted = parseInt(
getQueryArg(
window.location.href,
LISTING_NOTICE_PARAMETERS.workflowDeleted,
) as string,
10,
);
if (Number.isNaN(workflowHadBeenDeleted) && Number.isNaN(workflowDeleted)) {
return null;
}
const urlWithoutNotices = removeQueryArgs(
window.location.href,
...Object.values(LISTING_NOTICE_PARAMETERS),
);
window.history.pushState('', '', urlWithoutNotices);
if (workflowHadBeenDeleted) {
return (
<Notice type="error" closable timeout={false}>
<p>
{__(
'You cannot edit this automation because it is in the Trash.',
'mailpoet',
)}
</p>
</Notice>
);
}
if (workflowDeleted) {
return (
<Notice type="success" closable timeout={false}>
<p>{__('1 workflow moved to the Trash.', 'mailpoet')}</p>
</Notice>
);
}
return null;
}

View File

@ -17,7 +17,8 @@ const createDelayStep = (nextStepId: string) => ({
key: 'core:delay',
next_step_id: nextStepId,
args: {
seconds: 60,
delay: 1,
delay_type: 'HOURS',
},
});
@ -70,18 +71,21 @@ export function CreateTestingWorkflowButton(): JSX.Element {
}
type TemplateButtonProps = {
template: string;
slug: string;
children?: ReactNode;
};
export function CreateWorkflowFromTemplateButton({
template,
slug,
children,
}: TemplateButtonProps): JSX.Element {
const [createWorkflowFromTemplate, { loading, error }] = useMutation(
'workflows/create-from-template',
{
method: 'POST',
body: JSON.stringify({
slug,
}),
},
);
@ -91,12 +95,7 @@ export function CreateWorkflowFromTemplateButton({
className="button button-primary"
type="button"
onClick={async () => {
await createWorkflowFromTemplate({
body: JSON.stringify({
name: `Test from template ${new Date().toISOString()}`,
template,
}),
});
await createWorkflowFromTemplate();
window.location.reload();
}}
disabled={loading}

View File

@ -0,0 +1,198 @@
import { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { noop } from 'lodash';
import { MailPoet } from 'mailpoet';
import { Modal } from 'common/modal/modal';
import {
ManageSenderDomain,
SenderDomainDnsItem,
SenderDomainEntity,
} from 'common/manage_sender_domain';
import { isErrorResponse, Response, ErrorResponse } from 'ajax';
interface SenderDomainApiResponseType extends Response {
data: SenderDomainDnsItem[];
}
type VerifyResponseType = {
dns: SenderDomainDnsItem[];
ok: boolean;
error?: string;
};
interface SenderDomainApiVerifyResponseType extends Response {
data: VerifyResponseType;
}
type ApiActionType = 'fetch' | 'create' | 'verify';
/**
* @param {string} domain - Sender Domain
* @param {ApiActionType} type - action type
* @returns {Promise}
*/
const makeApiRequest = (domain: string, type: ApiActionType = 'fetch') => {
let requestAction = 'getAuthorizedSenderDomains';
if (type === 'create') {
requestAction = 'createAuthorizedSenderDomain';
} else if (type === 'verify') {
requestAction = 'verifyAuthorizedSenderDomain';
}
return MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'settings',
action: requestAction,
data: { domain },
});
};
const getApiErrorMessage = (error: { error?: ErrorResponse }): string =>
isErrorResponse(error) && error.errors[0] && error.errors[0].message
? error.errors[0].message
: '';
const generateRowData = (senderDomain: string, dns: SenderDomainDnsItem[]) => {
const row: SenderDomainEntity[] = [
{
domain: senderDomain,
dns,
},
];
return row;
};
type Props = {
senderDomain: string;
onRequestClose: () => void;
setVerifiedSenderDomain?: (senderDomain: string) => void;
useModal: boolean;
};
function AuthorizeSenderDomainModal({
senderDomain,
onRequestClose,
setVerifiedSenderDomain,
useModal,
}: Props): JSX.Element {
const [errorMessage, setErrorMessage] = useState('');
const [loadingButton, setLoadingButton] = useState(false);
const [rowData, setRowData] = useState<SenderDomainEntity[]>([]);
const modalIsOpened = useRef<boolean>(false);
const performStateUpdate = (callback: (param) => void, args) => {
if (!modalIsOpened.current) return; // do nothing if modal is not opened
callback(args);
};
const verifyDnsButtonClicked = async () => {
setLoadingButton(true);
try {
const res: SenderDomainApiVerifyResponseType = await makeApiRequest(
senderDomain,
'verify',
);
if (!modalIsOpened.current) return;
setRowData(generateRowData(senderDomain, res.data.dns));
if (res.data.ok) {
// record verified, close the modal
setErrorMessage('');
setVerifiedSenderDomain(senderDomain);
onRequestClose();
}
} catch (e) {
const error: { error?: ErrorResponse; meta?: VerifyResponseType } = e;
if (!modalIsOpened.current) return;
setRowData(generateRowData(senderDomain, error?.meta?.dns || []));
const apiErrorMessage = getApiErrorMessage(e);
setErrorMessage(apiErrorMessage || error?.meta?.error || '');
}
performStateUpdate(setLoadingButton, false);
};
useEffect(() => {
if (!senderDomain) {
return null;
}
modalIsOpened.current = true;
const allSenderDomains = window.mailpoet_all_sender_domains || [];
(async () => {
try {
if (allSenderDomains.includes(senderDomain)) {
// sender domain already exist
const res: SenderDomainApiResponseType = await makeApiRequest(
senderDomain,
);
performStateUpdate(
setRowData,
generateRowData(senderDomain, res.data),
);
} else {
// create new sender domain
const res: SenderDomainApiResponseType = await makeApiRequest(
senderDomain,
'create',
);
performStateUpdate(
setRowData,
generateRowData(senderDomain, res.data),
);
}
} catch (e) {
const apiErrorMessage = getApiErrorMessage(e);
performStateUpdate(setErrorMessage, apiErrorMessage);
}
})().catch(() => {
// do nothing
});
return () => {
modalIsOpened.current = false;
};
}, [senderDomain]);
const content = (
<>
{errorMessage && (
<strong className="mailpoet_error_item mailpoet_error">
{' '}
{errorMessage}{' '}
</strong>
)}
<ManageSenderDomain
rows={rowData}
verifyDnsButtonClicked={verifyDnsButtonClicked}
loadingButton={loadingButton}
/>
</>
);
return useModal ? (
<Modal
onRequestClose={onRequestClose}
contentClassName="authorize-sender-domain-modal"
>
{content}
</Modal>
) : (
<div>{content}</div>
);
}
AuthorizeSenderDomainModal.propTypes = {
senderDomain: PropTypes.string.isRequired,
useModal: PropTypes.bool,
};
AuthorizeSenderDomainModal.defaultProps = {
setVerifiedSenderDomain: noop,
useModal: true,
};
export { AuthorizeSenderDomainModal };

View File

@ -0,0 +1,109 @@
import { MailPoet } from 'mailpoet';
import classnames from 'classnames';
import { extractEmailDomain, extractPageNameFromUrl } from 'common/functions';
import { Tabs } from './tabs/tabs';
import { Tab } from './tabs/tab';
import { Modal } from './modal/modal';
import { AuthorizeSenderEmailModal } from './authorize_sender_email_modal';
import { AuthorizeSenderDomainModal } from './authorize_sender_domain_modal';
const trackEvent = (type: 'email' | 'domain') => {
const page = `${extractPageNameFromUrl() || 'some other'} page`;
if (type === 'email') {
MailPoet.trackEvent('MSS in plugin authorize email', {
'attempt to authorize': type,
'original page': page,
});
} else if (type === 'domain') {
MailPoet.trackEvent('MSS in plugin verify sender domain', {
'attempt to authorize': type,
'original page': page,
});
}
};
type Props = {
onRequestClose: () => void;
senderEmail: string;
onSuccessAction: (param: { type: 'email' | 'domain'; data: string }) => void;
showSenderEmailTab: boolean;
showSenderDomainTab: boolean;
initialTab: 'sender_email' | 'sender_domain';
};
function AuthorizeSenderEmailAndDomainModal({
onRequestClose,
senderEmail,
onSuccessAction,
showSenderEmailTab = false,
showSenderDomainTab = false,
initialTab = 'sender_email',
}: Props): JSX.Element {
if (!senderEmail) return null;
const emailAddressDomain = extractEmailDomain(senderEmail);
if (showSenderEmailTab) {
trackEvent('email');
}
if (showSenderDomainTab) {
trackEvent('domain');
}
return (
<Modal
onRequestClose={onRequestClose}
contentClassName="authorize-sender-email-and-domain-modal"
>
<Tabs activeKey={initialTab}>
<Tab
key="sender_email"
className={classnames({
mailpoet_hidden: !showSenderEmailTab,
})}
title={MailPoet.I18n.t(
'authorizeSenderEmailAndDomainModalSenderEmailTabTitle',
)}
>
{showSenderEmailTab && (
<AuthorizeSenderEmailModal
useModal={false}
senderEmail={senderEmail}
onRequestClose={onRequestClose}
setAuthorizedAddress={(authorizedEmailAddress) => {
onSuccessAction({
type: 'email',
data: authorizedEmailAddress,
});
}}
/>
)}
</Tab>
<Tab
key="sender_domain"
className={classnames({
mailpoet_hidden: !showSenderDomainTab,
})}
title={MailPoet.I18n.t(
'authorizeSenderEmailAndDomainModalSenderDomainTabTitle',
)}
>
{showSenderDomainTab && (
<AuthorizeSenderDomainModal
useModal={false}
senderDomain={emailAddressDomain}
onRequestClose={onRequestClose}
setVerifiedSenderDomain={(verifiedSenderDomain) => {
onSuccessAction({ type: 'domain', data: verifiedSenderDomain });
}}
/>
)}
</Tab>
</Tabs>
</Modal>
);
}
export { AuthorizeSenderEmailAndDomainModal };

View File

@ -7,6 +7,7 @@ import { MailPoet } from 'mailpoet';
import { Modal } from 'common/modal/modal';
import { Button, Loader } from 'common';
import { isErrorResponse, ErrorResponse } from 'ajax';
import { Grid } from 'common/grid';
const SET_INTERVAL_PERFORM_REQUEST_EVERY_SECONDS = 15;
@ -69,12 +70,14 @@ type Props = {
senderEmail: string;
onRequestClose: () => void;
setAuthorizedAddress?: (emailAddress: string) => void;
useModal: boolean;
};
function AuthorizeSenderEmailModal({
senderEmail,
onRequestClose,
setAuthorizedAddress,
useModal,
}: Props) {
const [createEmailApiResponse, setCreateEmailApiResponse] =
useState<boolean>(null);
@ -181,15 +184,8 @@ function AuthorizeSenderEmailModal({
*/
}, [senderEmailAddress]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Modal
title={MailPoet.I18n.t('authorizeSenderEmailModalTitle').replace(
'[senderEmail]',
senderEmailAddress,
)}
onRequestClose={onRequestClose}
contentClassName="authorize-sender-email-modal"
>
const content = (
<>
{createEmailApiResponse && (
<p>
{ReactStringReplace(
@ -208,7 +204,11 @@ function AuthorizeSenderEmailModal({
</>
)}
{showLoader && <Loader size={64} />}
{showLoader && (
<Grid.Column align="center">
<Loader size={64} />
</Grid.Column>
)}
{confirmEmailApiResponse && (
<>
@ -219,16 +219,33 @@ function AuthorizeSenderEmailModal({
</Button>
</>
)}
</>
);
return useModal ? (
<Modal
title={MailPoet.I18n.t('authorizeSenderEmailModalTitle').replace(
'[senderEmail]',
senderEmailAddress,
)}
onRequestClose={onRequestClose}
contentClassName="authorize-sender-email-modal"
>
{content}
</Modal>
) : (
<div>{content}</div>
);
}
AuthorizeSenderEmailModal.propTypes = {
senderEmail: PropTypes.string.isRequired,
useModal: PropTypes.bool,
};
AuthorizeSenderEmailModal.defaultProps = {
setAuthorizedAddress: noop,
useModal: true,
};
export { AuthorizeSenderEmailModal };

View File

@ -17,10 +17,14 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
target?: '_blank' | '_self' | '_parent' | '_top' | string;
automationId?: string;
className?: string;
dataTip?: boolean;
dataFor?: string;
}
export function Button({
children,
dataFor,
dataTip,
dimension,
variant,
withSpinner,
@ -56,6 +60,8 @@ export function Button({
'button-small': dimension === 'small',
})}
data-automation-id={automationId}
data-tip={dataTip}
data-for={dataFor}
>
{iconStart}
{children && <span>{children}</span>}

View File

@ -0,0 +1,54 @@
import { MailPoet } from 'mailpoet';
import { extractEmailDomain } from 'common/functions';
/**
* @param {string} email - Email address
* @param {ApiActionType} type - action type
* @returns {Promise}
*/
const makeApiRequest = (domain: string) =>
MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'settings',
action: 'checkDomainDmarcPolicy',
data: { domain },
});
/**
* Check domain DMARC policy
*
* returns `false` if not required, `true` if DMARC policy is Restricted
* @param {string} email Email address
* @param {boolean} isMssActive Is MailPoet sending service active?
* @returns {Promise<boolean>} false if not required, `true` if DMARC policy is Restricted
*/
const checkSenderEmailDomainDmarcPolicy = async (
email: string,
isMssActive = window.mailpoet_mss_active,
) => {
if (!email) return false;
if (!isMssActive) {
return false;
}
const emailAddressDomain = extractEmailDomain(email);
const isDomainVerified = (
window.mailpoet_verified_sender_domains || []
).includes(emailAddressDomain);
if (isDomainVerified) {
// do nothing if the email domain is verified
return false;
}
try {
const res = await makeApiRequest(emailAddressDomain);
const isDmarcPolicyRestricted = Boolean(res?.data?.isDmarcPolicyRestricted);
return isDmarcPolicyRestricted;
} catch (error) {
// do nothing for now when the request fails
return false;
}
};
export { checkSenderEmailDomainDmarcPolicy };

View File

@ -0,0 +1,5 @@
export const extractEmailDomain = (email: string): string =>
String(email || '')
.split('@')
.pop()
.toLowerCase();

View File

@ -0,0 +1,7 @@
export const extractPageNameFromUrl = () => {
const searchParam = new URLSearchParams(window.location.search);
const searchParamPage = searchParam.get('page') || '';
const mailpoetPageName = searchParamPage.replace('mailpoet-', '');
const pageNameFromUrl = mailpoetPageName || searchParamPage || '';
return pageNameFromUrl;
};

View File

@ -3,3 +3,5 @@ export * from './t';
export * from './is_email';
export * from './set_lowercase_value';
export * from './parsley_helper_functions';
export * from './extract_email_domain';
export * from './extract_page_name_from_url';

View File

@ -64,7 +64,7 @@ export const addOrUpdateError = (
};
/**
* Alias to Parsely removeError method
* Alias to Parsely reset method
*
* Remove an already present error message.
*
@ -72,13 +72,11 @@ export const addOrUpdateError = (
* @param {string} parsleyFieldName Parsely Error name
* @returns
*/
export const removeError = (
export const resetFieldError = (
domElementSelector: string,
parsleyFieldName: string,
) => {
if (!doesErrorFieldExist(parsleyFieldName)) return; // do nothing if error message does not exist
jQuery(domElementSelector)
.parsley()
.removeError(parsleyFieldName, { updateClass: true });
jQuery(domElementSelector).parsley().reset();
};

View File

@ -0,0 +1,55 @@
import classnames from 'classnames';
import { Tooltip } from 'common/tooltip/tooltip';
import { Button } from 'common/button/button';
import { useRef } from 'react';
import { copy } from '@wordpress/icons';
const copyTextToClipboard = (value: string) => {
if (!navigator.clipboard) {
try {
document.execCommand('copy');
} catch (error) {
// noop
}
return;
}
navigator.clipboard
.writeText(value)
.then()
.catch(() => {
// noop
});
};
function DomainKeyComponent({ className = '', tooltip = '', ...props }) {
const inputRef = useRef<HTMLInputElement>();
const performActionOnClick = () => {
inputRef.current?.focus();
inputRef.current?.select();
copyTextToClipboard(inputRef.current?.value);
};
return (
<div className={classnames(className, 'mailpoet-form-input', {})}>
<input ref={inputRef} onClick={performActionOnClick} {...props} />
{tooltip && (
<>
<Button
iconStart={copy}
variant="secondary"
onClick={performActionOnClick}
dataTip
dataFor={props.name}
/>
<Tooltip id={props.name} place="top">
<span> {tooltip} </span>
</Tooltip>
</>
)}
</div>
);
}
export { DomainKeyComponent };

Some files were not shown because too many files have changed in this diff Show More